I want my AOP!, Part 2

Learn AspectJ to better understand aspect-oriented programming

In Part 1 of this three-part series on aspect-oriented programming (AOP), I introduced AOP concepts and briefly gave AOP implementation examples. Continuing that trend in this article, I present a concrete AOP implementation for Java: AspectJ, a free implementation and language specification from Xerox PARC. Moreover, I aim to impart familiarity with the AspectJ concepts you will need for Part 3.

Read the whole "I Want My AOP" series:

In this article, I lean towards conciseness rather than completeness. For a comprehensive tutorial, I highly recommend the AspectJ Group's official AspectJ Programming Guide.

Note: You can download this article's complete source code in Resources. The sample code works with AspectJ 1.0.3 -- the latest available version at the time of publication.

AspectJ overview

AspectJ is a language specification as well as an AOP language implementation. The language specification defines various constructs and their semantics to support aspect-oriented concepts. The language implementation offers tools for compiling, debugging, and documenting code.

AspectJ's language constructs extend the Java programming language, so every valid Java program is also a valid AspectJ program. The AspectJ compiler produces class files that comply with Java byte code specification, enabling any compliant JVM to interpret the produced class files. By choosing Java as the base language, AspectJ passes on all the benefits of Java and makes it easy for Java programmers to use it.

AspectJ, as one of its strong points, features helpful tools. It provides an aspect weaver in the form of a compiler, an aspect-aware debugger and documentation generator, and a standalone aspect browser to visualize how an advice crosscuts a system's parts. Moreover, AspectJ offers good integration with popular IDEs, including Sun Microsystems' Forte, Borland's JBuilder, and Emacs, making AspectJ a useful AOP implementation for Java developers.

AspectJ language overview

To support AOP, AspectJ adds to the Java language concepts:

  • Joinpoints: Points in a program's execution. For example, joinpoints could define calls to specific methods in a class
  • Pointcuts: Program constructs to designate joinpoints and collect specific context at those points
  • Advices: Code that runs upon meeting certain conditions. For example, an advice could log a message before executing a joinpoint

Pointcut and advice together specify weaving rules. Aspect, a class-like concept, puts pointcuts and advices together to form a crosscutting unit. The pointcuts specify execution points and the weaving rule's context, whereas the advices specify actions, operations, or decisions performed at those points. You can also look at joinpoints as a set of events in response to which you execute certain advices.

I explain joinpoints, pointcuts, and advices in more detail below.

AspectJ HelloWorld

To keep with the tradition of introducing a new programming language with a HelloWorld program, let's write one in AspectJ.

First, here's a simple class containing methods to print a message:

// HelloWorld.java
public class HelloWorld {
    public static void say(String message) {
        System.out.println(message);
    }
    
    public static void sayToPerson(String message, String name) {
        System.out.println(name + ", " + message);
    }
}

Let's now write an implementation for adding greeting and gratitude manners. Before the program prints a message, it should print "Good day!"; and should follow the message with "Thank you!":

// MannersAspect.java
public aspect MannersAspect {
    pointcut callSayMessage() : call(public static void HelloWorld.say*(..));
    before() : callSayMessage() {
        System.out.println("Good day!");
    }
    after() : callSayMessage() {
        System.out.println("Thank you!");
    }
}

The MannersAspect.java file, in a process similar to class declaration in Java, declares a MannersAspect aspect. The aspect defines a callSayMessage() pointcut that captures calls to all public static methods with names that start with say. In our example, it would capture say() and sayToPerson() in a HelloWorld class taking any arguments. Then you define two advices for before and after reaching the callSayMessage() pointcut: printing "Good day!" and "Thank you!" When you compile the program with the AspectJ compiler, then run it, you'll see "Good day!" printed before each message, and "Thank you!" after each message. You now have excellent manners!

AspectJ language concepts and constructs

Now that you have a feel for the AspectJ language, let's examine its core constructs in greater detail. This section explains AspectJ's available joinpoints and various pointcut syntax. I also take a closer look at advice constructs.

Joinpoints

