Advice

Each piece of advice is of the form

[ strictfp ] AdviceSpec [ throws TypeList ] : Pointcut { Body }
where AdviceSpec is one of

and where Formal refers to a variable binding like those used for method parameters, of the form Type Variable-Name, and Formals refers to a comma-delimited list of Formal.

Advice defines crosscutting behavior. It is defined in terms of pointcuts. The code of a piece of advice runs at every join point picked out by its pointcut. Exactly how the code runs depends on the kind of advice.

AspectJ supports three kinds of advice. The kind of advice determines how it interacts with the join points it is defined over. Thus AspectJ divides advice into that which runs before its join points, that which runs after its join points, and that which runs in place of (or "around") its join points.

While before advice is relatively unproblematic, there can be three interpretations of after advice: After the execution of a join point completes normally, after it throws an exception, or after it does either one. AspectJ allows after advice for any of these situations.

  aspect A {
      pointcut publicCall(): call(public Object *(..));
      after() returning (Object o): publicCall() {
	  System.out.println("Returned normally with " + o);
      }
      after() throwing (Exception e): publicCall() {
	  System.out.println("Threw an exception: " + e);
      }
      after(): publicCall(){
	  System.out.println("Returned or threw an Exception");
      }
  }

After returning advice may not care about its returned object, in which case it may be written

  after() returning: call(public Object *(..)) {
      System.out.println("Returned normally");
  }

If after returning does expose its returned object, then the type of the parameter is considered to be an instanceof-like constraint on the advice: it will run only when the return value is of the appropriate type.

A value is of the appropriate type if it would be assignable to a variable of that type, in the Java sense. That is, a byte value is assignable to a short parameter but not vice-versa, an int is assignable to a float parameter, boolean values are only assignable to boolean parameters, and reference types work by instanceof.

There are two special cases: If the exposed value is typed to Object, then the advice is not constrained by that type: the actual return value is converted to an object type for the body of the advice: int values are represented as java.lang.Integer objects, etc, and no value (from void methods, for example) is represented as null.

Secondly, the null value is assignable to a parameter T if the join point could return something of type T.

Around advice runs in place of the join point it operates over, rather than before or after it. Because around is allowed to return a value, it must be declared with a return type, like a method.

Thus, a simple use of around advice is to make a particular method constant:

  aspect A {
      int around(): call(int C.foo()) {
	  return 3;
      }
  }

Within the body of around advice, though, the computation of the original join point can be executed with the special syntax

  proceed( ... )

The proceed form takes as arguments the context exposed by the around's pointcut, and returns whatever the around is declared to return. So the following around advice will double the second argument to foo whenever it is called, and then halve its result:

  aspect A {
      int around(int i): call(int C.foo(Object, int)) && args(i) {
	  int newi = proceed(i*2)
	  return newi/2;
      }
  }

If the return value of around advice is typed to Object, then the result of proceed is converted to an object representation, even if it is originally a primitive value. And when the advice returns an Object value, that value is converted back to whatever representation it was originally. So another way to write the doubling and halving advice is:

  aspect A {
      Object around(int i): call(int C.foo(Object, int)) && args(i) {
	  Integer newi = (Integer) proceed(i*2)
	  return new Integer(newi.intValue() / 2);
      }
  }

Any occurence of proceed(..) within the body of around advice is treated as the special proceed form (even if the aspect defines a method named proceed), unless a target other than the aspect instance is specified as the recipient of the call. For example, in the following program the first call to proceed will be treated as a method call to the ICanProceed instance, whereas the second call to proceed is treated as the special proceed form.

  aspect A {
     Object around(ICanProceed canProceed) : execution(* *(..)) && this(canProceed) {
        canProceed.proceed();         // a method call
        return proceed(canProceed);   // the special proceed form
     }
     
     private Object proceed(ICanProceed canProceed) {
        // this method cannot be called from inside the body of around advice in
        // the aspect
     }
  }	

In all kinds of advice, the parameters of the advice behave exactly like method parameters. In particular, assigning to any parameter affects only the value of the parameter, not the value that it came from. This means that

  aspect A {
      after() returning (int i): call(int C.foo()) {
	  i = i * 2;
      }
  }

will not double the returned value of the advice. Rather, it will double the local parameter. Changing the values of parameters or return values of join points can be done by using around advice.

With proceed(..) it is possible to change the values used by less-precedent advice and the underlying join point by supplying different values for the variables. For example, this aspect replaces the string bound to s in the named pointcut privateData:

  aspect A {
    Object around(String s): MyPointcuts.privateData(s) {
      return proceed("private data");
    }
  }

If you replace an argument to proceed(..), you can cause a ClassCastException at runtime when the argument refers to a supertype of the actual type and you do not supply a reference of the actual type. In the following aspect, the around advice replaces the declared target List with an ArrayList. This is valid code at compile-time since the types match.

  import java.util.*;

  aspect A {
    Object around(List list): call(* List+.*()) && target(list) {
      return proceed(new ArrayList());
    }
  }

