Thursday, September 15, 2011

Implementing Aspect Oriented Programming Framework - Java Instrumentation

What is Aspect Oriented Programing: Aspect Orented Programing or AOP is a design pattern or programming practice, in which the code for cross cutting concern is separated out from the main program logic, thus effectively reducing the program complexity. Now what is a cross cutting concern? They are application non-functional requirements that apply to multiple functionalities. A simple example would be logging. Logging is done whenever some predefined things happen (for example a method is invoked). Normally, every method that is invoked would have to contain the code for logging. In AOP, we write the logging code in a separate method may be in a separate class and then configure our AOP framework to do logging whenever a method is invoked. There are so many good tutorials available on AOP, one of which is here. However, in Java SE environment, it certainly requires a change in the method's byte code made by the AOP framework. I will demonstrate how an AOP framework performs load time weaving (changes byte code during runtime).

What is Java Instrumentation: Well instrumentation is modification of java byte code for the purpose of gathering data that are to be utilized by several tools. These data can be used by profilers, loggers etc. However, certain AOP frameworks like AspectJ use the instrumentation framework to implement load time weaving of their advices to the point-cuts.

Java Agent: A java agent is a class with a premain method that gets invoked before the main method is called on the main class. Java agents are loaded using command line option in the java command. The following will give a simple example.



To be able to run this code, you must package com.geekyarticles.instrumentation.SimpleJavaAgent in a jar file. Let's call it agent.jar. The MANIFEST.MF file must contain the line Premain-Class: com.geekyarticles.instrumentation.SimpleJavaAgent. Now you can run this command
java -javaagent:agent.jar com.geekyarticles.instrumentation.SimpleJavaAgentDemo


This will give the following output.
Premain
Number of classes loaded: 360
Hello! its the main method


Note here that the output from the premain method of the agent class comes before the main method's output.

Class File Transformation: Note above that the premain method in the agent receives an Instrumentation. Instrumentation has methods to add ClassFileTransformer objects, the transform methods of which get invoked in the order they were added. So, every transformer can do its own bit of changes every time a class is loaded or reloaded. Class reloading can be done by custom class-loaders and this feature is provided by some Java EE servers for dynamically detecting changes in the program.

For example, here I will create a ClassFileTransformer that enables logging (Now doing only System.out). I will use BCEL to achieve this. If you do not know about BCEL, you can read my earlier articles here. We will have something as below.
try {
    ByteArrayInputStream byteIn=new ByteArrayInputStream(classfileBuffer);
    ClassParser classParser=new ClassParser(byteIn, null);
    
    JavaClass originalClass=classParser.parse();
    
    ClassGen classGen=new ClassGen(originalClass);
    
    ConstantPoolGen cp=classGen.getConstantPool();

    for(Method methodclassGen.getMethods()){
        MethodGen methodGen=new MethodGen(method, classGen.getClassName(), cp);
        InstructionList il=methodGen.getInstructionList();
        
        InstructionList modiIL=new InstructionList();
        modiIL.append(new GETSTATIC(cp.addFieldref("java.lang.System", "out", "Ljava/io/PrintStream;")));
        modiIL.append(new LDC(cp.addString("Calling: "+method)));
        modiIL.append(new INVOKEVIRTUAL(cp.addMethodref("java.io.PrintStream", "println", "(Ljava/lang/String;)V")));
        
        il.insert(modiIL);
        methodGen.setMaxStack();
        methodGen.setMaxLocals();
        classGen.removeMethod(method);
        classGen.addMethod(methodGen.getMethod());
        
        ByteArrayOutputStream out=new ByteArrayOutputStream();
        classGen.update();
        classGen.getJavaClass().dump(out);
        return out.toByteArray();
            
            
    }
        
        
} catch (ClassFormatException e) {            
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} catch(Throwable t){
    t.printStackTrace();
}


This method is pretty straight forward. We pick every method and insert our logging code at the beginning of its code. However, we have to make sure that only classes in the package com.geekyarticles.instrumentation gets this code insertion, and also we want that only methods annotated with our custom annotation @Loggable gets this logging feature (this is something you always have in any AOP).

The Problem with Annotation: Unfortuanately, the current stable version of BCEL (ie 5.2) does not understand annotations. So we have to parse annotation our selves. Annotations are stored as attributes in the method. The attribute name for an annotation visible at runtime is RuntimeVisibleAnnotations. So we need to attach a reader for this kind of custom attribute. First we need to creat a class AnnotationAttribute. Then we need to attach an AttributeReader to Attribute class through a static method. The createAttribute method of this AttributeReader must return an instance of AnnotationAttribute.

Once this is done, we will get an instance of AnnotationAttribute every time we encounter an Annotation. At this moment, our annotation @Loggable does not have any attribute, so we can skip processing attributes for an annotation. In this case, only the first 6 bytes of the attribute data will be useful. The first two bytes form a pointer to the class the annotation is used in (in the constant pool). The second two bytes point to the signature of the annotation in the constant pool. The last two bytes represent the number of attributes (hence you can only have at most 65535 attributes. In our case, this is always zero. The rest of the code is pretty straight forward. I have added comments whenever necessary.



These above classes should be present in the agent jar file. The following demo class can be used as a main class for demonstration.



We are now all set to implement our own AOP framework.

2 comments:

Anonymous said...

Super Super Super.. searched a lot and got all info here.. Thanks.. i have a kutty friend. She will be very happy to see this.. :)

Unknown said...

Thank you for taking the time and sharing this information with us. It was indeed very helpful and insightful while being straight forward and to the point professional translation services

Post a Comment