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:
- Part 1. Separate software concerns with aspect-oriented programming
- Part 2. Learn AspectJ to better understand aspect-oriented programming
- Part 3. Use AspectJ to modularize crosscutting concerns in real-world problems
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.
|
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)
.
|
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.
|
Exception-handler pointcuts
Fourth, exception-handler pointcuts capture the execution of exception handlers of specified types. They take the form of handler(ExceptionTypePattern)
.
|
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)
.
|
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)
.
|
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)
.