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.
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
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.
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;
}
}
|
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.
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);
|
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.)
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
thishas 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.
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).
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.
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.
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.
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.
- 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.

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.



