Saturday, May 1, 2010

The easy-small-simple-quick-step-by-step-how-to article on AspectJ you’ve been looking for is right here

by Eduardo Rodrigues

That’s right. Have you ever spent hours of your precious time googling the Web trying to find an easy, small, simple, quick and step-by-step tutorial, article or sample on how to use AspectJ to solve that very simple use case where you only need to add some trace messages when certain methods of a certain library are called (and you don’t have access to its source code)? If your answer is “YES” then this post is exactly what you’ve been looking (maybe even praying) for.

For convenience, readers may download all files mentioned bellow at http://sites.google.com/site/errodrigues/Home/aspectj_sample.zip?attredirects=0&d=1

Step 0: get the latest stable version of AspectJ

AspectJ can be downloaded from http://www.eclipse.org/aspectj/downloads.php. I recommend using the latest stable release, of course. As of now, this would be release 1.6.8.

To install the package, just run “java -jar aspectj-1.6.8.jar”

Step 1: write the aspect code

Well, AspectJ doesn’t really have what I would call a very intuitive syntax so I’ll not try to explain it more than the strictly necessary for this post. A good way to start learning is reading the official documentation at http://www.eclipse.org/aspectj/docs.php.
In my case, I only needed to write 1 single and very simple aspect capable of capturing all calls to 1 particular method in 1 particular class and then log those calls when the method’s argument was equal to a particular value. In my opinion, this “trace calls” use case is the most simple, obvious and probably one of the most used for any AOP (Aspect Oriented Programming) solution. Therefore, this simple example will probably cover the requirements of most of the readers. That’s what I hope, at least. So here is my aspect FULLProjection.aj:

import java.util.logging.Logger;
import java.util.logging.Level;
import com.foo.Projection;

/**
 * Simple trace AspectJ class. In summary, what it does is:
 * Before executing any call to method setProjection() on any instance
 * of class com.foo.Projection, execute method logFullProjection() defined
 * in this aspect.
 *
 * Modifier "privileged" gives this aspect access to all private members
 * of the captured object.
 */
privileged aspect FULLProjection {

    private static final Logger logger = Logger.getLogger(Projection.class.getName());

    /**
     * AspectJ syntax: defining the execution points to be captured at runtime.
     * target(p) sets the target object to be the instance of
     * class Projection which method setProjection() is called on.
     */
     pointcut tracedCall(Projection p):
          call(void Projection.setProjection()) && target(p);

    /**
     * AspectJ syntax: defining what should run right before the
     * pointcut specified above is executed.
     * Argument p will contain the exact instance of class Projection
     * which is being executed at runtime.
     */
     before(Projection p): tracedCall(p) {
          // m_projectionName is a private member of object p.
          // that's why this aspect must be declared as privileged.
          if("FULL".equals(p.m_projectionName))
               logFullProjectionByZimbra(); // call our Java method, which does the trace
     }

    /**
     * Standard Java method to be executed.
     * Just logs the call if it came from any class under mypackage.*
     */
     private void logFullProjection()
     {
          try {
               StringBuilder c = new StringBuilder("FullProjectionCall: ");
               StackTraceElement st[] = Thread.currentThread().getStackTrace();

               for (StackTraceElement e : st)
               {
                    if(e.getClassName().startsWith("mypackage."))
                    {
                         c.append(e.getClassName())
                          .append(":")
                          .append(e.getMethodName())
                          .append(":")
                          .append(e.getLineNumber());
                         logger.log(Level.WARNING, c.toString());
                         break;
                    }
               }
          } catch(Throwable t) {
               return;
          }
     }
}

Most recent versions of AspectJ have 3 distinct modes of performing the necessary instrumentation on needed classes:
  1. instrument the original source code directly (when available) at compile time;
  2. weave the class files after before using or deploying them as a post-compilation step;
  3. weave classes on demand at runtime (a specific agent must be configured when starting the JVM).
I won’t discuss the pros and cons. In my my case, option 1 was discarded because I didn’t have access to the source code and option 3 was not good because I didn’t want to mess with the container’s setup. So I chose option 2.

Step 2: instrument classes with AspectJ

Since I decided to use option 2 above, my compilation process didn’t need to change a bit. What I needed was to add a post-compile step in which the necessary AspectJ instrumentation would be performed on already compiled classes. The right tool to do that is AspectJ’s command-line compiler: ajc.

This was the hardest part for me because I could not find any tutorial or example on the Web that would show me, in a direct and simple way, how to use ajc. So, instead of making the same mistake and start trying to describe and explain the compiler tool and its options, I’ll simply paste the shell script I used in my case:

#!/bin/bash

# AspectJ's install dir
ASPECTJ_HOME=/scratch/aspectj
# Defining the classpath to be used by ajc
MYCLASSPATH=<include here all elements needed by the classes to be instrumented>

$ASPECTJ_HOME/bin/ajc -classpath $MYCLASSPATH -argfile my_aspects.cfg

The trick here was to use file my_aspects.cfg to define all other parameters to be passed to ajc. Here is its content:

-1.5          # Java 1.5 compatibility
-g            # add debug info
-sourceroots  # path containing the aspects to be compiled (FULLProjection.aj in my case)
/scratch/WORK/aspects
-inpath  # path containing the classes to be instrumented (com.foo.Projection inside platform.jar in my case)
/scratch/WORK/jlib/platform.jar
-outjar  # output JAR file (I preferred to have a separate JAR and keep the original)
/scratch/WORK/platform-instrumented.jar

This file must contain only 1 command-line option per line. When an option requires a subsequent value (like –sourceroots), the value must also be declared on a separate line, subsequent to the option line. A complete reference to ajc can be found at http://www.eclipse.org/aspectj/doc/released/devguide/ajc-ref.html.

Notice that, as long as you don’t choose the runtime instrumentation mode, there’s no need to deploy your aspects with the application. They can be completely separate from your source code as well.

Conclusion

AspectJ as well as any other AOP solution can be a very powerful and useful ally to any software developer. Even as an architectural element by itself. Its possible applications go far beyond the simple use case shown in this post. So, if you’re still undecided, give it a try. Don’t be afraid of using it when necessary and, if you have the opportunity, consider including it as an active component since the beginning of your application’s development cycle.

And remember… if possible, share your experiences with the community (in an objective and clear way, please).

Enjoy!

0 comments: