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:
call(MethodPattern
)
MethodPattern
.
execution(MethodPattern
)
MethodPattern
.
get(FieldPattern
)
FieldPattern
.
[Note that references to constant fields (static final
fields bound to a constant string object or primitive value) are not
join points, since Java requires them to be inlined.]
set(FieldPattern
)
FieldPattern
.
[Note that the initializations of constant fields (static
final fields where the initializer is a constant string object or
primitive value) are not join points, since Java requires their
references to be inlined.]
call(ConstructorPattern
)
ConstructorPattern
.
execution(ConstructorPattern
)
ConstructorPattern
.
initialization(ConstructorPattern
)
ConstructorPattern
.
preinitialization(ConstructorPattern
)
ConstructorPattern
.
staticinitialization(TypePattern
)
TypePattern
.
handler(TypePattern
)
TypePattern
.
adviceexecution()
within(TypePattern
)
TypePattern
.
withincode(MethodPattern
)
MethodPattern
.
withincode(ConstructorPattern
)
ConstructorPattern
.
cflow(Pointcut
)
P
picked out by
Pointcut
, including
P
itself.
cflowbelow(Pointcut
)
P
picked out by
Pointcut
, but not
P
itself.
this(Type
or Id
)
this
) is an instance of
Type
, or of the type of the
identifier Id
(which must be bound in the enclosing
advice or pointcut definition).
Will not match any join points from static contexts.
target(Type
or Id
)
Type
, or of the type of the identifier
Id
(which must be bound in the enclosing
advice or pointcut definition).
Will not match any calls, gets, or sets of static members.
args(Type
or Id
, ...)
null
argument is matched iff the static type of the
argument (declared parameter type or field type) is the same as, or a subtype of,
the specified args type.
PointcutId
(TypePattern
or Id
, ...)
PointcutId
.
if(BooleanExpression
)
true
. The boolean expression used
can only access static members, parameters exposed by the enclosing
pointcut or advice, and thisJoinPoint
forms. In
particular, it cannot call non-static methods on the aspect or
use return values or exceptions exposed by after advice.
! Pointcut
Pointcut
.
Pointcut0
&& Pointcut1
Pointcut0
and
Pointcut1
.
Pointcut0
|| Pointcut1
Pointcut0
or
Pointcut1
.
( Pointcut
)
Pointcut
.
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.
call(MethodPattern
)
execution(MethodPattern
)
AspectJ provides two primitive pointcut designators designed to capture field reference and set join points:
get(FieldPattern
)
set(FieldPattern
)
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.
call(ConstructorPattern
)
execution(ConstructorPattern
)
initialization(ConstructorPattern
)
preinitialization(ConstructorPattern
)
AspectJ provides one primitive pointcut designator to pick out static initializer execution join points.
staticinitialization(TypePattern
)
AspectJ provides one primitive pointcut designator to capture execution of exception handlers:
handler(TypePattern
)
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
adviceexecution()
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.
this(Type
or Id
)
target(Type
or Id
)
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.
args(Type
or Id
or "..", ...)
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.
cflow(Pointcut
)
cflowbelow(Pointcut
)
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.
within(TypePattern
)
withincode(MethodPattern
)
withincode(ConstructorPattern
)
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.
if(BooleanExpression
)
The if pointcut picks out join points based on a dynamic property.
its 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
ThrowsClausePatternItem
s, where
ThrowsClausePatternItem
:[ ! ]
TypeNamePattern
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 to
void 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:
int
) match
those primitive types.java.util.HashMap
) match types
in other packages.
HashMap
) match types that are
resolved by Java's normal scope rules. So, for
example, HashMap
might match a
package-level type in the same package or a type that
have been imported with java's
import
form. But it would not match
java.util.HashMap
unless the aspect
were in java.util
or the type had
been imported.
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 ...