Joinpoints, a central concept in AspectJ, are well-defined points in a program's execution. Candidate joinpoints include calls to a method, a conditional check, a loop's beginning, or an assignment. Joinpoints also have a context associated with them. For example, a method-call joinpoint could have the target object and its argument as part of the context.

Although any identifiable point in a program's execution is a joinpoint, AspectJ limits the available joinpoints to those usable in a systematic manner. AspectJ makes the following pointcuts available:

  • Method call and execution
  • Constructor call and execution
  • Read/write access to a field
  • Exception handler execution
  • Object and class initialization execution

AspectJ does not expose finer execution structures such as if checks or for loops.

Pointcuts

Pointcuts, program constructs to designate joinpoints, let you specify a joinpoint collection. Pointcuts also let you expose context at the joinpoint to an advice implementation.

In the HelloWorld example above, I used the following pointcut:

pointcut callSayMessage() : call(public static void HelloWorld.say*(..));

In the code above, pointcut declares that what follows is a declaration of a named pointcut. Next, callSayMessage(), the pointcut's name, resembles a method declaration. The trailing empty () suggests that the pointcut collects no context.

Moving on, call(public static void HelloWorld.say*(..)) captures needed joinpoints. call indicates the pointcut captures a call to, as opposed to execution of, designated methods. The public static void HelloWorld.say*(..) is the signature for methods to be captured. public static indicates that the methods must have public access and be declared a static method. void says that methods captured must have a void return type. HelloWorld.say* specifies the to-be-captured method's class and name. Here, we are specifying HelloWorld as a class.

say* uses the * wildcard to indicate the capture of methods with names starting with "say." Finally, (..) specifies arguments to the captured methods. In this case, you specify the .. wildcard to capture methods regardless of type and number of arguments they take.

Now that you know how to specify pointcuts to capture joinpoints, let's look briefly at other pointcuts types.

Call to methods and constructors pointcuts

First, call to methods and constructors pointcuts capture execution points after they evaluate a method's arguments, but before they call the method itself. They take the form of call(MethodOrConstructorSignature). Table 1 shows examples of such pointcuts.

Table 1. Call to methods and constructors pointcuts
Pointcut
Description
call(public void MyClass.myMethod(String))Call to myMethod() in MyClass taking a String argument, returning void, and with public access
call(void MyClass.myMethod(..))Call to myMethod() in MyClass taking any arguments, with void return type, and any access modifiers
call(* MyClass.myMethod(..))Call to myMethod() in MyClass taking any arguments returning any type
call(* MyClass.myMethod*(..))Call to any method with name starting in "myMethod" in MyClass
call(* MyClass.myMethod*(String,..))Call to any method with name starting in "myMethod" in MyClass and the first argument is of String type
call(* *.myMethod(..))Call to myMethod() in any class in default package
call(MyClass.new())Call to any MyClass' constructor taking no arguments
call(MyClass.new(..))Call to any MyClass' constructor with any arguments
call(MyClass+.new(..))Call to any MyClass or its subclass's constructor. (Subclass indicated by use of '+' wildcard)
call(public * com.mycompany..*.*(..))All public methods in all classes in any package with com.mycompany the root package

Execution of methods and constructors pointcuts

Next, execution of methods and constructors pointcuts capture the method's execution. In contrast to call pointcuts, execution pointcuts represent the method or constructor body itself. They take the form of execution(MethodOrConstructorSignature).

Table 2. Execution of methods and constructors pointcuts
Pointcut
Description
execution(public void MyClass.myMethod(String))Execution of myMethod() in MyClass taking a String argument, returning void, and with public access
execution(void MyClass.myMethod(..))Execution of myMethod() in MyClass taking any arguments, with void return type, and any access modifiers
execution(* MyClass.myMethod(..))Execution of myMethod() in MyClass taking any arguments returning any type
execution(* MyClass.myMethod*(..))Execution of any method with name starting in "myMethod" in MyClass
execution(* MyClass.myMethod*(String,..))Execution of any method with name starting in "myMethod" in MyClass and the first argument is of String type
execution(* *.myMethod(..))Execution of myMethod() in any class in default package
execution(MyClass.new())Execution of any MyClass' constructor taking no arguments
execution(MyClass.new(..))Execution of any MyClass' constructor with any arguments
execution(MyClass+.new(..))Execution of any MyClass or its subclass's constructor. (Subclass indicated by use of '+' wildcard)
execution(public * com.mycompany..*.*(..))All public methods in all classes in any package with com.mycompany the root package

