A pointcut is a program element that picks out join points and exposes data from the execution context of those join points. Pointcuts are used primarily by advice. They can be composed with boolean operators to build up other pointcuts. The primitive pointcuts and combinators provided by the language are:
Pointcuts are defined and named by the programmer with the pointcut declaration.
pointcut publicIntCall(int i): call(public * *(int)) && args(i);
A named pointcut may be defined in either a class or aspect, and is treated as a member of the class or aspect where it is found. As a member, it may have an access modifier such as public or private.
class C { pointcut publicCall(int i): call(public * *(int)) && args(i); } class D { pointcut myPublicCall(int i): C.publicCall(i) && within(SomeType); }
Pointcuts that are not final may be declared abstract, and defined without a body. Abstract pointcuts may only be declared within abstract aspects.
abstract aspect A { abstract pointcut publicCall(int i); }
In such a case, an extending aspect may override the abstract pointcut.
aspect B extends A { pointcut publicCall(int i): call(public Foo.m(int)) && args(i); }
For completeness, a pointcut with a declaration may be declared final.
Though named pointcut declarations appear somewhat like method declarations, and can be overridden in subaspects, they cannot be overloaded. It is an error for two pointcuts to be named with the same name in the same class or aspect declaration.
The scope of a named pointcut is the enclosing class declaration. This is different than the scope of other members; the scope of other members is the enclosing class body. This means that the following code is legal:
aspect B percflow(publicCall()) { pointcut publicCall(): call(public Foo.m(int)); }
Pointcuts have an interface; they expose some parts of the execution context of the join points they pick out. For example, the PublicIntCall above exposes the first argument from the receptions of all public unary integer methods. This context is exposed by providing typed formal parameters to named pointcuts and advice, like the formal parameters of a Java method. These formal parameters are bound by name matching.
On the right-hand side of advice or pointcut declarations, in certain pointcut designators, a Java identifier is allowed in place of a type or collection of types. The pointcut designators that allow this are this, target, and args. In all such cases, using an identifier rather than a type does two things. First, it selects join points as based on the type of the formal parameter. So the pointcut
pointcut intArg(int i): args(i);
picks out join points where an int (or a byte, short, or char; anything assignable to an int) is being passed as an argument. Second, though, it makes the value of that argument available to the enclosing advice or pointcut.
Values can be exposed from named pointcuts as well, so
pointcut publicCall(int x): call(public *.*(int)) && intArg(x); pointcut intArg(int i): args(i);
is a legal way to pick out all calls to public methods accepting an int argument, and exposing that argument.
There is one special case for this kind of exposure. Exposing an argument of type Object will also match primitive typed arguments, and expose a "boxed" version of the primitive. So,
pointcut publicCall(): call(public *.*(..)) && args(Object);
will pick out all unary methods that take, as their only argument, subtypes of Object (i.e., not primitive types like int), but
pointcut publicCall(Object o): call(public *.*(..)) && args(o);
will pick out all unary methods that take any argument: And if the argument was an int, then the value passed to advice will be of type java.lang.Integer.
The "boxing" of the primitive value is based on the original primitive type. So in the following program
public class InstanceOf { public static void main(String[] args) { doInt(5); } static void doInt(int i) { } } aspect IntToLong { pointcut el(long l) : execution(* doInt(..)) && args(l); before(Object o) : el(o) { System.out.println(o.getClass()); } }
The pointcut will match and expose the integer argument, but it will expose it as an Integer, not a Long.
AspectJ provides two primitive pointcut designators designed to capture method call and execution join points.
AspectJ provides two primitive pointcut designators designed to capture field reference and set join points:
All set join points are treated as having one argument, the value the field is being set to, so at a set join point, that value can be accessed with an args pointcut. So an aspect guarding a static integer variable x declared in type T might be written as
aspect GuardedX { static final int MAX_CHANGE = 100; before(int newval): set(static int T.x) && args(newval) { if (Math.abs(newval - T.x) > MAX_CHANGE) throw new RuntimeException(); } }
AspectJ provides primitive pointcut designators designed to capture the initializer execution join points of objects.
AspectJ provides one primitive pointcut designator to pick out static initializer execution join points.
AspectJ provides one primitive pointcut designator to capture execution of exception handlers:
All handler join points are treated as having one argument, the value of the exception being handled. That value can be accessed with an args pointcut. So an aspect used to put FooException objects into some normal form before they are handled could be written as
aspect NormalizeFooException { before(FooException e): handler(FooException) && args(e) { e.normalize(); } }
AspectJ provides one primitive pointcut designator to capture execution of advice
This can be used, for example, to filter out any join point in the control flow of advice from a particular aspect.
aspect TraceStuff { pointcut myAdvice(): adviceexecution() && within(TraceStuff); before(): call(* *(..)) && !cflow(myAdvice) { // do something } }
Many concerns cut across the dynamic times when an object of a particular type is executing, being operated on, or being passed around. AspectJ provides primitive pointcuts that capture join points at these times. These pointcuts use the dynamic types of their objects to pick out join points. They may also be used to expose the objects used for discrimination.
The this pointcut picks out each join point where the currently executing object (the object bound to this) is an instance of a particular type. The target pointcut picks out each join point where the target object (the object on which a method is called or a field is accessed) is an instance of a particular type. Note that target should be understood to be the object the current join point is transfering control to. This means that the target object is the same as the current object at a method execution join point, for example, but may be different at a method call join point.
The args pointcut picks out each join point where the arguments are instances of some types. Each element in the comma-separated list is one of four things. If it is a type name, then the argument in that position must be an instance of that type. If it is an identifier, then that identifier must be bound in the enclosing advice or pointcut declaration, and so the argument in that position must be an instance of the type of the identifier (or of any type if the identifier is typed to Object). If it is the "*" wildcard, then any argument will match, and if it is the special wildcard "..", then any number of arguments will match, just like in signature patterns. So the pointcut
args(int, .., String)
will pick out all join points where the first argument is an int and the last is a String.
Some concerns cut across the control flow of the program. The cflow and cflowbelow primitive pointcut designators capture join points based on control flow.
The cflow pointcut picks out all join points that occur between entry and exit of each join point P picked out by Pointcut, including P itself. Hence, it picks out the join points in the control flow of the join points picked out by Pointcut.
The cflowbelow pointcut picks out all join points that occur between entry and exit of each join point P picked out by Pointcut, but not including P itself. Hence, it picks out the join points below the control flow of the join points picked out by Pointcut.
The cflow and cflowbelow pointcuts may expose context state through enclosed this, target, and args pointcuts.
Anytime such state is accessed, it is accessed through the most recent control flow that matched. So the "current arg" that would be printed by the following program is zero, even though it is in many control flows.
class Test { public static void main(String[] args) { fact(5); } static int fact(int x) { if (x == 0) { System.err.println("bottoming out"); return 1; } else return x * fact(x - 1); } } aspect A { pointcut entry(int i): call(int fact(int)) && args(i); pointcut writing(): call(void println(String)) && ! within(A); before(int i): writing() && cflow(entry(i)) { System.err.println("Current arg is " + i); } }
It is an error to expose such state through negated control flow pointcuts, such as within ! cflowbelow(P).
While many concerns cut across the runtime structure of the program, some must deal with the lexical structure. AspectJ allows aspects to pick out join points based on where their associated code is defined.
The within pointcut picks out each join point where the code executing is defined in the declaration of one of the types in TypePattern. This includes the class initialization, object initialization, and method and constructor execution join points for the type, as well as any join points associated with the statements and expressions of the type. It also includes any join points that are associated with code in a type's nested types, and that type's default constructor, if there is one.
The withincode pointcuts picks out each join point where the code executing is defined in the declaration of a particular method or constructor. This includes the method or constructor execution join point as well as any join points associated with the statements and expressions of the method or constructor. It also includes any join points that are associated with code in a method or constructor's local or anonymous types.
The if pointcut picks out join points based on a dynamic property. It's syntax takes an expression, which must evaluate to a boolean true or false. Within this expression, the thisJoinPoint object is available. So one (extremely inefficient) way of picking out all call join points would be to use the pointcut
if(thisJoinPoint.getKind().equals("call"))
Note that the order of evaluation for pointcut expression components at a join point is undefined. Writing if pointcuts that have side-effects is considered bad style and may also lead to potentially confusing or even changing behavior with regard to when or if the test code will run.
One very important property of a join point is its signature, which is used by many of AspectJ's pointcut designators to select particular join points.
Join points associated with methods typically have method signatures, consisting of a method name, parameter types, return type, the types of the declared (checked) exceptions, and some type that the method could be called on (below called the "qualifying type").
At a method call join point, the signature is a method signature whose qualifying type is the static type used to access the method. This means that the signature for the join point created from the call ((Integer)i).toString() is different than that for the call ((Object)i).toString(), even if i is the same variable.
At a method execution join point, the signature is a method signature whose qualifying type is the declaring type of the method.
Join points associated with fields typically have field signatures, consisting of a field name and a field type. A field reference join point has such a signature, and no parameters. A field set join point has such a signature, but has a has a single parameter whose type is the same as the field type.
Join points associated with constructors typically have constructor signatures, consisting of a parameter types, the types of the declared (checked) exceptions, and the declaring type.
At a constructor call join point, the signature is the constructor signature of the called constructor. At a constructor execution join point, the signature is the constructor signature of the currently executing constructor.
At object initialization and pre-initialization join points, the signature is the constructor signature for the constructor that started this initialization: the first constructor entered during this type's initialization of this object.
At a handler execution join point, the signature is composed of the exception type that the handler handles.
At an advice execution join point, the signature is composed of the aspect type, the parameter types of the advice, the return type (void for all but around advice) and the types of the declared (checked) exceptions.
The withincode, call, execution, get, and set primitive pointcut designators all use signature patterns to determine the join points they describe. A signature pattern is an abstract description of one or more join-point signatures. Signature patterns are intended to match very closely the same kind of things one would write when declaring individual members and constructors.
Method declarations in Java include method names, method parameters, return types, modifiers like static or private, and throws clauses, while constructor declarations omit the return type and replace the method name with the class name. The start of a particular method declaration, in class Test, for example, might be
class C { public final void foo() throws ArrayOutOfBoundsException { ... } }
In AspectJ, method signature patterns have all these, but most elements can be replaced by wildcards. So
call(public final void C.foo() throws ArrayOutOfBoundsException)
picks out call join points to that method, and the pointcut
call(public final void *.*() throws ArrayOutOfBoundsException)
picks out all call join points to methods, regardless of their name name or which class they are defined on, so long as they take no arguments, return no value, are both public and final, and are declared to throw ArrayOutOfBounds exceptions.
The defining type name, if not present, defaults to *, so another way of writing that pointcut would be
call(public final void *() throws ArrayOutOfBoundsException)
The wildcard .. indicates zero or more parameters, so
execution(void m(..))
picks out execution join points for void methods named m, of any number of arguments, while
execution(void m(.., int))
picks out execution join points for void methods named m whose last parameter is of type int.
The modifiers also form part of the signature pattern. If an AspectJ signature pattern should match methods without a particular modifier, such as all non-public methods, the appropriate modifier should be negated with the ! operator. So,
withincode(!public void foo())
picks out all join points associated with code in null non-public void methods named foo, while
withincode(void foo())
picks out all join points associated with code in null void methods named foo, regardless of access modifier.
Method names may contain the * wildcard, indicating any number of characters in the method name. So
call(int *())
picks out all call join points to int methods regardless of name, but
call(int get*())
picks out all call join points to int methods where the method name starts with the characters "get".
AspectJ uses the new keyword for constructor signature patterns rather than using a particular class name. So the execution join points of private null constructor of a class C defined to throw an ArithmeticException can be picked out with
execution(private C.new() throws ArithmeticException)
The signature-matching pointcuts all specify a declaring type, but the meaning varies slightly for each join point signature, in line with Java semantics.
When matching for pointcuts withincode, get, and set, the declaring type is the class that contains the declaration.
When matching method-call join points, the declaring type is the static type used to access the method. A common mistake is to specify a declaring type for the call pointcut that is a subtype of the originally-declaring type. For example, given the class
class Service implements Runnable { public void run() { ... } }
the following pointcut
call(void Service.run())
would fail to pick out the join point for the code
((Runnable) new Service()).run();
Specifying the originally-declaring type is correct, but would pick out any such call (here, calls to the run() method of any Runnable). In this situation, consider instead picking out the target type:
call(void run()) && target(Service)
When matching method-execution join points, if the execution pointcut method signature specifies a declaring type, the pointcut will only match methods declared in that type, or methods that override methods declared in or inherited by that type. So the pointcut
execution(public void Middle.*())
picks out all method executions for public methods returning void and having no arguments that are either declared in, or inherited by, Middle, even if those methods are overridden in a subclass of Middle. So the pointcut would pick out the method-execution join point for Sub.m() in this code:
class Super { protected void m() { ... } } class Middle extends Super { } class Sub extends Middle { public void m() { ... } }
Type patterns may be used to pick out methods and constructors based on their throws clauses. This allows the following two kinds of extremely wildcarded pointcuts:
pointcut throwsMathlike(): // each call to a method with a throws clause containing at least // one exception exception with "Math" in its name. call(* *(..) throws *..*Math*); pointcut doesNotThrowMathlike(): // each call to a method with a throws clause containing no // exceptions with "Math" in its name. call(* *(..) throws !*..*Math*);
A ThrowsClausePattern is a comma-separated list of ThrowsClausePatternItems, where
A ThrowsClausePattern matches the throws clause of any code member signature. To match, each ThrowsClausePatternItem must match the throws clause of the member in question. If any item doesn't match, then the whole pattern doesn't match.
If a ThrowsClausePatternItem begins with "!", then it matches a particular throws clause if and only if none of the types named in the throws clause is matched by the TypeNamePattern.
If a ThrowsClausePatternItem does not begin with "!", then it matches a throws clause if and only if any of the types named in the throws clause is matched by the TypeNamePattern.
The rule for "!" matching has one potentially surprising property, in that these two pointcuts
will match differently on calls tovoid m() throws RuntimeException, IOException {}
[1] will NOT match the method m(), because method m's throws clause declares that it throws IOException. [2] WILL match the method m(), because method m's throws clause declares the it throws some exception which does not match IOException, i.e. RuntimeException.
Type patterns are a way to pick out collections of types and use them in places where you would otherwise use only one type. The rules for using type patterns are simple.
First, all type names are also type patterns. So Object, java.util.HashMap, Map.Entry, int are all type patterns.
If a type pattern is an exact type - if it doesn't include a wildcard - then the matching works just like normal type lookup in Java:
So exact type patterns match based on usual Java scope rules.
There is a special type name, *, which is also a type pattern. * picks out all types, including primitive types. So
call(void foo(*))
picks out all call join points to void methods named foo, taking one argument of any type.
Type names that contain the two wildcards "*" and ".." are also type patterns. The * wildcard matches zero or more characters characters except for ".", so it can be used when types have a certain naming convention. So
handler(java.util.*Map)
picks out the types java.util.Map and java.util.java.util.HashMap, among others, and
handler(java.util.*)
picks out all types that start with "java.util." and don't have any more "."s, that is, the types in the java.util package, but not inner types (such as java.util.Map.Entry).
The ".." wildcard matches any sequence of characters that start and end with a ".", so it can be used to pick out all types in any subpackage, or all inner types. So
within(com.xerox..*)
picks out all join points where the code is in any declaration of a type whose name begins with "com.xerox.".
Type patterns with wildcards do not depend on Java's usual scope rules - they match against all types available to the weaver, not just those that are imported into an Aspect's declaring file.
It is possible to pick out all subtypes of a type (or a collection of types) with the "+" wildcard. The "+" wildcard follows immediately a type name pattern. So, while
call(Foo.new())
picks out all constructor call join points where an instance of exactly type Foo is constructed,
call(Foo+.new())
picks out all constructor call join points where an instance of any subtype of Foo (including Foo itself) is constructed, and the unlikely
call(*Handler+.new())
picks out all constructor call join points where an instance of any subtype of any type whose name ends in "Handler" is constructed.
A type name pattern or subtype pattern can be followed by one or more sets of square brackets to make array type patterns. So Object[] is an array type pattern, and so is com.xerox..*[][], and so is Object+[].
Type patterns are built up out of type name patterns, subtype patterns, and array type patterns, and constructed with boolean operators &&, ||, and !. So
staticinitialization(Foo || Bar)
picks out the static initializer execution join points of either Foo or Bar, and
call((Foo+ && ! Foo).new(..))
picks out the constructor call join points when a subtype of Foo, but not Foo itself, is constructed.
Here is a summary of the pattern syntax used in AspectJ:
MethodPattern = [ModifiersPattern] TypePattern [TypePattern . ] IdPattern (TypePattern | ".." , ... ) [ throws ThrowsPattern ] ConstructorPattern = [ModifiersPattern ] [TypePattern . ] new (TypePattern | ".." , ...) [ throws ThrowsPattern ] FieldPattern = [ModifiersPattern] TypePattern [TypePattern . ] IdPattern ThrowsPattern = [ ! ] TypePattern , ... TypePattern = IdPattern [ + ] [ [] ... ] | ! TypePattern | TypePattern && TypePattern | TypePattern || TypePattern | ( TypePattern ) IdPattern = Sequence of characters, possibly with special * and .. wildcards ModifiersPattern = [ ! ] JavaModifier ...