Introduction

This section describes how to write and debug pointcuts using the usual approach of iteration and decomposition. New users are often stumped when their advice does not match. That means the pointcut doesn’t match; they rewrite the pointcut and it still doesn’t match, with no new information. This can be frustrating if each iteration involves building, deploying, and testing a complex application. Learning to break it down, particularly into parts that can be checked at compile-time, can save a lot of time.

Debugging pointcuts

Go at it top-down and then bottom-up.

Top-down

Top-down, draft significant aspects by first writing the comments to specify responsibilities. Advice responsibility usually takes the form, "When X, do Y". Pointcut responsibility for "When X" often takes the form, "When [join points] [in locations] [are …​]". These []'s often translate to named pointcuts like libraryCalls() && within(Client) && args(Context), which form a semantic bridge to the plain-text meaning in a comment, e.g. // when the client passes only context into the library. This gets you to a point where you can debug the parts of the pointcut independently.

Bottom-up

Bottom-up (to build each part), consider each primitive pointcut designator (PCD), then the composition, and then any implicit constraints:

  1. What kinds of join points should it match? (constructor-call? field-get?)? This translates to using the kinded pointcuts (call(..), get(..), etc.).

  2. Are these restricted to being lexically within something? This translates to using within{code}(..). If this is true, it should always be used, to speed up weaving.

  3. What runtime constraints and context should be true and available at each join point? This translates to this(), target(), args(), cflow{below}() and if(..).

  4. Are there any advice or implementation limitations at issue? This involves knowing the few constraints on AspectJ imposed by Java bytecode as listed in the AspectJ Programming Guide section on Implementation Notes.

It’s much faster to iterate a pointcut at compile-time using declare warning (even better, some errors are identified at parse-time in the latest versions of AJDT). Start with the parts of the pointcut that are staticly-determinable (i.e., they do not involve the runtime PCD’s listed above). If compiles themselves take too long because of all the AspectJ weaving, then try to only include the debugging aspect with the prototype pointcut, and limit the scope using within(..).

Common pointcut mistakes

There are some typical types of mistakes developers make when designing pointcuts. Here are a few examples:

Mistakes in primitive pointcuts

  • this(Foo) && execution(static * *(..)): There is no this in a static context, so this() or target() should not be used in a static context or when targetting a static context (respectively). This happens most often when you want to say things like "all calls to Foo from Bar" and you only pick out calls to instance methods of Foo or you try to pick out calls from static methods of Bar.

  • target(Foo) && call(new(..): This will never match. In constructor-call join points, there is no target because the object has not been created yet.

  • call(* Foo.*(..)): Foo refers to the compile-time type of the invoking reference, not the implementing class. In Java before 1.4, the compile-time type was rendered as the defining type, not the reference type; this was corrected in 1.4 (as shown when using ajc with the -1.4 flag) Most people should use target(Foo) && call(…​).

  • execution(* Foo.bar(..)): An execution join point for Foo is always within Foo, so this won’t pick out any overrides of bar(..). Use target(Foo) && execution(* bar(..)) for instance methods.

  • within(Foo): anonymous types are not known at weave-time to be within the lexically-enclosing type (a limitation of Java bytecode).

Mistakes in composition

  • call(* foo(Bar, Foo)) && args(Foo): This will never match. The parameters in args(..) are position-dependent, so args(Foo) only picks out join points where there is only one argument possible, of type Foo. Use the indeterminate-arguments operator .. as needed, e.g., args(Foo, ..).

  • call(* foo()) && execution(* foo()): This will never match. Each pointcut must be true at each join point matched. For a union of different kinds of join points (here, call or execution), use ||. E.g., to match both method-call and field-get join points, use call(* …​) || get(…​).

Mistakes in implicit advice constraints

  • after () returning (Foo foo) : …​: after advice can bind the returned object or exception thrown. That effectively acts like target(), this(), or args() in restricting when the advice runs based on the runtime type of the bound object, even though it is not explicitly part of the pointcut.

Mistakes in implementation requirements

  • ajc has to control the code for a join point in order to implement the join point. This translates to an implicit within({code under the control of the compiler}) for all join points, with additional caveat for some join points. Take exception handlers, for example: there is no way to be sure from the bytecode where the original handler ends, so ajc can’t implement after advice on handler join points. (Since these are on a per-join-point basis, they should be considered for each corresponding primitive pointcut designator.) Unlike the mistakes with the primitive PCDs above, the compiler will emit an error for these caveats.

  • call(@SuperAnnotation Subclass.meth(): Annotations are not inherited by default, so e.g., if the pointcut specifies an annotation, then subclass implementations of that method will not be matched.