Field-access pointcuts

Third, field-access pointcuts capture read and write access to a class's field. For example, you can capture all access to the out field inside the System class (as in System.out). You can capture either read or write access. For example, you can capture writing into field x of MyClass, as in MyClass.x = 5. The read-access pointcut takes the form of get(FieldSignature), whereas the write-access pointcut takes the form of set(FieldSignature). FieldSignature can use wildcards in the same manner as MethodOrConstructor in call and execution pointcuts.

Table 3. Field-access pointcuts
Pointcut
Description
get(PrintStream System.out)Execution of read-access to field out of type PrintStream in System class
set(int MyClass.x)Execution of write-access to field x of type int in MyClass

Exception-handler pointcuts

Fourth, exception-handler pointcuts capture the execution of exception handlers of specified types. They take the form of handler(ExceptionTypePattern).

Table 4. Exception-handler pointcuts
Pointcut
Description
handler(RemoteException)Execution of catch-block handling RemoteException type
handler(IOException+)Execution of catch-block handling IOException or its subclasses
handler(CreditCard*)Execution of catch-block handling exception types with names that start with CreditCard

Class-initialization pointcuts

Fifth, class-initialization pointcuts capture the execution of static-class initialization, code specified in static blocks inside class definitions, of specified types. They take the form of staticinitialization(TypePattern).

Table 5. Class-initialization pointcuts
Pointcut
Description
staticinitialization(MyClass)Execution of static block of MyClass
Staticinitialization(MyClass+)Execution of static block of MyClass or its subclasses

Lexical-structure-based pointcuts

Next, lexical-structure-based pointcuts capture all joinpoints inside a class or method's lexical structure. The pointcut capturing the code lexically inside a class, including an inner class, takes the form of within(TypePattern). The pointcut capturing the code lexically inside a method or constructor, including any local classes, takes the form of withincode(MethodOrConstructorSignature).

Table 6. Lexical-structure-based pointcuts
Pointcut
Description
within(MyClass)Any pointcut inside MyClass's lexical scope
within(MyClass*)Any pointcut inside lexical scope of classes with a name that starts with "MyClass"
withincode(* MyClass.myMethod(..))Any pointcut inside lexical scope of any myMethod() of MyClass

Control-flow-based pointcuts

Seventh, control-flow-based pointcuts capture pointcuts based on other pointcuts' control flow (the flow of program instructions). For example, if in an execution, method a() calls method b(), then b() is said to be in a()'s control flow. With control-flow-based pointcuts, you can, for example, capture all methods, field access, and exception handlers caused by invoking a method. The pointcut that captures the pointcuts in control flow of some other pointcut, including the specified pointcut itself, takes the form of cflow(pointcut), whereas the one that excludes the specified pointcut itself takes the form of cflowbelow(pointcut).

