Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to crosscut annotated methods and constructors?

This is what I'm doing:

@Aspect
public class MethodLogger {
  @Around("(execution(* *(..)) || initialization(*.new(..))) && @annotation(Foo)")
  public Object wrap(ProceedingJoinPoint point) throws Throwable {
    // works fine, but only for methods
  }
}

The snippet works fine, but only for method calls. This is what AspectJ maven plugin is saying after applying the aspect (not during its compilation, which works just fine):

around on initialization not supported (compiler limitation)

Any workaround? I'm with OpenJDK 7:

java version "1.7.0_05"
Java(TM) SE Runtime Environment (build 1.7.0_05-b06)
Java HotSpot(TM) 64-Bit Server VM (build 23.1-b03, mixed mode)
like image 769
yegor256 Avatar asked Nov 30 '12 09:11

yegor256


1 Answers

Due to technical limitations there is no such thing as around() advice on initialization() or preinitialization() pointcuts. And there is another problem in the chronological order of when the corresponding joinpoints are being entered and exited. Look at this example:

public abstract class ApplicationBase {
    private int id = 0;

    public ApplicationBase(int id) {
        this.id = id;
    }
}
public class Application extends ApplicationBase {
    private String name = "<unnamed>";

    public Application(int id, String name) {
        super(id);
        this.name = name;
    }

    public static void main(String[] args) {
        new Application(1, "Foo");
        new Application(2, "Bar");
    }
}
public aspect ExecutionTimingAspect {
    private String indentText = "";

    pointcut constructorCall() :
        call(*Application*.new(..));

    pointcut constructorRelated() :
        constructorCall() ||
        initialization(*Application*.new(..)) ||
        preinitialization(*Application*.new(..)) ||
        execution(*Application*.new(..));

    after() : constructorRelated() {
        indentText = indentText.substring(2);
        System.out.println(indentText + "<< " + thisJoinPointStaticPart);
    }

    before() : constructorRelated() {
        System.out.println(indentText + ">> " + thisJoinPointStaticPart);
        indentText += "  ";
    }

    Object around() : constructorCall() {
        long startTime = System.nanoTime();
        Object result = proceed();
        System.out.println(indentText + "Constructor runtime = " + (System.nanoTime() - startTime) / 1.0e9 + " s\n");
        return result;
    }
}

You will see the following output:

>> call(Application(int, String))
  >> preinitialization(Application(int, String))
  << preinitialization(Application(int, String))
  >> preinitialization(ApplicationBase(int))
  << preinitialization(ApplicationBase(int))
  >> initialization(ApplicationBase(int))
    >> execution(ApplicationBase(int))
    << execution(ApplicationBase(int))
  << initialization(ApplicationBase(int))
  >> initialization(Application(int, String))
    >> execution(Application(int, String))
    << execution(Application(int, String))
  << initialization(Application(int, String))
<< call(Application(int, String))
Constructor runtime = 0.00123172 s

>> call(Application(int, String))
  >> preinitialization(Application(int, String))
  << preinitialization(Application(int, String))
  >> preinitialization(ApplicationBase(int))
  << preinitialization(ApplicationBase(int))
  >> initialization(ApplicationBase(int))
    >> execution(ApplicationBase(int))
    << execution(ApplicationBase(int))
  << initialization(ApplicationBase(int))
  >> initialization(Application(int, String))
    >> execution(Application(int, String))
    << execution(Application(int, String))
  << initialization(Application(int, String))
<< call(Application(int, String))
Constructor runtime = 0.00103393 s

Can you see how preinitialisation of the derived class starts and finishes before preinitialisation of its base class? And how initialisation works just the other way around, but as an additional complication constructor execution is embedded in initialisation?

Maybe now you understand that just measuring initialisation, even if it was possible via around(), would not reflect the constructor's overall execution time. So if you are lucky enough to be able to intercept constructor call() instead of execution() because you have access to the calling code, you are fine and can even use around() as I did in my example (which is, by the way, not thread-safe, but I tried to keep it simple). If you cannot influence the caller, but can only weave the callee, you need to use other tricks, such as aspect-internal bookkeeping of when preinitialisation of a certain constructor is entered via before() and then when initialisation of the same constructor call is exited via after(). I.e. you need to keep some internal state in between advice executions. This is possible, but a little more complicated. If you want to discuss this further, please let me know.

like image 149
kriegaex Avatar answered Oct 23 '22 04:10

kriegaex