Tuesday, June 9, 2009

AspectJ Through Bytecode - Examining The Woven Class

In the previous post we had a look at the Aspect class. Now let's go through the woven class itself. So we do a javap of the woven Test class and have a look at the output.

The static initializer
In our aspects we refer to the join point object in two places - in ExceptionTracer.aj and in FactoryIntercept.aj. Corresponding to these references, there are two private static fields of type org.aspectj.lang.JoinPoint$StaticPart injected in to the class.
private static final org.aspectj.lang.JoinPoint$StaticPart ajc$tjp_0;
private static final org.aspectj.lang.JoinPoint$StaticPart ajc$tjp_1;
These fields are initialized in the static initializer of the class, which calls helper methods in aspectj to construct the JoinPoint$StaticPart objects.

Code injection
  • Public methods and variable names are retained after being injected. So they can be accessed later using reflection.
  • The injected methods are just wrappers that call the actual body - a static method in the aspect class.
    public int getCalls();
    Code:
    0: aload_0
    1: invokestatic #110; //Method ajtest/aspects/AroundAndInject.ajc$interMethod$ajtest_aspects_AroundAndInject$ajtest_java_Test$getCalls:(Lajtest/java/Test;)I
    4: ireturn

    public void incCalls();
    Code:
    0: aload_0
    1: invokestatic #102; //Method ajtest/aspects/AroundAndInject.ajc$interMethod$ajtest_aspects_AroundAndInject$ajtest_java_Test$incCalls:(Lajtest/java/Test;)V
    4: return
  • Private injected variables are declared as public, but with an obfuscated name. So they can not be accessed with their original names through reflection. Why public? Because we have an aspect on the field access join point and the aspect needs to access this field from within the aspect code!
    public int ajc$interField$ajtest_aspects_AroundAndInject$nCalls;
Advices 'around' a field access
We test the FieldAccess aspect around the join points involving get of fld1 in our testFieldAccessAspect method. Look at the source code and you can see that we read fld1 thrice in the testFieldAccessAspect method - once to print it, then to increment it by 1 and then again to print it. Now take a look at the modified bytecode of the woven testFieldAccessAspect method in the javap output.
  • At the first instance where we read the field, instead of directly fetching the field, now there is a call to the around advice, a method fld1_aroundBody1$advice, to get the value.
    invokestatic    #144; //Method fld1_aroundBody1$advice:(Lajtest/aspects/FieldAccess;Lorg/aspectj/runtime/internal/AroundClosure;)I
  • The advice method, in turn, invokes another method "private static final int fld1_aroundBody0()" when it needs to access the field value. This method accesses the field directly through a getstatic instruction.

  • At the second and third instances where we read the field again, the same happens, but to a different set of methods.
    invokestatic    #150; //Method fld1_aroundBody3$advice:(Lajtest/aspects/FieldAccess;Lorg/aspectj/runtime/internal/AroundClosure;)I
    invokestatic #152; //Method fld1_aroundBody2:()I

    and

    invokestatic #156; //Method fld1_aroundBody5$advice:(Lajtest/aspects/FieldAccess;Lorg/aspectj/runtime/internal/AroundClosure;)I
    invokestatic #158; //Method fld1_aroundBody4:()I
  • The three sets of methods are identical. And they are copies from the FieldAccess aspect class bytecode method ajc$around$ajtest_aspects_FieldAccess$1$32f71218. So the weaver has been picking up the bytecode from the aspect class method and injecting new advice methods into the woven class.

  • The "ajc$around$ajtest_aspects_FieldAccess$1$32f71218proceed" method in the FieldAccess aspect class is however ignored in this case. It would have been used to chain aspects if I had multiple aspects on the same join point.

  • The reason behind multiple identical methods generated for the advice however beats me. If you have any explanations/suggestions, I'll be glad to hear.
Therefore, having an aspect around a field access may look innocent, but may be an excessive overhead in terms of code generation and execution. If you can, consider a different design like having an accessor method and having an advice around the execution of the accessor method.

Advices 'around' a method call
We test the an aspect around a method call in the call to the "doSyso" method in main. The story here is very similar to the behavior above.
  • There are two methods injected into the Test class for each instance of the call to doSyso. The methods injected for the first instance of the call are:
    private static final void doSyso_aroundBody7$advice(ajtest.java.Test, java.lang.String, ajtest.aspects.AroundAndInject, ajtest.java.Test, java.lang.String, org.aspectj.runtime.internal.AroundClosure);

    private static final void doSyso_aroundBody6(ajtest.java.Test, java.lang.String);
  • The method call at the join point is replaced to call the advice doSyso_aroundBody7$advice.

  • The weaver copies code from
    public void ajc$around$ajtest_aspects_AroundAndInject$1$38b5b4f8(ajtest.java.Test, java.lang.String, org.aspectj.runtime.internal.AroundClosure);
    in the AroundAndInject aspect class into
    private static final void doSyso_aroundBody7$advice(ajtest.java.Test, java.lang.String, ajtest.aspects.AroundAndInject, ajtest.java.Test, java.lang.String, org.aspectj.runtime.internal.AroundClosure);
    in the Test class.

  • The advice method in turn calls the second generated method doSyso_aroundBody6 to actually call the method.
    invokespecial   #21; //Method doSyso:(Ljava/lang/String;)V
  • Again, the reason behind multiple identical methods generated for the advice beats me.

Advices 'around' a method execution

We test the an aspect around a method call in the call to the "doSysoExec" method in main. This is also similar to the behavior above, except:
  • The call to doSysoExec method is retained as it is.
  • The body of the doSysoExec method is replaced with a call to an injected advice method:
    invokestatic    #306; //Method doSysoExec_aroundBody13$advice:(Lajtest/java/Test;Ljava/lang/String;Lajtest/aspects/AroundAndInject;Lajtest/java/Test;Ljava/lang/String;Lorg/aspectj/runtime/internal/AroundClosure;)V
  • The injected advice method in turn calls another advice method
    invokestatic    #308; //Method doSysoExec_aroundBody12:(Lajtest/java/Test;Ljava/lang/String;)V
    which actually contains what the original doSysoExec method had
    private static final void doSysoExec_aroundBody12(ajtest.java.Test, java.lang.String);
    Code:
    0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
    3: aload_1
    4: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    7: return
  • Multiple advice methods are not injected for multiple calls to the method - obviously since we are not interested in the 'calls' (which are at multiple places), but in execution (which is in one method).
Therefore, if your advice can do what it needs to do equally well around both the method call and method execution, prefer the execution join point as it is much more efficient in terms of code generation and execution.

Advices 'before' and 'after'
The testBeforeAfterAspect method in the Test class tests the before and after aspects.
  • The advice code is a method in the aspect class.
  • Calls are made to the advice methods before and after the join pont.
    invokevirtual   #176; //Method ajtest/aspects/BeforeAfterIntercept.ajc$before$ajtest_aspects_BeforeAfterIntercept$1$218e91dd:()V

    and

    invokevirtual #179; //Method ajtest/aspects/BeforeAfterIntercept.ajc$after$ajtest_aspects_BeforeAfterIntercept$2$218e91dd:()V
  • Since our aspect was for an "after" join point, it implicitly meant both "after returning" and "after throwing". And the weaver injected an exception handler to do the job.
Therefore, if your advice can do equally well before and after the join point, consider 'before' to avoid any unnecessary exception handling.

We also used a before advice for the exception handler advice and the code generation is similar.

We got some insights into what really happens when AspectJ weaves our aspects into the code. Hopefully, it will help us designing our aspects better. In the next post we'll see what happens under the hood when we use different aspect instantiation models.

No comments: