Skip to main content

skip to main content

developerWorks  >  Java technology  >

AOP@Work: Introducing AspectJ 5

A first look at Java 5 support in AspectJ, and other new features

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Advanced

Adrian Colyer (adrian_colyer@uk.ibm.com), AspectJ project leader and IBM senior technical staff member, IBM

12 Jul 2005

Now in its second milestone build, AspectJ 5 is a big leap forward for aspect-oriented programming on the Java™ platform. A major focus of AspectJ 5 is on providing support for the new Java language features introduced in Java 5, including annotations and generics. In addition, the language contains new features not tied to Java 5, such as an annotation-based style for writing aspects, improved load-time weaving, and a new aspect instantiation model. Get a first look at AspectJ 5 from Adrian Colyer, lead developer on the project, as he introduces you to both the AspectJ 5 language and the release containing the AspectJ compiler and associated tools.

A major focus of AspectJ 5 (now in its second milestone build) is on providing support for the new Java language features introduced in Java 5, including annotations and generics. AspectJ 5 also contains new features not tied to Java 5, such as an annotation-based development style, improved load-time weaving, and a new aspect instantiation model.

In this installment of the AOP@Work series, I provide an overview of both AspectJ 5 the language and the AspectJ 5 release containing the AspectJ compiler and associated tools. I start by looking at how to compile Java applications using the AspectJ 5 compiler (using either the command-line compiler or the AspectJ Development Tools (AJDT; see Resources)), and then I give you some examples of Java 5 features being used to implement AspectJ applications. I also discuss the implications for AOP systems of erasure, the technique used to implement generics in Java 5, and I explain AspectJ's approach to resolving the issues. Some of the features described in this article will only compile with the forthcoming AspectJ 5 M3 build (scheduled for availability in July 2005).

You may also want to download AJDT or the command-line AspectJ compiler for use in following the examples. See Resources for links to technology downloads.

Compiling Java 5 applications with AspectJ

The AspectJ compiler (ajc) supports compilation of Java source code at the version 1.3 (and earlier), 1.4, and 5.0 compliance levels and can produce byte codes targeted at any VM version from version 1.1 onwards. Like javac, ajc presents some restrictions: compilation from source at the 1.4 compliance level only supports targeting a 1.4 or higher VM, and compilation from source at the 5.0 compliance level only supports targeting a 5.0 VM.

About this series

The AOP@Work series is intended for developers who have some background in aspect-oriented programming and want to expand or deepen what they know. As with most developerWorks articles, the series is highly practical: you can expect to come away from every article with new knowledge that you can put immediately to use.

Each of the authors contributing to the series has been selected for his leadership or expertise in aspect-oriented programming. Many of the authors are contributors to the projects or tools covered in the series. Each article is subjected to a peer review to ensure the fairness and accuracy of the views expressed.

Please contact the authors individually with comments or questions about their articles. To comment on the series as a whole, you may contact series lead Nicholas Lesiecki. See Resources for more background on AOP.

The default compliance level of the AspectJ compiler is to use the 5.0 source level and produce byte codes targeted for a 5.0 VM. You can explicitly set the source compliance and target levels for Java 5 by passing the -1.5 compiler option. If you want to use the AspectJ 5 compiler to work with (say) the Java 1.4 language and target the 1.4 VM, you can simply pass -1.4 instead.

The AspectJ 5 weaver also runs in Java 5-compliant mode by default. In this mode, the weaver will correctly interpret the new features in Java 5; for example, it will consider autoboxing and unboxing when determining whether args(Integer) matches an int argument. If you are not compiling from source but instead are using the AspectJ compiler to weave aspects (on the aspectpath) with compiled Java 5 .class files (on the inpath), then this is the behavior you will want. Passing the -1.4 or -1.3 option will disable the Java 5 features.

The AspectJ Development Environment Guide contains more information on the new compiler flags and options. See the Resources section to access the guide.

Compilation with the AJDT and Eclipse

If you're using the AJDT to build and run AspectJ programs, then AspectJ inherits the compiler options specified for Java compilation in Eclipse. In this case, you can configure AspectJ to use Java 5 mode either as a workspace preference or on a per-project basis. Simply go to the Java compiler preferences page and set the Compiler compliance level property to 5.0. You may also need to update the JRE system library to a Java 5 JRE in your project's build settings if you are upgrading from JDK 1.4.

Figure 1 shows the AJDT's Java compiler preference page and a preference setting for Java 5.0 compliance.


Figure 1. Specifying the 5.0 compliance level in Eclipse
Specifying 5.0 compliance level in Eclipse


Back to top


Using Java 5 features in aspects

With the basics out of the way, we can now turn our attention to using Java 5 features in AspectJ. I've chosen an example aspect that supports a basic set of lifecycle operations for any type annotated as a ManagedComponent. ManagedComponent is a simple marker annotation, as shown below:

public @interface ManagedComponent {}

The aspect itself has been designed to show off a number of the Java 5 and/or AspectJ 5 language features, including enums, annotations, new-style for loops, and generics. The first part of the LifecycleManager aspect simply defines an enum indicating the possible states that a managed component can be in, and the Lifecycle interface that managed components will support, as shown in Listing 1:


Listing 1. LifecycleManager aspect with State and Lifecycle declarations

/**
 * This aspect provides default lifecycle management for all
 * types with the @ManagedComponent annotation.
 */
public aspect LifecycleManager {

    /**
     * The defined states that a managed component can be in.
     */
    public enum State {
        INITIAL,
        INITIALIZING,INITIALIZED,
        STARTING,STARTED,
        STOPPING,
        TERMINATING,TERMINATED,
        BROKEN;
    }
    
    /**
     * The lifecycle interface supported by managed components.
     */
    public interface Lifecycle {
        void initialize();
        void start();
        void stop();
        void terminate();
        boolean isBroken();
        State getState();
        void addObserver(LifecycleObserver observer);
        void removeObserver(LifecycleObserver observer);
    }
    
    ...

Annotation-based type matching

The next piece of the aspect uses some of the new AspectJ 5 support for matching types based on annotations. It simply says that any type with the ManagedComponent annotation implements the Lifecycle interface (and hence will acquire all the behaviors defined for such components later in the aspect). The type pattern "@ManagedComponent *" matches a type with any name that has the ManagedComponent annotation, as shown in Listing 2:


Listing 2. Declare parents with annotation-based type matching

    /**
     * Any type with an @ManagedComponent annotation implements
     * the Lifecycle interface (and acquires the default implementation
     * defined in this aspect if none is provided by the type).
     */
    declare parents : @ManagedComponent * implements Lifecycle;

The LifeCycleObserver interface

Listing 3 shows the definition of the LifecycleObserver interface referenced in the add/remove observer operations in Lifecycle:


Listing 3. LifecycleObserver interface

    /**
     * Interface to be implemented by any type needing to
     * observe the lifecycle events of managed components.
     */
    public interface LifecycleObserver {
        void componentInitialized(Lifecycle component);
        void componentStarted(Lifecycle component);
        void componentStopped(Lifecycle component);
        void componentTerminated(Lifecycle component);
        void componentBroken(Lifecycle component);
    }

The aspect provides default implementations of the Lifecycle operations for all implementers that do not provide their own definitions. It also declares private state and observers fields for all implementers. Note that the state field is an enumerated type and the observers field uses a parameterized type, as you can see in Listing 4:


Listing 4. Default implementation of the Lifecycle interface

   // default implementations for the state-based lifecycle events
    private State Lifecycle.state = State.INITIAL;
    public void Lifecycle.initialize() {}
    public void Lifecycle.start() {}
    public void Lifecycle.stop() {}
    public void Lifecycle.terminate() {}
    public boolean Lifecycle.isBroken() { return state == State.BROKEN; }
    public State Lifecycle.getState() { return state; }
    
    // default implementation of the add/remove observer lifecycle operations
    private List<LifecycleObserver> Lifecycle.observers = 
      new ArrayList<LifecycleObserver>();
    public void Lifecycle.addObserver(LifecycleObserver observer) {
        observers.add(observer);
    }
    public void Lifecycle.removeObserver(LifecycleObserver observer) {
        observers.remove(observer);
    }

State management and event handling

Because I want to cover a lot of ground in this article, I will just present some extracts from the remaining aspect implementation. For each lifecycle event, the aspect provides before and after returning advice to verify the managed component is in a valid state to perform the operation and to inform any registered observers of the transition, as shown below:


Listing 5. State management and notification

 // these pointcuts capture the lifecycle events of managed components
 pointcut initializing(Lifecycle l) : 
   execution(* Lifecycle.initialize(..)) && this(l);
 pointcut starting(Lifecycle l) : 
   execution(* Lifecycle.starting(..)) && this(l);
 pointcut stopping(Lifecycle l) : 
   execution(* Lifecycle.stopping(..)) && this(l);
 pointcut terminating(Lifecycle l): 
   execution(* Lifecycle.terminating(..)) && this(l);
 
 /**
  * Ensure we are in the initial state before initializing.
  */
 before(Lifecycle managedComponent) : initializing(managedComponent) {
     if (managedComponent.state != State.INITIAL)
         throw new IllegalStateException("Can 
           only initialize from INITIAL state");
     managedComponent.state = State.INITIALIZING;
 }
 
 /**
  * If we successfully initialized the component, update the state and
  * notify all observers.
  */
 after(Lifecycle managedComponent) returning : initializing(managedComponent) {
     managedComponent.state = State.INITIALIZED;
     for (LifecycleObserver observer: managedComponent.observers)
         observer.componentInitialized(managedComponent);
 }

Note the use of a new-style for loop within the body of the advice to iterate over all registered observers. The after returning advice executes if the lifecycle operation returns normally. If a lifecycle operation exits via a run-time exception, then the following advice (Listing 6) transitions the component to the BROKEN state. It is possible to imagine further advice in the aspect that would prevent the execution of any operation on a managed component in the BROKEN state, but that discussion is beyond the scope of this article:


Listing 6. Failure detection and transition to the BROKEN state

     /**
      * If any operation on a managed component fails with a runtime exception
      * then move to the broken state and notify any observers.
      */
    after(Lifecycle managedComponent) throwing(RuntimeException ex) :
        execution(* *(..)) 
        && this(managedComponent) {
        managedComponent.state = State.BROKEN;
        for (LifecycleObserver observer: managedComponent.observers)
            observer.componentBroken(managedComponent);
    }


The example aspect has shown that Java 5 features can be used just as easily within aspects as they can within classes. It has also given you just a hint of what AspectJ 5 can do in terms of matching based on the presence or absence of annotations (in the declare parents statement). There's a whole lot more to annotation matching in AspectJ 5 though, as you'll see in the next section.



Back to top


Join point matching and annotations

Previous articles in the AOP@Work series have explored the relationship between annotations, metadata, and aspect-oriented programming (see "AOP and metadata" in the Resources section), so here I'm going to jump straight in and show you some of what AspectJ 5 can do.

For the sake of example, I'll use some annotations inspired by the EJB 3.0 specification (See Resources). Methods that have a transaction policy associated with them can be annotated using the @Tx annotation. For example:

 @Tx(TxType.REQUIRED) 
 void credit(Money amount) {...}

If you wanted to write a TransactionManager aspect, you would be interested in the execution of methods with the @Tx annotation. Writing a pointcut to match them is simple, as Listing 7 shows:


Listing 7. Matching transactional method executions

public aspect TransactionManager {

    /**
     * The execution of any method that has the @Tx
     * annotation
     */
    pointcut transactionalMethodExecution() :
        execution(@Tx * *(..));
    
    /**
     * Placeholder for implementing tx policies
     */
    Object around() : transactionalMethodExecution() {
        return proceed();
    }
    
}

Matching annotated method calls and executions

The execution(@Tx * *(..)) pointcut expression matches the execution of any method, with any name, in any type, taking any arguments, where the method is annotated with @Tx. It is also possible to narrow some of the constraints if you need to. Matching calls to transactional methods is equally simple, you just write "call(@Tx * *(..))".

In this case, the advice that implements the transaction policies will need to know the value of the @Tx annotation on the executing method. With AspectJ, you can bind contextual values at a join point in pointcut expressions, and hence expose context to advice. In AspectJ 5, this ability is extended to annotations using the new pointcut designator, @annotation. Like all the other context-binding pointcut designators in AspectJ, @annotation plays a dual role, both restricting join point matches to join points where the subject (method, field, constructor, etc.) has an annotation of the given type, and also exposing that value. You can easily refine the fledgling TransactionManager aspect to take advantage of this, as shown here:


Listing 8. Exposing an annotation value

public aspect TransactionManager {

    /**
     * The execution of any method that has the @Tx
     * annotation
     */
    pointcut transactionalMethodExecution(Tx tx) :
        execution(* *(..)) && @annotation(tx);
    
    /**
     * Placeholder for implementing tx policies
     */
    Object around(Tx tx) : transactionalMethodExecution(tx) {
        TxType transactionType = tx.value();
        // do before processing
        Object ret = proceed(tx);
        // do after processing
        return ret;
    }
    
}

Run-time retention

When using @annotation to match against an annotation, the annotation type must have run-time retention (otherwise AspectJ would be unable to expose the annotation value at run time). Matching simply using execution as you saw earlier will work with annotations that have only class-file retention.

Annotations and retention policies

An annotation type can have one of three different retention policies: SOURCE, CLASS, or RUNTIME. CLASS file retention is the default.

AspectJ 5 does not support join point matching for annotations with SOURCE retention because this cannot be supported when doing binary weaving (weaving using .class files as input). The AspectJ 5 pointcut designators that support exposing annotations as context (@this, @target, @args, and @annotation) can only be used with annotations that have RUNTIME retention.

The techniques shown so far also work as you would expect for field-based join points. Given a field annotated with a @ClassifiedData annotation, you could write one of the two pointcuts shown in Listing 9, depending on whether or not you needed to expose the actual annotation value:


Listing 9. Annotated fields

  /**
   * Any access or update to classified data
   */ 
   pointcut classifiedAction() : 
      get(@ClassifiedData * *) || set(@ClassifiedData * *);
      
  /**
   * Alternative declaration:
   * Any access or update to classified data,
   * exposes the annotation to provide access to 
   * classification level attribute
   */
   pointcut classifiedAction(ClassifiedData classification) :
     (get(* *) || set(* *)) && @annotation(classification);


Matching annotated types

Before leaving this discussion on annotations, let's take a more in-depth look at how AspectJ 5 enables you to match annotations on types. Anywhere that AspectJ lets you specify a type pattern, you can qualify that pattern with an annotation pattern. I've been using the simplest possible annotation pattern so far, @Foo, which matches when the subject has a Foo annotation. Combinations are also possible; "@Foo @Goo" matches when the subject has both a Foo annotation and a Goo annotation. "@(Foo || Goo)" matches if the subject has either the Foo annotation or the Goo annotation. See the discussion on annotation patterns in the AspectJ 5 Developers Guide (in Resources) for more details.

In EJB 3.0, session beans can be annotated with either @Stateful or @Stateless. The type pattern "@(Stateless || Stateful) *" matches any type that has either of these annotations. If you wanted to restrict the TransactionManager aspect to only work with session beans for some reason, then you could refine the transactionalMethodExecution pointcut from Listing 8 as follows.

    pointcut transactionalMethodExecution(Tx tx) :
        execution(* *(..)) && @annotation(tx)
        && within(@(Stateless || Stateful) *);
  

This can be read as "match the execution of any method with the Tx annotation within a type that has either the Stateless or Stateful annotation." An alternative way of writing this would be to express the type pattern directly within the execution pointcut expression: execution(* (@(Stateless || Stateful) *).*(..)), but in my opinion, the former is clearer. (Note that if you were using call rather than execution there would be a significant difference between the two variants: the first would match calls made to transactional methods from within a session bean, and the second would match calls to transactional methods defined in a session bean.)

Join point matching based on annotation values

It is possible to write an AspectJ 5 pointcut expression that matches based not just on the presence or absence of an annotation, but also on the value of an annotation (for example, the TxType).

Simply use an if test in the pointcut expression:

pointcut transactionRequired(Tx tx) : 
  execution(* *(..)) && 
  @annotation(tx) && 
  if(tx.value() == TxType.REQUIRED);

Further pointcut designators

AspectJ defines further pointcut designators for matching and exposing annotations:

@withincode
Matches any join point arising from the execution of code lexically within a member (method, constructor, advice) that has a given annotation.
@within
Matches any join point within a type that has a given annotation.
@this
Matches any join point where the object currently bound to this has a given annotation.
@target
Matches any join point where the target of the join point has a given annotation annotation.
@args
Matches any join point where the arguments have the given annotation(s).

See the AspectJ 5 Developers Guide for more information on join point matching and annotations.



Back to top


Generics in AspectJ 5

New support for generics in the Java language is arguably the biggest change introduced by Java 5. A generic type is declared with one or more type parameters, and these type parameters are bound to concrete type specifications when declaring variables of the type. The most cited examples of generic types are the Java collection classes. The List interface in Java 5 is a generic type with one type parameter -- the type of the elements in a list. By convention, a single letter is used for type parameters, and the list interface might be declared public interface List<E> {...}. If you want to create a variable to refer to a list of strings, you can declare it as of type List<String>. The generic type List<E> has its type parameter E bound to String, creating the parameterized type List<String>.

The LifecycleManager aspect extract shown in Listing 4 contains an example of a parameterized type (List<LifecycleObserver>) being used in an inter-type declaration. AspectJ 5 also lets you make inter-type declarations on a generic type. Listing 10 shows a generic type DataBucket<T> and an aspect that makes an inter-type declaration on it:


Listing 10. Inter-type declarations on generic types

  public class DataBucket<T> {
  
    private T data;
  
    public T getData() {
      return data;
    } 
    
    public void setData(T data) {
      this.data = data;
    }
  
  }
  
  aspect DataHistory {
  
    private E DataBucket<E>.previousDataValue;
    
    private E DataBucket<E>.getPreviousDateValue() {
      return previousDataValue;
    }
  
  }

Note that the name of the type parameter used in the inter-type declaration does not have to correspond to the name of the type parameter used in the declaration of the DataBucket class itself; instead, the signature of the types must match (number of type parameters and any restrictions placed on them via extends or super clauses).

For the remainder of this section, I'm going to focus on matching generic signatures and types in pointcut expressions. In the discussion that follows, it is helpful to divide the pointcut designators into two types: those that match based on static signatures (execution, call, get, set, and so on) and those that match based on run-time type information (this, target, args). This distinction is important because of something called erasure, which I'll get to shortly.

Matching generic signatures and types

For pointcut designators that match based on signature, AspectJ takes the simple view that the type parameter specification(s) of a type are part of the signature. For example, the following methods are all distinct signatures:

  • void process(List<Number> numbers)

  • void process(List<? extends Number> numbers)

  • void process(List<?> items)

The execution of these methods could be matched by the following pointcuts respectively:

  • execution(* process(List<Number>))

  • execution(* process(List<? extends Number>))

  • execution(* process(List<?>))

AspectJ allows the * and + wildcards when matching types. The expression "execution(* process(List<*>))" matches all three process methods because * matches any type. However, the expression "execution(* process(List<Number+>))" matches the first process method (Number is matched by the pattern Number+) but not the second or the third. The pattern List<Number+> can be expanded to match List<Float>, List<Double>, List<Integer> and so on, but these are all distinct signatures to List<? extends Number>. For one important difference, consider the fact that within the body of the process method, it is legal to insert into the list using non-wildcarded signatures but not when using the ? extends form.

The rule to remember is that generics wildcards are part of the signature, and that AspectJ pattern wildcards are used to match against the signature.

Matching based on run-time type information

When it comes to matching based on run-time type information, things get a little more interesting. The this, target, and args pointcut designators all match based on run-time type information. Consider another variation on the process method:

void process(Number n) {...}

The pointcut expression "execution(* process(..)) &&args(Number)" can be statically determined to always match the execution of this method -- the argument passed is guaranteed to be a number. If instead you wrote "execution(* process(..)) &&args(Double)" then this expression might match the execution of the method, depending on the actual run-time type of the argument passed. In situations like this, AspectJ applies a run-time test to see whether the argument is an instanceof Double.

If you now consider once more the following signature for process that takes a parameterized type

void process(List<? extends Number> ns) {...}

and apply the same kind of reasoning, you can see that:

execution(* process(..)) &&args(List<? extends Number>)
Will always match, as whatever kind of list you pass in must meet this specification.
execution(* process(..)) && args(List<String>)
Can never match, as a list of strings can never be passed to a method expecting a list of something that extends Number.
execution(* process(..)) && args(List<Number>)
Might match, depending on whether the list you actually pass in is a list of numbers, a list of doubles, or a list of floats.

What you would like to do in this latter case is apply a run-time test to the actual argument to see whether it is an instanceof List<Number>. Unfortunately, Java 5 implements generics using a technique called erasure -- and what gets erased is the run-time type parameter information for a parameterized type. At run time, the argument is simply seen as a plain old List (the so-called "raw" type of the argument).

Why erasure?

The Java Language Specification says that the decision not to make all generic types reifiable (available at run time) is one of the most crucial and controversial design decisions involving the language's type system. The ultimate rationale for this decision is platform compatibility (allowing interoperation between modules compiled for previous versions of the platform, and modules compiled for the Java 5 platform). The generic type system seeks to support migration compatibility, which allows the evolution of existing code to take advantage of generics without imposing dependencies between independently developed software modules.

AspectJ has to decide whether or not such a pointcut should match, even though it is lacking the information needed to make a definitive ruling. There is a precedence for situations like this in the Java language: In the case that an instance of a raw type (such as a List) is passed to a method expecting a parameterized type (such as a List<Number>), the invocation will pass through a Java 5 compiler but will generate an "unchecked" warning indicating that the conversion may not be type safe.

In a similar vein, when AspectJ determines that a pointcut may match a given join point but cannot apply the run-time test to be certain, then the pointcut is considered to match and the AspectJ compiler issues an "unchecked" warning to indicate that the actual matching cannot be checked. Just as the Java language supports the @SuppressWarnings annotation enabling unchecked warnings to be suppressed in a member, AspectJ supports the @SuppressAjWarnings annotation, which can be used to annotate advice to suppress unchecked warnings arising from pointcut matching.

Execution join points and generic types

Before I leave the topic of generics, there is one more important point to consider. Returning to the DataBucket class defined in Listing 10, note that regardless of how many different type parameters you use to instantiate instances of DataBucket (see below), there is only one DataBucket class:


DataBucket<Integer> myIntBucket = new DataBucket<Integer>();
DataBucket<String> myStringBucket = new DataBucket<String>();
DataBucket<Food> myFoodBucket = new DataBucket<Food>();
...


What this means is that inside the DataBucket class, there is no such thing as the execution of a getData method returning a String, an Integer, or a Food instance. Instead, there is only the execution of a getData method that returns an instance of the type parameter T. So you can write this and it will match the execution of any method getData returning a T in a type named DataBucket where Tis a type parameter:

execution<T>(T DataBucket<T>.getData())

For a complete treatment of generics in AspectJ 5, please refer to the AspectJ 5 Developers Guide.



Back to top


The @AspectJ annotations

Having covered the most important Java 5 language features of the new AspectJ 5 release, I'll now shift focus to look at some of the new features that are not explicitly tied to supporting Java 5. One of the most important of these is the new annotation-based style of aspect declaration, known as the @AspectJ annotations. In AspectJ 5, it is possible to write aspects using regular Java syntax and then annotate the declarations so that they can be interpreted by AspectJ's weaver. For example, whereas in the code style you would write this:

public aspect TransactionManager {
  ...

In the annotation-based style, you would write this:

@Aspect
public class TransactionManager {
  ...

The @AspectJ annotations were contributed to AspectJ following the merger with AspectWerkz in early 2005. They enable the processing of AspectJ source code using any standard Java 5 compiler, and indeed any other tools that work from Java source code. They can also make a significant difference to the day-to-day editing experience when working in an IDE that does not provide integrated support for working with AspectJ programs (though of course, such environments will still lack the views that display crosscutting structure).

It is important to note that the AspectJ 5 distribution has the following (albeit with two development styles):

  • One language
  • One semantics
  • One weaver

Whichever style you choose to express your aspects in, they mean exactly the same thing and behave in exactly the same way. This important property lets you easily mix and match styles (so that aspects developed using the @AspectJ style can be used in conjunction with aspects developed using the code style, and vice-versa). There are some limitations to the @AspectJ style, however. For example, when using a regular Java compiler, annotation style versions of AspectJ constructs such as declare soft cannot be supported because such constructs require support at compile time and not just at weave time.

Now let's take a look at an example using @AspectJ annotations.

Writing aspects using @AspectJ annotations

I'll start with the simplified LifecycleManager aspect shown in Listing 11 and rewrite it using the @AspectJ style:


Listing 11. Simplified Lifecycle manager aspect, code style

/**
 * This aspect provides default lifecycle management for all
 * types with the @ManagedComponent annotation.
 */
public aspect LifecycleManager {

    /**
     * The defined states that a managed component can be in.
     */
    public enum State {
        INITIAL,
        INITIALIZING,INITIALIZED,
        STARTING,STARTED,
        STOPPING,
        TERMINATING,TERMINATED,
        BROKEN;
    }
    
    /**
     * The lifecycle interface supported by managed components.
     */
    public interface Lifecycle {
        void initialize();
        void start();
        void stop();
        void terminate();
        boolean isBroken();
        State getState();
    }
    
    /**
     * Any type with an @ManagedComponent annotation implements
     * the Lifecycle interface (and acquires the default implementation
     * defined in this aspect if none is provided by the type).
     */
    declare parents : @ManagedComponent * implements Lifecycle;

    // default implementations for the state-based lifecycle events
    private State Lifecycle.state = State.INITIAL;
    public void Lifecycle.initialize() {}
    public void Lifecycle.start() {}
    public void Lifecycle.stop() {}
    public void Lifecycle.terminate() {}
    public boolean Lifecycle.isBroken() { return state == State.BROKEN; }
    public State Lifecycle.getState() { return state; }
    
    // these pointcuts capture the lifecycle events of managed components
    pointcut initializing(Lifecycle l) : 
      execution(* Lifecycle.initialize(..)) && this(l);
    pointcut starting(Lifecycle l) : 
      execution(* Lifecycle.starting(..)) && this(l);
    pointcut stopping(Lifecycle l) : 
      execution(* Lifecycle.stopping(..)) && this(l);
    pointcut terminating(Lifecycle l): 
      execution(* Lifecycle.terminating(..)) && this(l);
    
    /**
     * Ensure we are in the initial state before initializing.
     */
    before(Lifecycle managedComponent) : initializing(managedComponent) {
        if (managedComponent.state != State.INITIAL)
            throw new IllegalStateException("Can 
              only initialize from INITIAL state");
        managedComponent.state = State.INITIALIZING;
    }
    
    /**
     * If we successfully initialized the component, update the state and
     * notify all observers.
     */
    after(Lifecycle managedComponent) returning : initializing(managedComponent) {
        managedComponent.state = State.INITIALIZED;
    }
    
    ...
}

The aspect declaration and the inner type declarations move easily into the the new style, as shown in Listing 12:


Listing 12. Simplified Lifecycle manager aspect, annotation style

/**
 * This aspect provides default lifecycle management for all
 * types with the @ManagedComponent annotation.
 */
@Aspect
public class LifecycleManager {

    /**
     * The defined states that a managed component can be in.
     */
    public enum State {
        INITIAL,
        INITIALIZING,INITIALIZED,
        STARTING,STARTED,
        STOPPING,
        TERMINATING,TERMINATED,
        BROKEN;
    }
    
    /**
     * The lifecycle interface supported by managed components.
     */
    public interface Lifecycle {
        void initialize();
        void start();
        void stop();
        void terminate();
        boolean isBroken();
        State getState();
    }
    
    ...


Using annotations with pointcuts and advice

Next, let's see what happens when I rewrite the pointcuts and advice using the @AspectJ style. Pointcuts are written using the @Pointcut annotation on a void method with the same signature as the pointcut. Advice is written using the @Before, @Around, @AfterReturning, @AfterThrowing, and @After annotations on methods, as you can see in Listing 13:


Listing 13. Pointcuts and advice in the annotation style

/**
 * This aspect provides default lifecycle management for all
 * types with the @ManagedComponent annotation.
 */
@Aspect
public class LifecycleManager {

  ...
  
    // these pointcuts capture the lifecycle events of managed components
    @Pointcut(
        "execution(* 
          org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.initialize(..)) 
          && this(l)"
    )
    void initializing(Lifecycle l) {}
    
    @Pointcut(
        "execution(* 
          org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.starting(..))
          && this(l)"
    )
    void starting(Lifecycle l){}
    
    @Pointcut(
        "execution(* 
          org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.stopping(..)) 
         && this(l)"
    )
    void stopping(Lifecycle l) {}
    
    @Pointcut(
        "execution(* 
          org.aspectprogrammer.devWorks.LifecycleManager.Lifecycle.terminating(..)) 
         && this(l)"
    )
    void terminating(Lifecycle l) {}
 
    /**
     * Ensure we are in the initial state before initializing.
     */
    @Before("initializing(managedComponent)")
    public void moveToInitializingState(Lifecycle managedComponent) {
        if (managedComponent.state != State.INITIAL)
            throw new IllegalStateException("Can 
              only initialize from INITIAL state");
        managedComponent.state = State.INITIALIZING;
    }
    
    /**
     * If we successfully initialized the component, update the state and
     * notify all observers.
     */
    @AfterReturning("initializing(managedComponent)")
    public void moveToInitializedStated(Lifecycle managedComponent) {
        managedComponent.state = State.INITIALIZED;
    }


Note that in the pointcut expressions, any types referred to must be fully-qualified (import statements only exist as a source code phenomenon and are not available to the weaver when processing the annotations). Advice methods must be declared public and return void (except for @Around advice, which must return a value).

Using annotations with inter-type declarations

All that remains now is to move the inter-type declarations into the @AspectJ-style aspect as well. In the annotation style, these declarations are made as shown in Listing 14:


Listing 14. Inter-type declarations in annotation style

/**
 * This aspect provides default lifecycle management for all
 * types with the @ManagedComponent annotation.
 */
@Aspect
public class LifecycleManager {

  ...

    // default implementations for the state-based lifecycle events
    @DeclareParents("@ManagedComponent *")
    class DefaultLifecycleImpl implements Lifecycle {
      private State state = State.INITIAL;
      public void initialize() {}
      public void start() {}
      public void stop() {}
      public void terminate() {}
      public boolean isBroken() { return state == State.BROKEN; }
      public State getState() { return state; }
    }

  ...  
}


There are a number of other interesting issues with respect to the annotation style of development, such as how to refer to thisJoinPoint within the body of an annotation-style advice method, and how proceed is supported in around advice. For more information on these topics, please refer to the AspectJ 5 Developers Guide.



Back to top


Load-time weaving enhancements

Load-time weaving refers to the process of weaving classes as they are loaded into the VM (as opposed to weaving ahead of time -- during the compilation process for example). AspectJ has had the necessary infrastructure to support load-time weaving since the 1.1 release, but it was necessary to write custom classloaders to actually integrate AspectJ's weaver into an application. Support for load-time weaving was improved in the AspectJ 1.2 release with the addition of an aj script that could launch and run any Java application from the command-line, weaving aspects from an ASPECTPATH as classes were loaded. This script supports JDK 1.4 and upwards.

Command-line scripts cannot, however, be used easily in all environments, and in particular do not integrate well with Java EE applications. In AspectJ 5, AspectJ supports configuration of load-time weaving via META-INF/aop.xml files placed in the classpath. This is another feature brought to AspectJ as a result of the merger with AspectWerkz in early 2005.

Let's take a look at the aop.xml file and its associated elements.

XML specification for load-time weaving

An aop.xml file consists of two main sections: an aspects element that defines the set of aspects to be used for load-time weaving and a weaver element that specifies options controlling the behavior of the weaver (primarily, which types should be woven). Listing 15 shows a sample file:


Listing 15. Sample aop.xml file

<aspectj>
          
   <aspects>
        <!-- declare two existing aspects to the weaver -->
        <aspect name="com.MyAspect"/>
        <aspect name="com.MyAspect.Inner"/>

        <!-- define a concrete aspect inline -->
        <concrete-aspect name="com.xyz.tracing.MyTracing" 
          extends="tracing.AbstractTracing">
          <pointcut name="tracingScope" 
            expression="within(com.xyz..*)"/>
        </concrete-aspect>
    
   </aspects>

   <weaver options="-XlazyTjp">
        <include within="com.xyz..*"/>
   </weaver>
          
</aspectj>

In the aspects element, known aspects are defined to the weaver either by name or by defining them inline in the aop.xml file. The latter technique can only be used when extending an existing abstract aspect that has one or more abstract pointcuts: The pointcut expressions are supplied in the XML. For "infrastructure" aspects, this can be a great way of externalizing configuration (the pointcut expression). Having defined the set of aspects in the weaver's world, one or more optional include and exclude elements can be used if needed (not shown) to control which of them are actually used in the weaving process. By default, the weaver uses all defined aspects.

The weaver element contains options to be passed to the weaver and an optional definition of the set of types that should be woven (via include statements). If no include statements are specified, then all types are made available to the weaver for weaving.

If multiple META-INF/aop.xml files are visible on the classpath, then the content is aggregated to form the complete specification passed to the weaver.

AspectJ 5 supports a number of agents that integrate the load-time capability into an existing environment. Listing 16 shows sample JVM startup options for using the JVMTI (Java 5) agent, which is supported by any Java 5-compliant JVM:


Listing 16. The JVMTI agent

-javaagent=aspectjweaver.jar

AspectJ 5 also ships with a JRockit agent that supports the same functionality for pre-Java 5 JRockit VMs (JRockit also supports jvmti on Java 5). The equivalent startup option is -Xmanagement:class=org.aspectj.weaver.tools. JRockitWeavingAgent

You'll find more details on load-time weaving with AspectJ 5 in the AspectJ 5 Developers Guide.



Back to top


Closing thoughts

All in all, AspectJ 5 represents a major step forward for AspectJ. This article focused primarily on how the new release supports not only full compilation of Java 5 language constructs, but also join point matching based on annotations and generic types.

Two of the most exciting new features in the AspectJ 5 release -- the new annotation-based development style and enhancements to AspectJ's load-time weaving support -- are the result of the merger of AspectJ with AspectWerkz. Because of the importance of the merger and the relevance of the features, I've discussed them both in-depth here.

Naturally, a single article cannot hope to cover all of the enhancements to a release as comprehensive as AspectJ 5. For example, I've focused on the major updates to join point matching in AspectJ 5, leaving more minor ones (such as new approaches to dealing with autoboxing and covariant return types) for you to discover on your own. Other features unexplored here but worth researching include the new pertypewithin aspect instantiation model, reflection APIs for interrogating aspect types at run time, revisions to the way declare soft handles run-time exceptions, compilation, weaving performance improvements, and more.

Having read this far in the article, I'm sure you could have a pretty good guess at where to continue your learning about these new features (and more). You got it -- the AspectJ 5 Developers Guide.



Resources

  • The AspectJ 5 Developer's Guide provides a complete description of all of the new features of AspectJ 5.

  • To learn more about using the AspectJ Developer Kit see " Develop aspect-oriented Java applications with Eclipse and AJDT" (developerWorks, September 2004).

  • AOP@Work is a year-long series dedicated to helping you incorporate AOP into your day-to-day Java programming. Don't miss a single article in the series. See the complete series listing. Of particular relevance to this article is Ramnivas Laddad's two-part look at AOP and metadata. Part 1 provides a conceptual overview of annotations in Java 5 and how AOP can benefit from the addition of metadata annotations; Part 2 offers a series of guidelines for effectively combining metadata and AOP and discusses the impact of metatdata annotations on the adoption of aspect-oriented programming.

  • Brett McLaughlin's two-part "Annotations in Tiger" (developerWorks, September 2004) is an excellent introduction to using Java 5's built-in annotations feature.

  • See Brian Goetz's "Generics gotchas" (developerWorks, January 2005) for a guide to identifying and avoiding some of the pitfalls in learning to use Java 5's generics facility.

  • The AspectJ site on Eclipse.org contains links to many AspectJ related articles, documentation, and downloads.

  • AJDT provides a complete suite of tools for developing AspectJ applications in Eclipse.

  • The AspectJ Development Environment Guide provides information on using the command-line compiler and associated tools.

  • The author's blog contains a series of articles exploring the new features of AspectJ 5 and on aspect-oriented programming with AspectJ in general.

  • The EJB 3.0 specification contains more details of the annotations to be used for EJBs.

  • You'll find articles about every aspect of Java programming in the developerWorks Java technology zone.

  • Also see the Java technology zone tutorials page for a complete listing of free Java-focused tutorials from developerWorks.


About the author

Adrian Colyer

Adrian Colyer is a senior technical staff member at IBM and the leader of the AspectJ project on Eclipse.org. He is co-author of the book Eclipse AspectJ: Aspect-Oriented Programming in Eclipse with AspectJ and AJDT, and in 2004 he was voted one of the top 100 young innovators in the world by MIT Technology Review magazine. His time is divided between developing tools and technology to support aspect-oriented programming (AOP), helping groups inside and outside of IBM to adopt and apply the ideas behind AOP, and writing and speaking on the theme of aspect-oriented software development. You can catch his blog at http://www. aspectprogrammer.org/blogs/adrian.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top