AspectJ Tutorial
AspectJ Tutorial
Abstract
Aspect Oriented Programming (AOP) is a new programming technique for
modularizing behavior that cuts across many classes. This code-level tutorial
will focus on AspectJ, a popular AOP implementation for Java.
The
tutorial will cover an introduction to Aspects, the AspectJ language, and
practical immediate uses of aspects: plugable logging, tracing, debugging,
profiling, and testing.
This tutorial based on an AspectJ presentation I have given, and is still under development. If you have any comments, please email me.
Introduction to AOP -- What is AOP?
AOP is a technique for making
programs more modular by identifying and separating "cross-cutting concerns". A
concern is a feature point, or a requirement. A crosscutting concern is a
concern that needs to be applied in many different compilation modules. In Java,
this means we are talking about a requirement that touches many different
classes. For example:
- "All exceptions must be logged" or
- "Capture all sql statements produced by the application"
- "Cache all data that is rarely changed"
- "Record all changes to the account for historical purposes"
- Timing, authentication, and persistence are also applicable concerns
These sorts of requirements would normally require implementation in many different classes. AOP calls this `code-scattering' or `tangling'. This can lead to code that is difficult to change (e.g. a new requirement says - now when logging this, log the current stacktrace as well.)
Why AOP?
OOP has become accepted as a mainstream paradigm for
analysis design and development. AOP is not a replacement for OOP. It is a tool
to help deal with some of the problems inherent in OOP. Some problems map well
to object hierarchies. Some do not. Aspects are in some ways like Dynamic
Proxies, XDoclet, EJB/JSP containers, and CLR (& JSR 201) meta-data. All are
trying to resolve some long-standing problems that have become obvious as OOP
matures. All are trying to remove redundant code, make it possible to supply
additional behavior at runtime to objects that is declaratively proposed and
interjected at runtime.
AOP wants to:
- Separate concerns
- Provide a way via language and runtime to describe crosscuts
How does it work?
- Write the classes
- Write the aspects
- Weave together
When do you weave?
When you weave is implementation dependent.
AspectJ 1.0 did it at compile time (meaning that you needed the source for the
code you wanted to weave.) AspectJ 1.1 uses a bytecode weaver, so classes
without source can be woven. In reality it could be done at compile time, link
time (1.1), load time (classloader), or run time (vm).
AspectJ
AspectJ is an AOP implementation by Gregor Kiczales and
others. Initially started at Xerox PARC, now an Eclipse.org subproject. I t was
a winner in this years JavaWorld Editors' Choice Awards in "Most Innovative Java
Product or Technology". It is an extension to java, with a new syntax.
Definitions
- AspectJ - an open source language that is 100% compatible with Java. It is a particular implementation of AOP much like Java is an implementation of OOP.
- Concern - a feature point, or requirement
- Crosscutting concern - a feature point that cannot be encapsulated in one class
- Aspect - the code module that centralizes the crosscutting functionality. The aspect is the AOP analog to the class.
- Join Points - a specific point in the execution of the program. E.g. method calls, constructor invocations, exception handlers.
- Pointcuts - a structure and syntax for selecting join points in a program. E.g. to match all set methods on a particular class.
- Advice - code to be executed when a join point is reached by the application. AspectJ exposes an API in the advice that makes contextual information available. In most AOP implementations, advice can run before the join point, after, or around.
- Introductions (Inter-type declaration) - the ability of aspect oriented programs to alter existing program structure (adding members to existing classes, adding methods, changing class hierarchies, etc.)
Getting/installing and runtime
Download the AspectJ distribution
from www.eclipse.org/aspectj. It is
a standalone distribution and the JAR file you download is an executable JAR
install file. It also includes a structure browser, and a debugger.
You can use the compiler on sources, or it can weave with jars, output to a jar, create aspect libraries, compile without weaving, compile incrementally, etc.
Writing the aspect
- Write the component.
- Write the aspect.
- In the aspect, first write the pointcut, then the corresponding advice.
- Compile with the ajc compiler, using the aspectjrt.jar file on the classpath for compile and run.
The pointcut (which selects the joinpoint, and the advice) are packaged into the compilation unit, the aspect. Aspects can be named with aj extensions, or java.
Example
The following example shows a simple Java class,
BankAccount, and two simple aspects. The first will fire when the balance is
changed, the second, the balance or the owner.
import java.math.BigDecimal; public class BankAccount { private String owner; private BigDecimal balance; public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public BigDecimal getBalance() { return balance; } public void setBalance(BigDecimal balance) { System.out.println("in BankAccount.setBalance:"+balance); this.balance = balance; } public static void main(String[] args) { BankAccount b = new BankAccount(); b.increment(); b.setOwner("Owner"); b.setBalance(new BigDecimal ("300.00")); b.setBalance(b.getBalance().subtract(new BigDecimal("100.00"))); } } public aspect BankAspect { //The pointcut designates where we want to cross cut the application pointcut changeBalance() : call (public void BankAccount.setBalance(java.math.BigDecimal)); //the advice is what we want to do when we crosscut before() : changeBalance() { System.out.println("Before setBalance advice:"+thisJoinPoint.getSignature()); } } public aspect BankAspect2 { pointcut changeBalance() : call (public void setBalance(java.math.BigDecimal)); pointcut changeOwner() : call (public void setOwner(String)); before() : changeBalance() { System.out.println("setBalance:"+thisJoinPoint.getSignature()); } before() : changeOwner() { System.out.println("setOwner:"+thisJoinPoint.getSignature()); } }//show output
Identify the joinpoint
AspectJ defines a number of join points:
- Method call
- Join point is within the calling application
- pointcut foo() : call( public void setOwner(String) ) && target(BankAccount); defines a pointcut as a call from any object of static method to a method
- setOwner as long as the target of the call is of type BankAccount
- Constructor call
- Join point is within calling object
- pointcut const() : initialization (BankAccount.new() );
- Method call execution
- Join point is within the called method, before any code is executed
- pointcut inFoo() : execution( public void setOwner(String) );
- Constructor call execution-- Within constructor body, before statements
- Field get -- When a field is referenced
- Field set -- When a field is changed
- Exception handler execution -- When an exception handler is executed
- Class initialization -- When static intializers are executed
- Object initialization -- Just before return of object constructor
Patterns
- Names can be matched with *, params with `..'
- pointcut foo() : call (* * BankAccount.* (*)) matches all method calls on BankAccount with 1 parameter
- pointcut foo() : call (* * . (*)) matches all method calls with 1 parameter
- pointcut foo() : call (* * . (..)) matches all method calls
- Subtypes can be matched with +
- pointcut foo() : call (* * BankAccount+.* (*))
- Can also match on throws.
Examples of Naming Patterns, Tracing
public aspect BankAspectWild { pointcut changeBalance() : call (public * set*(..)); before() : changeBalance() { System.out.println("ADVICE:"+thisJoinPoint.getSignature()); } } public aspect TraceAspect { pointcut trace () : call (* * (..)) && ! within(TraceAspect); before() : trace() { System.out.println("ADV:"+thisJoinPoint.getSignature()); } } public aspect TraceAspect2 { pointcut interestingJoinPoints() : within(BankAccount); Object around() : interestingJoinPoints() { System.out.println("About to do " + thisJoinPoint); Object o = proceed(); System.out.println("Finished " + thisJoinPoint); return o; } }
Pointcuts
A point cust is designated as follows: pointcut
name([parameters]): designator(ajoinpoint);
- the name will be used to handle actions when a join point is encountered
- the ajoinpoint is a signature match
- the designator decides when this particular join point will match (e.g. on method call)
- pointcuts can be combined with logical (set) operators &&, ||, !
public aspect BankAspectOr { pointcut change() : call (public void setBalance(java.math.BigDecimal)) || call (public void setOwner(String)); before() : change() { System.out.println("ADVICE:"+thisJoinPoint.getSignature()); } }
Designators
Pointcut designators match join points, and exposes
additional functionality
- execution - method execution
- call - call to method
- initialization - first constructor
- handler - exception
- get - field access
- set - field write
- this - returns the object associated with a join point, or limits the scope
- args - exposes the arguments to join point, or limits the scope
- target - returns the target object, or limits the scope
- cflow - join points in the execution flow of another join point
- cflowbelow - join points in the execution flow of another join point, excluding the current
- staticinitialization - static init
- withincode - join points within a method or constructor
- within - match with a type
- if - dynamic condition
- adviceexecution - match advice join points
- preinitialization - preinit
We will focus on just a few: call, exception
Call
call - use this when
you are interested in the calling of a method
format: call (join point
signature)
e.g. call (public void BankAccount.setOwner()
);
handler
Captures the execution of an exception handler anywhere in
the primary application.
format: handler(type)
e.g.
handler(ClassCastException)
(Remember, patterns also apply here, * and
+)
import java.util.Date; public class ExceptionHandlerExample { public static void main(String[] args) throws SampleException { Object b = new BankAccount(); try { throw new SampleException(); } catch (SampleException e) { e.printStackTrace(); } } } public aspect ExceptionHandlerAspect { pointcut handle() : handler(SampleException); before() : handle() { System.out.println( "Exception occurred: " + thisJoinPoint.toString() ); } }State based designators
- this (class type pattern, or id) - used to narrow type, or expose object to advice
- target(class type pattern, or id) - used to narrow type, or expose object to advice
- args(class type pattern, or id) - used to narrow type, or expose object to advice
pointcut setBalance(BankAccount b) : call(public void setBalance(*)) && target (b); before (BankAccount b) : setBalance(b) { //b is accessible here }Control Flow Designators
So far join points have matched throughout an application. Control flow designators allow us to match within certain flows. cflow and cflowbelow
Class Initialization
- staticinitialization
- within (join point)
- withincode( join point)
if()
adviceexectuion()
Interfaces
Pointcuts are valid on interfaces.
Advice
Advice is the second half of AOP, the code that gets executed when a join point is reached. Advice is always relative to a joinpoint.
public aspect BankAspectSysout { pointcut sysout() : call (public void print*(*)) && target(java.io.PrintStream) && !within(BankAspectSysout) ; before() : sysout() { System.out.println("Wrote to sysout at:"+thisJoinPoint.getSignature()); System.out.print(thisJoinPointStaticPart.getSourceLocation() + ":"); } }Give the advice before: excellent for preconditions argument checking, setup code, lazy init before() : pointcutName() { System.out.println("ADVICE:"+thisJoinPoint.getSignature()); } after After can be qualified with: after returning, or after throwing Cleanup of resources checking/manipulating the return value around the most powerful advice can replace invocation, or just surround use proceed() to call method
public class NullReturnClass { public String lookup(String s){ return null; } public static void main(String[] args) { new NullReturnClass().lookup("someval"); } } public aspect NoNullReturnAspect { //The first pointcut matches all calls, //The second avoids those that have a void return type. pointcut methodsThatReturnObjects(): call(* *.*(..)) && !call(void *.*(..)); Object around(): methodsThatReturnObjects() { Object lRetVal = proceed(); if (lRetVal == null) { System.err.println( "Detected null return value after calling " + thisJoinPoint.getSignature().toShortString() + " in file " + thisJoinPoint.getSourceLocation().getFileName() + " at line " + thisJoinPoint.getSourceLocation().getLine()); } return lRetVal; } }
thisJoinPoint
- info about the join point that was just matched from inside the advice
- the kind of join point that was matched
- the source location of the current join point
- normal, short and long string representations of the current join point
- the actual argument(s) to the method or field selected by the current join point
- the signature of the method or field selected by the current join point
- the target object
- the currently executing object
- a reference to the static portion of the object representing the current join point. This is also available through the special variable thisJoinPointStaticPart.
Formal Access to Parameters To access parameters statically:
- Add a parameter to the pointcut name
- Add && args(s) to the designator
- Add parameter to advice designator
- Add variable name to advice body
Reflective Access to Join Point The joinpoint can be reflectively accessed via objects:
- thisJoinPoint - getTarget(), getThis(), getArgs() - accessed via reflection
- thisJoinPointStaticPart - signature, kind, location, no allocation when used
Advice and Exceptions
Advice is expected not to throw and
exceptions not thrown in the code being called. This means things would normally
get wrapped in a Runtime.
Precedence
use the precedence keyword in an aspect: declare
precedence : A , B; Sub aspects execute before parents. Otherwise undefined.
Multiple advice in an aspect: natural order order of declaration
Inter-type Declarations (Introductions)
Add new functionality to
an application, new attributes, new methods, and change inheritance. To do this,
you will write normal variable and methods in your aspect, but prefix them with
your "classname.". You can then access those fields and methods in a) normal
java code, or b) in your aspects. To get access to the object being references,
: Add a parameter to the pointcut name Add && target(t) to the
designator Add parameter to advice designator Add variable name to advice body
Very powerful, a little too complicated to get into. You can: add members (id
fields, dirty-ness) add methods (toXML, storeToJDBC) add types that extend
existing types or implement interfaces declare custom compilation errors or
warnings convert checked exceptions to unchecked Can use from aspects &
regular code. Can do wacky things: Add concrete fields & methods to
interfaces (no constructors) Modify aspects Make an existing class dynamically
implement an interface Make an existing class extend another Like EJB?
public aspect BankAspectIntro { private int BankAccount.count = 0; public void BankAccount.increment() { count++; } public int BankAccount.getCount() { return count; } pointcut changeBalance(BankAccount b, java.math.BigDecimal d) : call (public void setBalance(java.math.BigDecimal)) && args(d) && target(b); before(BankAccount b, java.math.BigDecimal d) : changeBalance(b,d) { b.increment(); System.out.println("Change amt: "+d.toString()+" balance now "+ b.getBalance() +" has been changed:"+b.getCount()+" times"); } }Declare custom compilation warn/error declare [error|warning] : statically determinable pointcut
import java.sql.*; public class SQLAccess { public static void main (String[] args) throws java.sql.SQLException { Connection con = java.sql.DriverManager.getConnection("connect"); Date d = new Date(System.currentTimeMillis()); //do something with the connection } } public aspect SQLAccessAspect { /* Any call to methods or constructors in java.sql */ pointcut restrictedCall(): call(* java.sql.*.*(..)) || call(java.sql.*.new(..)); /* Any code in my system not in the sqlAccess package */ pointcut illegalSource(): !within(com.foo.sqlAccess.*); declare error: restrictedCall() && illegalSource(): "java.sql package can only be accessed from com.foo.sqlAccess"; }Softening Exceptions Suppress checked as unchecked declare soft : java.io.IOException: execution (* * (..)) && within (SomeClass); Then does not need to be handled in the code. [Example: 11]Soften exception
import java.io.FileInputStream; import java.io.ObjectInputStream; public class CacheClass { public Object getFromCache(Object o) { FileInputStream f = new FileInputStream("o.obj"); ObjectInputStream ois = new ObjectInputStream(f); Object o2 = ois.readObject(); ois.close(); return o2; } public static void main(String[] args) { CacheClass c = new CacheClass(); c.getFromCache(new Object()); } } public aspect CacheClassAspect { pointcut cache() : within(CacheClass); declare soft : Exception: cache(); }Aspect Structure Can be written standalone, same file, or embedded within a class. Aspects can extend, be abstract, inherit from classes, and implement interfaces. Instantiation All of the aspects seen so far have been singletons. This is the default, but can be specified explicitly: aspect Foo issingleton { } Per-Object aspect Foo perthis(pointcut) {} aspect Foo pertarget(pointcut) {} create a new one whenever the designated pointcut is matched Per control flow aspect Foo percflow(pointcut) {} aspect Foo percflowbelow(pointcut) {} create a new one whenever the designated flow of execution is entered Development Examples Advise methods in Java API Development Toolbox
- tracing
- log sql
- good coding practice (sql access)
- log errors, serialize, produce junit
- dbc (no return null)
- caching
- exception cleanup
- who called ctx.setRollBackOnly();
- Logging & Exception Handling
- Debugging
- Profiling and performance monitoring
- Testing
- Caching
- logging
- Monitoring
- Authorization
- Pattern implementation
- Testing refactorings
So, is it a good idea?
- Tools Support
- Eclipse, Jbuilder, NetBeans, Emacs, Idea 4.0?
- Alternative Implementations
- AspectWerkz http://aspectwerkz.sourceforge.net/ XML definition file
- Nanning
- HyperJ
- Bindings for non-Java languages
- Component boundaries
- Extension to Java, not yet widely accepted, not a JSR, integration with J2EE
- Patent nonsense?
- How will we model? What are appropriate roles? What patterns should it be used in?
- No dynamic weaving (can be accomplished by other means)
- Visibility - programmer looking at method knows about aspects that advise? (proper tool support)