Sunday, December 12, 2010

Aspect-Oriented Programming in JBoss AOP: Method Interception

Aspect-Oriented Programming is a modern design paradigm that focuses on the separation of "unrelated concerns" from the implementation of a piece of functionality.  AOP is especially useful in Domain Driven Design because it allows engineers to focus on business logic without needing replicate tedious logging, security, and persistence related code (not that these are not important). 

My goal for this post is not to describe the merits or technical rationale of AOP.  If you do not understand the principles of AOP, I suggest search for the topic in Google or evaluating a number of the key AOP frameworks:

Java
  • AspectJ (utilized by the Spring Framework)
  • JBoss AOP
There are many others for Java, but these are the two dominant (in my opinion).

.NET
  • Spring.NET (similar to AspectJ)
  • Enterprise Library 3.0:  Policy Injection Application Block
  • PostSharp
  • Aspect#
  • LOOM.NET
There are also a number of books on the subject, but I think you will find the AspectJ in Action title from Manning excellent (don't worry about the framework, the principles are identical and API's very similar):  AspectJ in Action (2nd Ed):  http://www.manning.com/laddad2/.

AOP is an important concept because it is used by almost all of the important enterprise frameworks.  You typically find AOP in those frameworks that perform Dependency Injection;  Spring and J2EE serve as perfect examples.  In this demonstration, I'm going to use JBoss AOP.  JBoss AOP is an excellent framework, and is functionally equivalent to the better known AspectJ framework.  My rationale for choosing JBoss AOP over AspectJ is primarily due to JBoss AOP's integration on the JBoss Application Server (no need to add the libraries since they already exist).  This demo, however, will not require the use of a J2EE application server.

In this post, I'm going to demonstrate how to perform method interception using the JBoss AOP framework.  For those who don't know what Method Interception is, AOP frameworks allow engineers to intercept a method before or after it is executed.  Commonly used examples include logging/auditing and transactions.  What if there was a requirement to log the execution of a particular method, recording the time, user and outcome (operation successful?).  We also need to wrap the logic in a transaction, because the method Account.withdraw needs to be rolled back if it fails.

A traditional approach to this requirement might look something like this:

public bool withdrawMoney(Account account, 
     User user, Currency amountToWithdraw){

   if(account.hasSufficientFunds(amountToWithdraw)){
      
      Transaction transaction = GetTransaction();

      try {

        transaction.begin();

        account.withdraw(user, amountToWithdraw);

        logger.info("Customer %s tried successfully "
                + "attempted to withdraw %s from " 
                + "account %s at %s.", 
                account.getId(), user.getFullName(),
                amountToWithdraw.getDollarAmount(),
                (new Date()).toString());

      }  catch (Exception e){

        transaction.rollback();

        logger.info("Customer %s tried unsuccessfully "
                + "attempted to withdraw %s from " 
                + "account %s at %s.  "  
                + "Details: %s.",
                account.getId(), 
                user.getFullName(),
                amountToWithdraw.getDollarAmount(),
                (new Date()).toString()
                e.getMessage());

      } finally {

        transaction.close();
      }
      
      return transaction.wasSuccessful();
   } else {

     logger.info("Customer %s tried unsuccessfully "
                + "attempted to withdraw %s from " 
                + "account %s at %s.  "  
                + "Details: %s.",
                account.getId(), 
                user.getFullName(),
                amountToWithdraw.getDollarAmount(),
                (new Date()).toString()
                "Insufficient Funds.");

      return false;
   }
}

The AOP approach to this method is to remove the "logging" and "transaction" concerns from the business logic (withdrawing money). Let's take a look at the AOP version of withdrawMoney.

public bool withdrawMoney(Account account, 
     User user, Currency amountToWithdraw){

   if(account.hasSufficientFunds(amountToWithdraw)){

        account.withdraw(user, amountToWithdraw);

        return true;
   }

   return false;
}

What!? Clearly this method does not meet the requirement of logging and using a transaction on the Account.withdraw method. AOP allows us to intercept the withdrawMoney method, applying Advice to it before, after, or around its execution. Advice is the formal term for the code (typically a class) responsible for applying functionality onto a method call.

In our AOP example, we would add the following advice to the withdrawMoney method:

import org.jboss.aop.joinpoint.MethodInvocation;

public class LoggingAndTransactionAdvice {

   public Object invoke(MethodInvocation invocation) 
      throws Throwable {  
     
      Object returnValue;

      Transaction transaction = GetTransaction();

      try {

        transaction.begin();

        // Call the original method and collect
        // its return value
        returnValue = invocation.invokeNext();

        LogInfo(invocation.getMethod().getName(),
                 invocation.getArguments());

      }  catch (Exception e){

        transaction.rollback();

        LogError(invocation.getMethod().getName(),
                 e.getMessage(),
                 invocation.getArguments());

      } finally {

        transaction.close();
      }
      
      // return the method's original value
      return returnValue;
   }


   private void LogInfo(String template, 
           Object[] methodParameters){

       //Get the correct template (method name) 
       //and apply the parameters
   }

   private void LogError(String template, 
           String errorMessage, Object[] methodParameters){

       //Get the correct template (method name) 
       //and apply the error message and parameters
   }
 
}

Under the hood, the AOP framework is going to generate proxy objects that wrap your object with another, creating chains of interceptors that each get a chance at applying their functionality to your method call. The previous example is actually not appropriate because we probably would not want to mix transactions and logging in the same Advice. You should probably thinking right now, "how do we proxy the advice onto the method?" That's an excellent question. JBoss AOP allows us to define the methods we want to apply advice to by a number of properties: classes, types, packages, arguments of methods, etc. In my opinion, the best way to apply this advice is through XML configuration, however, you can also do this via Annotation. As a side note, if you use Annotation, this class is commonly called an Aspect, not an Advice because it contains the pointcut description with its Advice. Wait a minute, what is a "pointcut"!?

A pointcut is a place where we need to separate concerns. In the case of the previous example, it was "around" the withdrawMoney method. Sometimes, you may not want to entirely wrap a method. Pointcuts can also be "Before" or "After" method execution, or during "Execeptional" circumstances that occur within a method. In JBoss AOP, we can define Pointcuts in XML:

<?xml version="1.0" encoding="UTF-8"?>
<aop>
   <aspect 
      class=
      "com.berico.aop.LoggingAndTransactionAdvice" 
      scope="PER_VM" />
   <bind 
      pointcut=
      "execution(* com.berico.banking.->*(..))" >
      <around name="invoke" 
      aspect=
      "com.berico.aop.LoggingAndTransactionAdvice" />
   </bind>
</aop>

The pointcut expression, execution(* com.berico.banking.->*(..)) basically says we want to intercept execution on all methods in the com.berico.banking namespace, with any return type (the * at the beginning of the expression, and any argument signature (the two periods ".." in the parenthesis of the expression). This expression syntax gives engineers quite a bit of flexibility in applying AOP against any method or methods in their application.

The last thing that we need to do to get this hypothetical application to run is to apply an environment parameter to the Java run path:
-javaagent:/path/to/jboss-aop-2.1.8.GA/lib/jboss-aop.jar
-Djboss.aop.path=/path/to/pointcuts/jboss-aop.xml

That's it. Now we have AOP integrated into our application. Of course, the previous example was a non-compilable demonstration. Let me show you a real example of AOP, and introduce an interesting feature you may not have thought about regarding AOP.

What if I am working on a system, and I want logging or transaction support on an API I do not have the source for? AOP allows us to apply Advice onto classes in any library, not just the ones we have created. In this next example, I'm going to demonstrate apply Advice onto the JODA Time Library: http://joda-time.sourceforge.net/.

Consider the following application:
package com.berico.aop;

import org.joda.time.Chronology;
import org.joda.time.DateTime;

public class InterceptionDemo {

   public static void main(String... args){
  
      DateTime dt = new DateTime(
          Chronology.getGregorianUTC());

      int year = dt.getYear();

      System.out.println(
         String.format(
            "Welcome to the year %s.", year));
  
  }
 
}

Here is the output from the console:

CALLING METHOD getYear 
  ON org.joda.time.DateTime
CALLING METHOD getYear 
  ON org.joda.time.chrono.GregorianChronology
METHOD HAS RETURNED VALUE: 2010
METHOD HAS RETURNED VALUE: 2010
Welcome to the year 2010.

Obviously, there's a lot more information than was defined in the main method of the InterceptionDemo class.  Let's look at the Advice producing this extra information:

package com.berico.aop;

import org.jboss.aop.joinpoint.MethodInvocation;

public class LoggingAspect {

   public Object invoke(MethodInvocation invocation) 
         throws Throwable {
      
      System.out.println(String.format(
         "CALLING METHOD %s \n ON %s", 
         invocation.getMethod().getName(), 
         invocation.getTargetObject()
                   .getClass().getName()));

      Object returnValue = invocation.invokeNext();

      System.out.println(
         String.format("METHOD HAS RETURNED VALUE: %s",
         (returnValue != null)? 
             returnValue : "No return value"));

      return returnValue;
   }
 
}

And finally, here is our pointcut definition using the JBoss AOP xml format.
<?xml version="1.0" encoding="UTF-8"?>
<aop>
   <aspect 
   class="com.berico.aop.LoggingAspect" 
   scope="PER_VM" />
   <bind 
   pointcut=
     "execution(* org.joda.time.*->getYear(..))" >
   <around 
      name="invoke" 
      aspect="com.berico.aop.LoggingAspect" />
   </bind>
</aop>

*One thing to note is that this piece of advice actually captures two methods from different classes in the Joda Time framework!

AOP offers engineers considerable flexibility, while separating unnecessary concerns from the business logic of their application. AOP can also be used to enhance the functionality of external code without requiring that code to be modified in anyway. JBoss AOP is an incredibly useful framework, and in a subsequent post, I will use it to demonstrate a more powerful capability of AOP called "introductions".

Rich

Resources

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.