Sunday, June 7, 2009

AspectJ Through Bytecode - Anatomy of an Aspect Class

We have been using AspectJ in our product for sometime now. I thought it would be interesting to examine what actually the AspectJ compiler and weaver do at the bytecode level. I made a few simple test classes and a few aspects to test out different types of pointcuts and join points, particularly:
  • Field access
  • Exceptions
  • Code injection
  • Before, After and Around constructs
  • Intercepting and completely replacing method calls
You can find the source code of the classes and aspects here.

I compiled the test classes and the aspects into separate jar files and used the compile time weaver to create a woven jar file separately. My intention was to examine the java bytecode before and after being woven to get a better understanding of aspectj code generation. Knowing what happens under the hood helps in creating better designs. Let me take you through what I went through. I have included the javap outputs and compiled classes along with the source code, but you may want to download the source code and compile them once yourself before we start.

Examining the Aspects Themselves
First, lets examine the aspect bytecode. We pick up one of the simplest aspects - the FieldAccess aspect, and do a bytecode disassembling with javap. Here's what we see:

  • It is a public class
    (public class ajtest.aspects.FieldAccess extends java.lang.Object)
  • There is a singleton instance of the aspect stored as ajc$perSingletonInstance and initialized in a static block. So only one instance of the aspect is created when the aspect class loads.

    This is an important learning which the novice tend to overlook. This implies that the aspects must be coded to be thread safe. Otherwise, remember to modify the aspect declaration with a per... (perthis, pertarget, ...) modifier.

  • In case there is an exception during initialization of the aspect, there is a private static Throwable named ajc$initFailureCause declared in the class which is initialized in the static block of the class with the exception.
  • Since the aspect was used 'around' the pointcut, there is a method for around and a corresponding method for proceed which is called from within the around method.

    public int ajc$around$ajtest_aspects_FieldAccess$1$32f71218(org.aspectj.runtime.internal.AroundClosure);
    static int ajc$around$ajtest_aspects_FieldAccess$1$32f71218proceed(org.aspectj.runtime.internal.AroundClosure) throws java.lang.Throwable;

  • The proceed method is static and does not simply access the field. Instead, it calls run method of the AroundClosure object. That is to futher chain any other aspects that may need to be run.

    invokevirtual #67; //Method org/aspectj/runtime/internal/AroundClosure.run:([Ljava/lang/Object;)Ljava/lang/Object;

  • Note the strange naming convention of the methods, ending with $1$32f71218. We will take it up later and cover another interesting fact of the AspectJ weaver.
  • Then there are other generated methods like aspectOf and hasAspect.
The other interesting aspect would be the one that does the code injection. So we disassemble the AroundAndInject aspect class using javap. Apart from the regular artifacts that we saw earlier, here are few new ones in this class:

  • For each injected field, the aspect has initializer, getter and setter methods

    public static void ajc$interFieldInit$ajtest_aspects_AroundAndInject$ajtest_java_Test$nCalls(ajtest.java.Test);
    public static int ajc$interFieldGetDispatch$ajtest_aspects_AroundAndInject$ajtest_java_Test$nCalls(ajtest.java.Test);
    public static void ajc$interFieldSetDispatch$ajtest_aspects_AroundAndInject$ajtest_java_Test$nCalls(ajtest.java.Test, int);

  • For each injected method, the aspect has the code that goes into the method body. What is injected into the class are methods that in turn call these methods in the aspect.

    public static void ajc$interMethod$ajtest_aspects_AroundAndInject$ajtest_java_Test$incCalls(ajtest.java.Test);
    public static int ajc$interMethod$ajtest_aspects_AroundAndInject$ajtest_java_Test$getCalls(ajtest.java.Test);

  • For each injected method, there are local dispatcher methods in the aspect that in turn call the method of the instrumented class.

    public static int ajc$interMethodDispatch1$ajtest_aspects_AroundAndInject$ajtest_java_Test$getCalls(ajtest.java.Test);
    public static void ajc$interMethodDispatch1$ajtest_aspects_AroundAndInject$ajtest_java_Test$incCalls(ajtest.java.Test);

  • The aspect itself used the local dispatch methods to access the injected methods or variables. So calling an injected method from within the aspect goes through the following path:
    dispatcher method in aspect --> injected method in class --> method body in aspect.

All this seems to be big overheads, but are required to handle complex situations like multiple aspects overlapping at a join point and weaving the same code at multiple times with different aspects. So, if you are thinking of using aspects to just increment an integer in a class, think twice; there might be better ways of doing it. Use aspects for incorporating complex concerns, that is what it is meant for.

In the next post we'll go through a few woven classes and see what interesting things we can see there.

No comments: