Chapter 3. Introduction to JBossAop

In this chapter, we will give an overview to JBossAop, specifically to the usage pertinent to PojoCache. Material in this chapter can be found in the full JBossAop documentation. For more details, users are encouraged to visit its doc page.

3.1. What is it?

Aspect-Oriented Programming (AOP) is a new paradigm that allows you to organize and layer your software applications in ways that are impossible with traditional object-oriented approaches. Aspects allow you to transparently glue functionality together so that you can have a more layered design. AOP allows you to intercept any event in a Java program and trigger functionality based on those events. Combined with JDK 5.0 annotations, it allows you to extend the Java language with new syntax.

An aspect is a common feature that's typically scattered across methods, classes, object hierarchies, or even entire object models. It is behavior that looks and smells like it should have structure, but you can't find a way to express this structure in code with traditional object-oriented techniques.

For example, metrics is one common aspect that is orthogonal to the business logic. In AOP, a feature like metrics is called a crosscutting concern, as it's a behavior that "cuts" across multiple points in your object models, yet is distinctly different. As a development methodology, AOP recommends that you abstract and encapsulate crosscutting concerns.

For example, let's say you wanted to add code to an application to measure the amount of time it would take to invoke a particular method. In plain Java, the code would look something like the following.

   public class BankAccountDAO
   {
     public void withdraw(double amount)
     {
       long startTime = System.currentTimeMillis();
       try
       {
         // Actual method body...
       }
       finally
       {
         long endTime = System.currentTimeMillis() - startTime;
         System.out.println("withdraw took: " + endTime);
       }
     }
   }

While this code works, it is difficult to turn it on and off and we have a code bloat if we want to obtain the metrics for all our methods. This approach to metrics is very difficult to maintain, expand, and extend, because it's dispersed throughout your entire code base. And this is just a tiny example! In many cases, OOP may not always be the best way to add metrics to a class.

Aspect-oriented programming gives you a way to encapsulate this type of behavior functionality. It allows you to add behavior such as metrics "around" your code. For example, AOP provides you with programmatic control to specify that you want calls to BankAccountDAO to go through a metrics aspect before executing the actual body of that code.

3.2. Creating Aspects in JBoss AOP

In short, all AOP frameworks define two things: a way to implement crosscutting concerns, and a programmatic construct -- a programming language or a set of tags -- to specify how you want to apply those snippets of code.

Let's take a look at how JBoss AOP, its cross-cutting concerns, and how you can implement a metrics aspect in JBoss.

The first step in creating a metrics aspect in JBoss AOP is to encapsulate the metrics feature in its own Java class. Listing Two extracts the try/finally block in Listing One's BankAccountDAO.withdraw() method into Metrics, an implementation of a JBoss AOP Interceptor class.

Listing Two: Implementing metrics in a JBoss AOP Interceptor

01. public class Metrics implements org.jboss.aop.advice.Interceptor
02. {
03.   public Object invoke(Invocation invocation) throws Throwable
04.   {
05.     long startTime = System.currentTimeMillis();
06.     try
07.     {
08.       return invocation.invokeNext();
09.     }
10.     finally
11.     {
12.       long endTime = System.currentTimeMillis() - startTime;
13.       java.lang.reflect.Method m = ((MethodInvocation)invocation).method;
14.       System.out.println("method " + m.toString() + " time: " + endTime + "ms");
15.     }
16.   }
17. }

Under JBoss AOP, the Metrics class wraps withdraw(): when calling code invokes withdraw(), the AOP framework breaks the method call into its parts and encapsulates those parts into an Invocation object. The framework then calls any aspects that sit between the calling code and the actual method body.

When the AOP framework is done dissecting the method call, it calls Metric's invoke method at line 3. Line 8 wraps and delegates to the actual method and uses an enclosing try/finally block to perform the timings. Line 13 obtains contextual information about the method call from the Invocation object, while line 14 displays the method name and the calculated metrics.

Having the metrics code within its own object allows us to easily expand and capture additional measurements later on. Now that metrics are encapsulated into an aspect, let's see how to apply it.

3.3. Applying Aspects in JBoss AOP

To apply an aspect, you define when to execute the aspect code. Those points in execution are called pointcuts. An analogy to a pointcut is a regular expression. Where a regular expression matches strings, a pointcut expression matches events/points within your application. For example, a valid pointcut definition would be "for all calls to the JDBC method executeQuery(), call the aspect that verifies SQL syntax."

An entry point could be a field access, or a method or constructor call. An event could be an exception being thrown. Some AOP implementations use languages akin to queries to specify pointcuts. Others use tags. JBoss AOP uses both. Listing Three shows how to define a pointcut for the metrics example.

Listing Three: Defining a pointcut in JBoss AOP