Table 7. Control-flow-based pointcuts
Pointcut
Description
cflow(call(* MyClass.myMethod(..))All the joinpoints in control flow of call to any myMethod() in MyClass including call to the specified method itself
cflowbelow(call(* MyClass.myMethod(..))All the joinpoints in control flow of call to any myMethod() in MyClass excluding call to the specified method itself

Self-, target-, and arguments-type pointcuts

Eighth, self-, target-, and arguments-type pointcuts capture joinpoints based on the self-object, target-object, and argument types. These are the only constructs that can capture context at the joinpoints. The pointcut that captures the pointcuts based on the self object takes the form of this(TypePattern or ObjectIdentifier), whereas the one based on target takes the form of target(TypePattern or ObjectIdentifier). Argument-based pointcuts take the form of args(TypePattern or ObjectIdentifier, ..).

Table 8. Self-, target-, and arguments-type pointcuts
Pointcut
Description
this(JComponent+)All the joinpoints where this is instanceof JComponent
target(MyClass)All the joinpoints where the object on which the method is called is of type MyClass
args(String,..,int)All the joinpoints where the first argument is of String type and the last argument is of int type
args(RemoteException)All the joinpoints where the type of argument or exception handler type is RemoteException

Conditional-test pointcuts

The ninth and last pointcut we'll look at, conditional test, captures joinpoints based on some conditional check at the joinpoint. It takes the form of if(BooleanExpression).

Table 9. Conditional-test pointcuts
Pointcut
Description
if(EventQueue.isDispatchThread())All the joinpoints where EventQueue.isDispatchThread() evaluates to true

Named and anonymous pointcuts

Named pointcuts explicitly specify their names. You can reuse such pointcuts for defining other pointcuts, defining part of an advice, overriding a pointcut, and so on.

Anonymous pointcuts, like anonymous classes, become defined at the point of their usage, specifically in the advice specification or as a part of some other pointcut's definition. As with anonymous classes, anonymous pointcuts cannot be reused. In the examples above, I used an anonymous pointcut with cflow().

Use ||, &&, and ! operators with pointcuts

You can use || and && operators to combine named and anonymous pointcuts. For example, to designate a pointcut for a call to method m1() or m2() in MyClass, you could use call(* MyClass.m1()) || call(* MyClass.m2()). To designate a pointcut for a call to method m1() in MyClass that is in control flow of a call to m2 in MyClass, you could use call(* MyClass.m1()) && cflow(call(* MyClass.m2()).

You can use broad pointcuts such as this(), within(), and cflow() with other pointcuts using && to capture a smaller joinpoint subset.

Use ! to specify joinpoints not captured by the specified pointcut. For example, to designate a call to all methods, except m1() in MyClass, you could use a !call(* MyClass.m1()) pointcut.

Exposing context

Advice implementations often require access to data at the joinpoint. For example, to log certain operations, an advice might need information, called context, about a method and arguments to it. Pointcuts, therefore, allow exposure of the context at the execution point to pass to an advice implementation. AspectJ offers target(), this(), and args() pointcuts to collect the context. For example, the following pointcut collects all arguments to method executions associated with it:

    pointcut publicOperationCardAmountArgs(CreditCard card, Money amount):
        execution(public * CreditCardProcessor.*(..)) && args(card, amount);

Reflection

AspectJ supports a limited form of reflection. With AspectJ's reflection, you can examine information at any pointcut's execution point. Each advice body has access to a special object, thisJoinPoint, which contains the information about the joinpoint. Such reflective access proves particularly important in logging and debugging aspects.

Advices

Advices specify the executable code when reaching certain pointcuts. AspectJ provides three ways to associate an advice with a joinpoint: before, after, and around a joinpoint's execution. A before advice runs just before the joinpoint, whereas an after advice runs just after. With an after advice, you can specify after normal return, after throwing an exception, or after returning either way from a joinpoint. An around advice surrounds a joinpoint and has control if the joinpoint's execution should proceed. It can also decide to proceed with a different argument set.

Let's look at a few simple advice examples.

The following advice example prints thisJoinPoint and the current system time before and after calling any public method in MyClass:

before() : call(public * MyClass.*(..)) {
        System.out.println("Before: " + thisJoinPoint + " " + 
System.currentTimeMillis());
}
after() : call(public * MyClass.*(..)) {
        System.out.println("After: " + thisJoinPoint + " " + 
System.currentTimeMillis());
}

The next advice example captures all calls to Connection.close(), and, if you've enabled connection pooling, puts the connection into a resource pool instead; otherwise it uses proceed() to proceed with the captured operation. This advice also uses context collected by target():

void around(Connection conn) : call(Connection.close()) && target(conn) {
    if (enablePooling) {
        connectionPool.put(conn);
    } else {
        proceed();
    }
}

Aspects

Aspects act as AspectJ's unit of modularization, just as classes do in Java. An aspect puts together pointcuts and advices. Aspects resemble classes: an aspect can contain methods and fields, extend other classes or aspects, and implement interfaces. However, aspects differ from classes in that you cannot create an object for an aspect using new.

AspectJ allows classes to declare pointcuts. You must declare static pointcuts declared inside a class. However, AspectJ does not allow classes to contain advices; only aspects can contain advices.

An aspect can mark itself and any pointcut as abstract. Abstract pointcuts act similarly to a class's abstract methods: they let you defer the details to the derived aspects. A concrete aspect extending an abstract aspect can then provide concrete definitions of abstract pointcuts.

AspectJ examples

With your new AspectJ knowledge, it's time to look at a few examples implemented in AspectJ. (For another AOP implementation, review Part 1's credit card example. In Part 3, I will present more complex AOP examples.)

Example 1: Detect a Swing UI (user interface) update from a nonevent dispatch thread

The following aspect detects any call to a Swing model that updates the UI. (For more information about when to use such detection, see Resources.) The test code that exercises this aspect is in Test.java:

// SwingThreadSafetyCheckAspect.java
import java.awt.*;
/**
 * Aspect that warns if Swing components are updated from a
 * nonevent dispatch thread.
 *
 * Because AspectJ currently does not allow byte-code weaving, the 
 * implementation is short. It is required to manually
 * encode each method that updates model. Besides, it does not take care of
 * custom models.
 *
 * The implementation is for illustration only. It takes care of only
 * part of DefaultTableModel and DefaultListModel. 
 */
public aspect SwingThreadSafetyCheckAspect {
    private static final String WARNING_MESSAGE
        = "BEWARE: Swing update called from non-AWT thread\n"
        + "Change code to use EvenetQueue.invokeLater() or invokeAndWait()";
    
    // Define a pointcut to capture calls to TableModel and its subclass's
    // method that would update the UI. Note, this list is incomplete.
    pointcut swingTableUpdateCalls() : 
           call(* javax..*TableModel+.set*(..))
        || call(* javax..*TableModel+.add*(..))
        || call(* javax..*TableModel+.remove*(..));
    
    // Define a pointcut to capture calls to ListModel and its subclass's
    // method that would update the UI. Note, this list is incomplete.
    pointcut swingListUpdateCalls() :
           call(* javax..*ListModel+.set*(..))
        || call(* javax..*ListModel+.insert*(..));
    // Pointcuts for tree, text, and so on models
    // Implementation left out...
    // Pointcut for updates to any Swing model (use || to add pointcuts
    // to be defined above for tree, text, and so on).
    pointcut swingUpdateCalls() 
        : swingTableUpdateCalls() || swingListUpdateCalls();
    /**
     * Advice to print a warning and stack trace that led to a call
     * to Swing update methods from nonevent dispatch thread.
     */
    before() : swingUpdateCalls() && !if(EventQueue.isDispatchThread()) {
        
        System.err.println(WARNING_MESSAGE);
        System.err.println("Joinpoint captured: " + thisJoinPoint);
        Thread.dumpStack();
        System.err.println();
    }
}

Example 2: Authentication

The next example, found in AbstratcAuthenticationAspect.java, illustrates abstract pointcuts and abstract aspects, as well as crosscutting multiple methods. Other classes are available in DatabaseServer.java, DatabaseClient.java, Authenticator.java, and Test.java:

// AbstratcAuthenticationAspect.java
public abstract aspect AbstractAuthenticationAspect {
    public abstract pointcut operationsNeeddingAuthentication();
    before() : operationsNeeddingAuthentication() {
        // Perform authentication. If it could not be authenticated,
        // let the exception thrown by authenticate propagate.
        Authenticator.authenticate();
    }
}

DatabaseAuthenticationAspect extends the abstract AbstractAuthenticationAspect aspect. It specifies the definition for the operationsNeeddingAuthentication() pointcut capturing operations needing authentication -- in our case calls to the DatabaseServer.connect() method:

// DatabaseAuthenticationAspect.java
public aspect DatabaseAuthenticationAspect 
    extends AbstractAuthenticationAspect {
    public pointcut operationsNeeddingAuthentication():
        call(* DatabaseServer.connect());
}

Example 3: Add Japanese manners to HelloWorld

Now that you've seen the around() advice and collecting context, let's use it to add manners, Japanese style, by adding a "-san" suffix to each person's name to show respect. We simply add another aspect: JapaneseMannersAspect. This aspect advises the call to HelloWorld.sayToPerson(String, String) and proceeds with a modified person argument by adding "-san":

// JapaneseMannersAspect.java
public aspect JapaneseMannersAspect {
    pointcut callSayMessageToPerson(String person) 
        : call(* HelloWorld.sayToPerson(String, String))
               && args(*, person);
    void around(String person) 
        : callSayMessageToPerson(person) {
        proceed(person + "-san");
    }
}

Type-modification constructs

Sometimes it is necessary to affect the static structure in a crosscutting manner, in contrast to affecting dynamic behavior using an advice. AspectJ allows several static-crosscutting types: introduction of new methods and fields, introduction of supertypes, and alteration of a method's exception specification.

Method and field introduction

AspectJ lets you introduce methods or fields to classes and interfaces. Interestingly, AspectJ lets you introduce method implementations and fields into interfaces. You can, for example, provide a default implementation of a few methods in an interface.

The following aspect introduces a foo() method and an instanceCount field of int type in MyClass:

aspect IntroduceMethodIllustration {
        private void MyClass.foo() { 
                System.out.println("This is foo");
        }
        private static int MyClass.instanceCount = 0;
}

Inheritance hierarchy restructuring

With AspectJ, you can modify an existing class's inheritance hierarchy. Using these constructs, you can declare super classes and interfaces of an existing class or interface.

The following aspect declares that MyClass is a Serializable:

aspect MakeMyClassSerializable {
        declare parents : MyClass implements Serializable;
}

Soften an exception

AspectJ can soften a checked exception into an unchecked exception for a particular method set. Such methods then behave as if they've thrown an unchecked exception of type org.aspectj.lang.SoftException, which wraps the original exception.

The next aspect example softens a RemoteException thrown by a call to any public method in DateFetcher's subclasses or DataFetcherImpl's constructor if such a call is made from within Test class's code:

aspect SoftenDateFetcherRemoteException {
        declare soft : 
            RemoteException : (call(public * DateFetcher.*(..))
                               || call(public DateFetcherImpl.new(..)))
                              && within(Test);
}

Note: Since such a declaration affects compile-time behavior, the pointcut must be statically determinable.

The rest in short

In this section, I briefly cover additional AspectJ features:

  • You can declare one aspect as dominant over others to control precedence when multiple aspects affect a certain pointcut.
  • By default, Aspect J associates aspects with other classes and aspects, and therefore all aspectee instances share the aspect's state. Beyond the default association type, AspectJ offers additional types: perthis, pertarget, percflow, and percflowbelow. Each associates a separate aspect instance with a self object, target object, control flow, and control flow excluding the pointcut, respectively.
  • You can mark an aspect privileged, which gives it access to the private members of aspected classes.
  • AspectJ also lets you introduce compile-time crosscutting behavior by allowing compile-time warnings and errors (similar to, but more powerful than, #error and #warning preprocessor directives supported by some C/C++ compilers).

AspectJ in all its glory

AspectJ provides a powerful and useful AOP implementation for Java. AspectJ offers modularizing crosscutting concerns by adding new joinpoint, pointcut, and advice concepts. Since AspectJ extends Java, Java programmers will find it an easy language to learn. The byte code produced by the AspectJ compiler is standard Java byte code, therefore it keeps Java's advantages.

AspectJ could evolve into a far more powerful language and implementation. As more people use it, the experience gained should guide changes to the language specification and implementation.

In Part 3, I will demonstrate AspectJ's power via several examples. Those examples, taken from various domains, will also show how AspectJ/AOP offers an elegant solution to otherwise complex problems.

Ramnivas Laddad is a Sun Certified Architect of Java Technology. He holds a master's degree in electrical engineering with a specialization in communication engineering. He has been architecting, designing, and developing software projects involving GUIs, networking, distributed systems, realtime systems, and modeling for more than eight years. Ramnivas began working with object-oriented Java systems five years ago. As a principal software engineer at Real-Time Innovations, Inc., Ramnivas leads the development of the next generation of ControlShell, a component-based programming framework for building complex real-time systems.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies