Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Aspect-Oriented Programming in ASPECTJ


Aug02: Aspect-OrientedProgramming& AspectJ

William is chief architect and director of QA at Hipbone Inc. He is also the author of Java RMI (O'Reilly & Associates, 2001). He can be contacted at [email protected].


Tips for Writing Aspects


Aspect-oriented programming extends the object-oriented paradigm by enabling you to write more maintainable code using units of software modularity called "aspects." Aspects encapsulate elements such as performance optimization, synchronization, error checking/handling, monitoring/logging, and debugging, which cut across traditional module or class boundaries.

Aspects are separated ("modularized") from the classes and methods that make up components at design time, then compilers or interpreters create extended classes that combine aspect functionality with application components.

There are a number of tools and languages that support the concept of aspect-oriented programming (AOP). TransWarp (http://www.zope.org/Members/ pje/TransWarp/) and Pythius (http:// pythius.sourceforge.net/), for instance, provide Python-based AOP support, while AspectC++ (http://www.aspectc .org/) and AspectC (http://www.cs.ubc.ca/ labs/spl/projects/ aspectc.html) support C++ and C, respectively. Java Aspect Components or JAC (http://jac .aopsys.com/) is a Java AOP framework, while HyperJ (http://www.research.ibm.com/hyperspace/ HyperJ/HyperJ.htm) provides Java extensions for AOP. In this article, I will focus on AspectJ (http:// aspectj.org/). For a more complete list of AOP tools and languages, see http:// aosd.net/tools.html.

Developed at Xerox PARC, AspectJ is a freely available open-source aspect-oriented extension to Java for the modularization of Java resource sharing, error checking, design patterns, and distribution. As Figure 1 illustrates, AspectJ makes modularization possible by taking code that appears in multiple places in a codebase and putting it in a single place, along with instructions on how to insert it into all the correct places.

The most common example of code that's amenable to this sort of "pulling out" is logging code, which:

  • Appears in many places in the codebase and isn't intrinsically a part of any class.
  • Is conceptually distinct from the classes it occurs in.

  • Isn't an intrinsic part of the program's functionality.

  • Gets frequently inserted and removed during development.

  • Is often removed from the final shipping codebase.

The first two properties are important, and code that exhibits them is referred to as "crosscutting code." The goal of AOP is to pull out the crosscutting code and put all the related pieces of code together in a single place, along with instructions on how to insert the crosscutting code back into the program (either statically at compile time or dynamically at run time). The last three properties are mostly important for tutorials — they tell you that everyone has a good idea of how to pull out logging code (and how to evaluate the resulting program).

AspectJ Overview

AspectJ is both a language for doing aspect-oriented programming and a set of tools. Java programs are valid AspectJ programs and can access all the standard Java libraries. The AspectJ toolset includes a compiler, debugger, ant task (for automating builds), and plug-ins for several Java IDEs (such as JBuilder).

The AspectJ language centers around three main concepts:

  • Join points, which are places in a codebase where something happens. For example, there are join points for method calls, for catching exceptions, and for changing the value of a member variable. Join points are mostly an abstraction; when using AspectJ, you use pointcuts.
  • Pointcuts, which are named collections of join points that have something in common. AspectJ contains a complete language for describing pointcuts. Understanding how to write pointcuts, and when a particular pointcut applies, is the hardest part of using AspectJ.

  • Advice, which is Java code that has been attached to a pointcut. For example, you use pointcuts in AspectJ to specify a set of join points in a program where you want something to happen. You then attach advice to your pointcuts to say what should happen. Advice is almost always fairly simple code. The important thing about advice is not that it's particularly tricky or complex, but that the advice is executed when a particular pointcut applies.

call Pointcuts and execution Pointcuts

The two most commonly used pointcuts are the call pointcut and the execution pointcut. The basic syntax for these two pointcuts is:

pointcut name(): call ([method signature])

pointcut name(): execution([method signature])

where name is the name of the pointcut (and must be unique) and [method signature] is an expression that captures a set of methods. AspectJ has a whole language — based on string matching — for specifying signatures. For example, * com.wgrosso..*.*(..) matches every method on every class that's in a package whose name begins with com.wgrosso. Listing One presents some signatures along with their explanation.

As Figure 2 shows, you can think of a method call as being on the boundary of two instances. (When a method is invoked, a thread exits the scope of one instance and enters the scope of another one. In doing so, it has crossed through a join point.)

From the point of view of AspectJ, a stack trace (like that in Figure 3) is nothing but a list of join points that the current thread has traversed and will traverse again as the stack unwinds. (While it's convenient to think of call and execution pointcuts as being comprised of pointcuts that are on the boundaries of distinct instances, they're also involved when one method on an instance calls another method on the same instance. It's the method call that matters, not the number of instances involved.)

A call pointcut specifies a set of join points right before (or after) the methods are called. That is, if you attach advice to a call pointcut and a thread is about to call a method that matches the signature of the pointcut, the advice will be executed. On the other hand, execution pointcuts specify a set of join points right after the method is called (or right before the call returns). In short, call pointcuts occur inside the calling object, whereas execution pointcuts occur inside the target object. In most cases, it doesn't matter whether you use advice based on call or execution join points.

There are differences between call and execution pointcuts, however. For one thing, call and execution execute at different times. This means that if more than one piece of advice is attached to a particular method, then whether a particular piece of advice is defined using a call or an execution pointcut can make a difference in how the program executes.

Also, call pointcuts are based on the compile-time object types, as understood by the calling class, while execution pointcuts are based on the run-time type of the target of the method call. Thus, if the calling object doesn't know the exact type of the target, then whether advice is based on a call or execution pointcut could be important. In Listing Two, the instance stringWrapper is declared to be an instance of Object, but at run time, it's an instance of StringWrapper. Even though they have the same signatures, the call pointcut in Listing Two is never applicable, while the execution pointcut is.

The final difference between call and execution pointcuts involves a shortcoming in the current release of AspectJ. Because AspectJ is implemented in the compiler and requires you to have the source code for all the classes, you cannot specify execution pointcuts for classes for which you don't have the source.

cflow Pointcuts

The idea behind a cflow (control flow) pointcut is that you often want to insert advice based on something like a stack trace, not just the currently executing line of code.

cflow pointcuts are defined in terms of other pointcuts. The basic syntax for a cflow pointcut is:

pointcut name(): cflow([name of a different pointcut])

pointcut name(): cflowbelow ([name of a different pointcut])

The current point of execution is in the cflow of a pointcut if the pointcut is below it in the stack trace (for example, if unwinding the stack causes the current thread of execution to pass through the pointcut).

The difference between cflow and cflowbelow is that a pointcut is inside its own cflow, but is not inside its cflowbelow (cflow and cflowbelow are analogous to <= and <). Listing Three has examples of cflow pointcuts.

By themselves, cflow pointcuts aren't that useful. What makes them useful is a pointcut algebra. You can use all the standard Boolean operators (!, &&, ||, and so on) with pointcuts. Combining pointcuts lets you build fairly sophisticated statements.

For example, suppose you wrote a library and, in another program, want to log all calls into the library. That is, you want to log calls coming into your library from other code, not calls from one library object to another and not calls that originate in your library code (even if some nonlibrary classes appear in the middle of the stack trace). This can be done in just two steps: You first write a call pointcut specifying your library's public methods, call it "pointcut A." Then the pointcut defined by the expression A && !cflowbelow(A) captures exactly what you want — it picks out, at run time, all the method calls into your library that are not in the cflow of a method call into your library. Listing Four is a complete example of this type of pointcut.

Adding Advice

There are three types of advice: before advice, after advice, and around advice.

before and after advice are straightforward. before advice executes immediately before the point in the code indicated by the join point. For its part, after advice executes immediately after the point in the code indicated by the join point. Listing Five prints the sequence "12345," illustrating the basic syntax of before and after advice. As Figure 4 depicts, the reason Listing Five prints "12345" is that the statements are executed in the following order:

1. The before advice attached to the call pointcut.

2. The before advice attached to the execution pointcut.

3. The actual method body.

4. The after advice attached to the execution pointcut.

5. The after advice attached to the call pointcut.

around advice is more complicated than before or after advice. The way around advice works is that some of the code in the advice executes before the pointcut, and that code must call proceed for the contents of the method to execute; see Listing Six.

A Real-World Example

A few months ago, I had a server that kept running out of memory, and standard memory profiling tools didn't show a memory leak when we ran it on developer machines. What we needed to do was trace memory allocations for a week, while the server was running under real-world conditions.

Luckily, I recalled that Heinz Kabutz addressed the problem of tracking object allocations in issue 38 of his Java Specialist newsletter (http://www.smotricz.com/ kabutz/). Kabutz's solution involves inserting code into java.lang.Object to track object allocations. While this approach can be made to work, it is tricky and fragile. Furthermore, you can get a similar result with a simple aspect. Listing Seven (available electronically; see "Resource Center," page 5) contains code for tracking the number of currently allocated instances in a program. It doesn't track allocations of objects that aren't in your codebase (for example, instances of java.lang.String are not tracked) because of the way AspectJ is currently implemented. But it does track the number of live objects in your program, printing out a status report to the console at predefined intervals, and vending a current report via an RMI interface.

This approach uses only one aspect, which is called during the constructor of all the objects that are tracked. Every time an object is created, it is added into a hash table using a weak reference. A background thread goes through and cleans the hash table occasionally. And the results are summarized and sent over the wire when a client asks.

The impressive thing about this example is that it just works. It requires absolutely no code changes to the original classes. If you have an existing project, you can change the signature of the pointcut and use the resulting aspect to track memory allocations over time. If you change the output format slightly, you can store the results to a comma-delimited file format, then graph your object allocations in Excel. It's very easy to do.

To further illustrate, consider the logging code in Listing Eight (also available electronically). The idea here is that you have an RMI server that is accepting both remote calls (method calls that originate in another process) and in-process calls. You want to log the remote calls, along with the time they occurred, but not the local calls. Listing Eight includes an aspect that lets you do this. This technique works because of the way RMI is structured. Any remote call must go through the RMI run time — that is, through a class in one of the java.rmi.* packages — before it gets into the server. Local calls do not go through the RMI run time at all. This means that a clever combination of execution and cflowbelow pointcuts let you spot exactly those threads of execution that originate in a call from a remote process.

This approach works without requiring code changes in the original source code. If you have an existing project, you can change the signature of the pointcut and use the resulting aspect to log remote method calls to your server. And that's the beauty of aspects.

DDJ

Listing One

/* Pulls out calls to any public method that throws a RemoteException */
    pointcut example1(): call(public * * (..) throws RemoteException);

/* Pulls out calls to a constructor of any subclass of UnicastRemoteObject. 
The '+' indicates any subclass.  */
    pointcut example2(): call(UnicastRemoteObject+.new(..));

/* Pulls out executions of public methods of classes in any package below 
com.wgrosso (including classes in com.wgrosso). The '..' is what indicates 
any subpackage. */
    pointcut example3(): execution(public * com.wgrosso..*.*(..));

/* Pulls out executions of public methods of classes in any package below 
com.wgrosso (including classes in com.wgrosso). The methods have to 
be "setter" methods and return void */
    pointcut example4(): execution(public void com.wgrosso..*.set*(..));

Back to Article

Listing Two

/* The call pointcut isn't applicable, because stringWrapper is declared to 
be of type Object (which does not match the signature). Because StringWrapper 
is in the package com.wgrosso, the execution pointcut does apply.  */
    pointcut callExample(): call(* com.wgrosso..*.*(..));
    pointcut executionExample(): execution(* com.wgrosso..*.*(..));

/* The code */
    public static void main(String[] args) {
        if ((null==args) || (0==args.length)) {
            System.out.println("No arguments");
        }
        else {
            System.out.println("Arguments:\n");
            for (int i=0; i < args.length; i++) {
                Object stringWrapper = new StringWrapper(args[i]);
                System.out.println("\t" + stringWrapper.toString() + "\n");
            }
        }
    }

Back to Article

Listing Three

/* The call pointcut isn't applicable, because stringWrapper is declared to 
be of type Object (which does not match the signature). Because StringWrapper 
is in the package com.wgrosso, the execution pointcut does apply.  */
    pointcut callExample(): call(* com.wgrosso..*.*(..));
    pointcut executionExample(): execution(* com.wgrosso..*.*(..));

/* The code */
    public static void main(String[] args) {
        if ((null==args) || (0==args.length)) {
            System.out.println("No arguments");
        }
        else {
            System.out.println("Arguments:\n");
            for (int i=0; i < args.length; i++) {
                Object stringWrapper = new StringWrapper(args[i]);
                System.out.println("\t" + stringWrapper.toString() + "\n");
            }
        }
    }

Back to Article

Listing Four

/* enteringFromExternalSource picks out join points which are involve public 
methods and are not in the cflowbelow of a public method invocation. You 
need to use && and ! to define this pointcut. */
    pointcut comWGrossoPulicMethods():
        call(public * com.wgrosso..*.*(..));
    pointcut enteringFromExternalSource():
        comWGrossoPulicMethods() &&
        !cflowbelow(comWGrossoPulicMethods());

Back to Article

Listing Five

/*  The entire aspect. */
package com.wgrosso.ddjarticle1;
import org.aspectj.lang.*;
public aspect Aspect_PrintThree {
    pointcut CallStringWrapper(): call(* com.wgrosso..*.*(..));
    pointcut ExecuteStringWrapper(): execution(* com.wgrosso..*.*(..));
    before(): CallStringWrapper() {
        System.out.println("1");
    }
    before(): ExecuteStringWrapper() {
        System.out.println("2");
    }
    after(): ExecuteStringWrapper() {
        System.out.println("4");
    }
    after(): CallStringWrapper() {
        System.out.println("5");
    }
}
/* The entire class */
package com.wgrosso.ddjarticle1;
public class PrintThree {
    public static void main(String[] args) {
        System.out.println("3");    
    }
}

Back to Article

Listing Six

/* We still need to define a pointcut */
    pointcut callExample(): call(public void com.wgrosso..*.*(..));

/* Around advice must call proceed in order for the  original method 
invocation to occur. Note that around advice needs a return type that 
matches the pointcut. */
    void around(): callExample() {
        /* Any code before the proceed executes before the method body. */
        proceed(); // proceed executes the method body
        /*  Any code after the proceed executes after the method body.*/
   }

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.