Welcome!

Java Authors: Liz McMillan, Walter H. Pinson, III, Maureen O'Gara, Yakov Werde, Tony Bishop

Related Topics: Java, IT SOLUTIONS GUIDE

Java: Article

Kicking the Tires on Java 5.0

Building an aspect-oriented framework based on annotations

Ok, we're almost done with the Agent installation. The only thing left is to pass the correct information into the JVM that causes the Agent to get called and the class-file transformer to be instantiated and installed. Here's the command line argument that accomplishes this:

java -cp %CP% -javaagent:%DIST%\j105.jar=MyTransformer Main

It may not be clear as to what is happening here. The "-javaagent: [path to jar]\[jar file]=[String Arg to Agent]" works like this: The jar file that's specified is the jar file that contains YOUR agent (in our case our "Agent" class). Recall that this jar file MUST contain the manifest file that specifies the premain class that is to be called. It was my experience developing this framework that simple mistakes in this setup cause nothing to happen! That is, your agent won't be loaded, your classes won't be transformed and your pre- or post-invocation methods won't be called with your business methods. The source code included with this article contains simple println statements that should tell you what is (or isn't) going on with the framework.

Byte-Code Engineering
Up to this point I haven't introduced anything that isn't JDK straight out of the box, but unfortunately that won't get us where we want to go. So just to recap, we've developed the ability to mark classes that we're interested in transforming (injecting pre- or post-logic). We've created an infrastructure to install a class transformer that will re-engineer our classes at load time based on runtime annotations. Up to this point we've been dealing with stock JDK components, but it's time to take a little diversion.

Recall earlier that we had to install a class that implemented the Java interface "java.lang.instrumentation.ClassFileTransformer." In the case of our little framework, the name of the class that does this is called "MyTransformer." We've already seen pieces of this class in this article. Recall the snippet of code that iterated over a collection of Methods in a class looking for our marker (the BAM annotation). What was omitted in that snippet was the actual mechanics of the class transformation. For stylistic reasons, I'm not going to include the entire class listing (please see the source code available with this article). So for a moment, imagine that we've intercepted a class being loaded, we've determined that it is annotated with our BAM metadata and that we now wish to introduce code that's either executed before or after the method we've determined contains our annotation.

So as per the "ClassFileTransformer" interface, we must implement the following method:


public byte[] transform (ClassLoader cl,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
{
}

In pseudo-code, what this method must do (in our framework) is to extract the Methods from the "classBeing-Redefined" parameter, iterate over them looking for annotations. We've already seen how this works. When we find a class that needs to be redefined (annotated), we must inject the pre- or post-code as directed by the annotation itself. To accomplish this injection, I used the "JavaAssist" toolkit for bytecode engineering. I've found this one of the easiest and most straightforward BCELs (Bytecode Engineering Libraries) to use. The code to do this looks something like this:


CtMethod ctM = cc.getDeclaredMethod(m.getName());
if( getAnnotationProperty("insertionPoint", a.toString()).equals("pre") )
{
StringBuffer injectedCode = new StringBuffer();
injectedCode.append("{");
injectedCode.append("System.out.println(\"initialize Spring...\");\n");
injectedCode.append("
MyTransformer.getInjector(\""+getAnnotationProperty("processBean",a.toString()
)+"\").executeChain(null);\n");
injectedCode.append("}\n");
ctM.insertBefore(injectedCode.toString());
}

JavaAssist makes re-engineering class files trivial. Notice that I simply concatenate together regular Java syntax and let JavaAssist translate it into bytecode and tack it on as a preamble to the method in question. Also, I've defined the method that gets attached in terms of a Spring Bean that the annotation references. This adds a convenient level of abstraction and indirection since the pre- and post-logic is defined in terms of a Spring Bean lookup key as opposed to the fully qualified class name of the code the developer wants to have executed before the business method's execution.

Bringing It All Together
Last, but not least, I've put together some classes that implement what amounts to an interceptor pattern in our annotations-based aspect system. These classes are mainly for convenience and allow us to chain together pre- and post-method interceptors. They also showcase the generic and type-safe collections supported by Java 5.0. The following snippet is the declaration for an abstract base type called "BaseInterceptor":


public abstract class BaseInjector
{
LinkedList <BaseInjector> injectors = new LinkedList ();
public BaseInjector( )
{
System.out.println("addding me");
injectors.add(this);
}

public BaseInjector( BaseInjector bi )
{
this();
injectors.add(bi);
}
public void executeChain(Object [] parameters )
{
for( BaseInjector bi: injectors )
{
bi.execute(parameters);
}
}

public abstract void execute(Object [] parameters);

} // class BaseInjector

Comments (3) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
slopzster 07/13/05 11:30:22 PM EDT

fantastic article, very thought provoking -- kudos. Can you comment on potential performance related issues in instrumented components that have high availability requirements?

Boris 06/29/05 11:45:06 AM EDT

I'm sorry, but I cann't find link to sources
of this cool article on the web pages.

Peter Braswell 06/24/05 01:03:39 PM EDT

This article will survey some of Java 5.0's new features and put them into practice through example. We'll build up a lightweight aspect-oriented system based on annotations to showcase what's new in 5.0. Some of these features you may be familiar with, some you may not. I've attempted to mix the obvious with some of the obscure. We'll examine some of the new hooks that the JVM has exposed for class loading, which makes the once dreadful work of bytecode manipulation during class loading much easier. In the "obvious" column, we'll look at generics and how they enable us to write more robust and sane programs, especially when dealing with collections. Perhaps the most notable aspect of Java 5.0 that we'll examine is the annotation framework. Annotations allow developers to inject metadata into their applications. We'll use this feature to demark classes we want manipulated at load time. To put this all in context, we'll create a lightweight framework that will manipulate classes as they are being loaded to enable logging, security, BAM (business activity monitoring), or any number of other scenarios that have yet to be dreamed up