1. <bind pointcut="public void com.mc.BankAccountDAO->withdraw(double amount)">
2.       <interceptor class="com.mc.Metrics"/>
3. </bind >

4. <bind pointcut="* com.mc.billing.*->*(..)">
5.       <interceptor class="com.mc.Metrics"/>
6. </bind >

Lines 1-3 define a pointcut that applies the metrics aspect to the specific method BankAccountDAO.withdraw(). Lines 4-6 define a general pointcut that applies the metrics aspect to all methods in all classes in the com.mc.billing package.

There is also an optional annotation mapping if you do not like XML. See JBossAop Reference Guide for more information.

JBoss AOP has a rich set of pointcut expressions that you can use to define various points/events in your Java application so that you can apply your aspects. You can attach your aspects to a specific Java class in your application or you can use more complex compositional pointcuts to specify a wide range of classes within one expression.

3.4. Dynamic Aop

With JBoss AOP you can change advice and interceptor bindings at runtime. You can unregister existing bindings, and hot deploy new bindings if the given points have been instrumented. There is also a runtime API for adding advice bindings at runtime. Getting an instance of org.jboss.aop.AspectManager.instance(), you can add your binding.

AdviceBinding binding = new AdviceBinding("execution(POJO->new(..))", null);
binding.addInterceptor(SimpleInterceptor.class);
AspectManager.instance().addBinding(binding);

First, you allocated an AdviceBinding passing in a pointcut expression. Then you add the interceptor via its class and then add the binding through the AspectManager. When the binding is added the AspectManager will iterate through ever loaded class to see if the pointcut expression matches any of the joinpoints within those classes.

3.4.1. Per Instance AOP

Any class that is instrumented by JBoss AOP, is forced to implement the org.jboss.aop.Advised interface.

public interface InstanceAdvised
{
   public InstanceAdvisor _getInstanceAdvisor();
   public void _setInstanceAdvisor(InstanceAdvisor newAdvisor);
}

public interface Advised extends InstanceAdvised
{
   public Advisor _getAdvisor();
}

The InstanceAdvisor is the interesting interface here. InstanceAdvisor allows you to insert Interceptors at the beginning or the end of the class's advice chain.

public interface InstanceAdvisor
{
   public void insertInterceptor(Interceptor interceptor);
   public void removeInterceptor(String name);
   public void appendInterceptor(Interceptor interceptor);

   public void insertInterceptorStack(String stackName);
   public void removeInterceptorStack(String name);
   public void appendInterceptorStack(String stackName);

   public SimpleMetaData getMetaData();

}

So, there are three advice chains that get executed consecutively in the same java call stack. Those interceptors that are added with the insertInterceptor() method for the given object instance are executed first. Next, those advices/interceptors that were bound using regular binds . Finally, those interceptors added with the appendInterceptor() method to the object instance are executed. You can also reference stacks and insert/append full stacks into the pre/post chains.

3.4.2. Preparation

Dynamic AOP cannot be used unless the particular joinpoint has been instrumented. You can force instrumentation with the prepare functionality that declares in an xml file.

3.5. Annotations

Annotations are only available in JDK 5.0, but using our annotation compiler you can acheive similar functionality with JDK 1.4.2 as well.

Annotations must map to an annotation type, in JDK 5.0 they are defined as:

   package com.mypackage;

   public @interface MyAnnotation
   {
      String myString();
      int myInteger();
   }
         

Annotation types for use with the annotation compiler are defined in exactly the same way for JDK 1.4.2, with the important difference that '@interface' is replaced by 'interface'. i.e. the similar annotation type is a normal Java interface:

   package com.mypackage;

   public interface MyAnnotation
   {
      String myString();
      int myInteger();
   }
         

The syntax for using annotations in JDK 1.4.2 is almost exactly the same as JDK 5.0 annotations except for these subtle differences:

  • they are embedded as doclet tags
  • You use a double at sign, i.e. '@@'
  • You MUST have a space after the tag name otherwise you will get a compilation error. (This is the quirkiness of the QDox doclet compiler used to compile the annotations.')
  • You cannot import the annotation type, you must use the fully qualified name of the interface.
  • You cannot specify default values for an annotation's value

This example shows an annotated class in JDK 1.4.2:

   package com.mypackage;

   /**
    * @@com.mypackage.MyAnnotation (myString="class", myInteger=5)
    */
   public class MyClass
   {
      /**
       * @@com.mypackage.MyAnnotation (myString="field", myInteger=4)
       */
      private String myField;

      /**
       * @@com.mypackage.MyAnnotation (myString="constructor", myInteger=3)
       */
      public MyClass()
      {
      }

      /**
       * @@com.mypackage.MyAnnotation (myString="method", myInteger=3)
       */
      public int myMethod()
      {
      }
   }