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
");
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.