But imagine a simple program where the actual target is LinkedList. In this case, the advice would cause a ClassCastException at runtime, and peek() is not declared in ArrayList.

  public class Test {
    public static void main(String[] args) {
      new LinkedList().peek();
    }
  }

The ClassCastException can occur even in situations where it appears to be unnecessary, e.g., if the program is changed to call size(), declared in List:

  public class Test {
    public static void main(String[] args) {
      new LinkedList().size();
    }
  }

There will still be a ClassCastException because it is impossible to prove that there won't be a runtime binary-compatible change in the hierarchy of LinkedList or some other advice on the join point that requires a LinkedList.

Advice modifiers

The strictfp modifier is the only modifier allowed on advice, and it has the effect of making all floating-point expressions within the advice be FP-strict.

Advice and checked exceptions

An advice declaration must include a throws clause listing the checked exceptions the body may throw. This list of checked exceptions must be compatible with each target join point of the advice, or an error is signalled by the compiler.

For example, in the following declarations:

  import java.io.FileNotFoundException;

  class C {
      int i;

      int getI() { return i; }
  }

  aspect A {
      before(): get(int C.i) {
	  throw new FileNotFoundException();
      }
      before() throws FileNotFoundException: get(int C.i) {
	  throw new FileNotFoundException();
      }
  }

both pieces of advice are illegal. The first because the body throws an undeclared checked exception, and the second because field get join points cannot throw FileNotFoundExceptions.

The exceptions that each kind of join point in AspectJ may throw are:

method call and execution
the checked exceptions declared by the target method's throws clause.
constructor call and execution
the checked exceptions declared by the target constructor's throws clause.
field get and set
no checked exceptions can be thrown from these join points.
exception handler execution
the exceptions that can be thrown by the target exception handler.
static initializer execution
no checked exceptions can be thrown from these join points.
pre-initialization and initialization
any exception that is in the throws clause of all constructors of the initialized class.
advice execution
any exception that is in the throws clause of the advice.

Advice precedence

Multiple pieces of advice may apply to the same join point. In such cases, the resolution order of the advice is based on advice precedence.

Determining precedence

There are a number of rules that determine whether a particular piece of advice has precedence over another when they advise the same join point.

If the two pieces of advice are defined in different aspects, then there are three cases:

  • If aspect A is matched earlier than aspect B in some declare precedence form, then all advice in concrete aspect A has precedence over all advice in concrete aspect B when they are on the same join point.
  • Otherwise, if aspect A is a subaspect of aspect B, then all advice defined in A has precedence over all advice defined in B. So, unless otherwise specified with declare precedence, advice in a subaspect has precedence over advice in a superaspect.
  • Otherwise, if two pieces of advice are defined in two different aspects, it is undefined which one has precedence.

If the two pieces of advice are defined in the same aspect, then there are two cases:

  • If either are after advice, then the one that appears later in the aspect has precedence over the one that appears earlier.
  • Otherwise, then the one that appears earlier in the aspect has precedence over the one that appears later.

These rules can lead to circularity, such as

  aspect A {
      before(): execution(void main(String[] args)) {}
      after():  execution(void main(String[] args)) {}
      before(): execution(void main(String[] args)) {}
  }

such circularities will result in errors signalled by the compiler.

Effects of precedence

At a particular join point, advice is ordered by precedence.

A piece of around advice controls whether advice of lower precedence will run by calling proceed. The call to proceed will run the advice with next precedence, or the computation under the join point if there is no further advice.

A piece of before advice can prevent advice of lower precedence from running by throwing an exception. If it returns normally, however, then the advice of the next precedence, or the computation under the join pint if there is no further advice, will run.

Running after returning advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation returned normally, the body of the advice will run.

Running after throwing advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation threw an exception of an appropriate type, the body of the advice will run.

Running after advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then the body of the advice will run.

Reflective access to the join point

Three special variables are visible within bodies of advice and within if() pointcut expressions: thisJoinPoint, thisJoinPointStaticPart, and thisEnclosingJoinPointStaticPart. Each is bound to an object that encapsulates some of the context of the advice's current or enclosing join point. These variables exist because some pointcuts may pick out very large collections of join points. For example, the pointcut

  pointcut publicCall(): call(public * *(..));

picks out calls to many methods. Yet the body of advice over this pointcut may wish to have access to the method name or parameters of a particular join point.

thisJoinPoint is bound to a complete join point object.

thisJoinPointStaticPart is bound to a part of the join point object that includes less information, but for which no memory allocation is required on each execution of the advice. It is equivalent to thisJoinPoint.getStaticPart().

thisEnclosingJoinPointStaticPart is bound to the static part of the join point enclosing the current join point. Only the static part of this enclosing join point is available through this mechanism.

Standard Java reflection uses objects from the java.lang.reflect hierarchy to build up its reflective objects. Similarly, AspectJ join point objects have types in a type hierarchy. The type of objects bound to thisJoinPoint is org.aspectj.lang.JoinPoint, while thisStaticJoinPoint is bound to objects of interface type org.aspectj.lang.JoinPoint.StaticPart.