ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


An Introduction to Aspect-Oriented Programming with the Spring Framework, Part 2 An Introduction to Aspect-Oriented Programming with the Spring Framework, Part 2

by Russell Miles
10/20/2004

In part one of this series, you were shown how to implement the "HelloWorld"s of aspect orientation: tracing and logging. Using the Aspect-Oriented Programming (AOP) facilities provided by the Spring framework, you were shown how to use before-, after-, and exception-based advice, along with how to use simple regular-expression-based pointcuts. While tracing and logging provided some great examples to get you started, this article takes things a couple of steps further by looking at a new form of advice: around advice.

The around form of advice is a more intrusive and powerful AO concept than those covered in part one. This article describes each of the features of around advice so that you can use it accurately and carefully within your own Spring AOP applications. This article concludes by showing you how around advice can be used to intercept and change the way that features within your applications are interacted with, in order to implement the Cuckoo's Egg aspect-oriented design pattern.

A Quick Recap on Spring AOP, IoC, and Proxies

In part one, you were taken through a quick tour of some of Spring's AOP features without delving too much into the details of how Spring implements AOP. To understand how the Spring framework does its job, especially its AOP facilities, you need first to understand that Spring is a lightweight framework that relies on the Inversion of Control (IoC) design pattern.

Reader's Note: This article does not aim to go into too much detail on the IoC pattern; it merely aims to give you an understanding of how this design pattern influences the Spring AOP implementation. See Resources at the end of this article for a more detailed explanation of the IoC pattern.

The IoC design pattern has been around for some time. One of the most obvious examples is in the J2EE architecture itself. With the advent of enterprise development, and in particular the J2EE platform, applications began to rely on facilities such as bean creation, persistence, messaging, sessions, and transaction management being provided by an external container.

IoC introduces the concept of a framework of components that in turn has many similarities to a J2EE container. The IoC framework separates facilities that your components are dependent upon and, according to Sam Newman's article, provides the "glue for connecting the components."

The control of the facilities upon which your components depend is inverted so that external frameworks can provide the facilities as transparently as possible. The IoC pattern formally recognizes the move from traditional components being responsible for the facilities upon which they depend, to these facilities being configured and provided by a separate framework.

Figure 1 shows some examples of the different component roles that make up the IoC pattern.

Figure 1
Figure 1. Sequence diagram showing when no aspects are applied to the BusinessLogic bean.

The IoC pattern uses three different approaches in order to achieve this decoupling of control of services from your components: Type 1, Type 2, and Type 3.

IoC is proving increasingly popular in component and enterprise development. Some examples of IoC in practice are in traditional J2EE solutions such as JBoss, the Apache Foundation's Avalon project, and the subject of this article, the Spring framework. In fact, the Spring framework is built on the IoC pattern to help it to inject its lightweight facilities into its dependent application's components.

So what does IoC mean for Spring AOP? The IoC characteristics of Spring are one of the motivations behind using the IoC springconfig.xml configuration file in order to apply aspects to your application. The springconfig.xml configuration informs the Spring framework runtime as to the types of facilities your application's components are to be injected with, and so it is natural that the lightweight AOP facilities be applied in the same way. Spring then uses the Proxy pattern to implement the indicated AOP features around your existing classes and beans.

Figure 2 shows how Spring, and its IoC framework, provide AOP facilities using proxy objects, according to the IoC configuration found in the springconfig.xml file.

Click for larger view
Figure 2. The springconfig.xml configuration influencing the Spring framework IoC in order to then supply AOP proxies into one of the sequence diagrams from part one (Click for full-sized image)

Throughout the rest of this series, you will see that the proxy objects are now included on the sequence diagrams. This is just to show that there is no "magic" to Spring AOP, just a good example of object-oriented design patterns in practice.

Back to AOP: Active Aspects Using around Advice

In part one, you saw how tracing and logging can be implemented using Spring AOP. Tracing and logging are both passive aspects, in that their presence in an application does not affect the rest of the application's behavior; the tracing and logging aspects both use the passive before and after forms of advice.

But what if you want to change the normal behavior of your application? What if you actually want to override a method, for instance? To achieve this you need to use the more active around form of advice.

The simple example application from part one contained the IBusinessLogic interface, the BusinessLogic class, and the MainApplication class, as shown below.

public interface IBusinessLogic
{
  public void foo();
}


public class BusinessLogic 
  implements IBusinessLogic
{
  public void foo() 
  {
     System.out.println(
       "Inside BusinessLogic.foo()");
  }
}

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class MainApplication
{
  public static void main(String [] args)
  {
    // Read the configuration file
    ApplicationContext ctx = 
      new FileSystemXmlApplicationContext(
        "springconfig.xml");

    //Instantiate an object
    IBusinessLogic testObject = 
      (IBusinessLogic) ctx.getBean(
        "businesslogicbean");

    // Execute the public 
    // method of the bean
    testObject.foo();
  }
}

To completely override a call to the foo() method on an instance of the BusinessLogic class, you need to create some around advice, as shown in the AroundAdvice class.

import org.aopalliance.intercept.MethodInvocation;
import org.aopalliance.intercept.MethodInterceptor;

public class AroundAdvice 
   implements MethodInterceptor
{
   public Object invoke(
      MethodInvocation invocation)
      throws Throwable
   {
      System.out.println(
         "Hello world! (by " + 
         this.getClass().getName() + 
         ")");

      return null;
   }
}

To be used as around advice in Spring, the AroundAdvice class must implement the MethodInterceptor interface and its single invoke(..) method. The invoke(..) method is called whenever a method to be overridden is intercepted. The last step is to change the Spring runtime configuration contained in the application's springconfig.xml file so that the AroundAdvice is applied to your application.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

   <!-- Bean configuration -->
   <bean id="businesslogicbean"
   class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="proxyInterfaces">
         <value>IBusinessLogic</value>
      </property>
      <property name="target">
         <ref local="beanTarget"/>
      </property>
      <property name="interceptorNames">
         <list>
            <value>theAroundAdvisor</value>
         </list>
         </property>
   </bean>
   <!-- Bean Classes -->
   <bean id="beanTarget"
   class="BusinessLogic"/>

   <!-- Advisor pointcut definition for around advice -->
   <bean id="theAroundAdvisor"
      class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
      <property name="advice">
         <ref local="theAroundAdvice"/>
      </property>
      <property name="pattern">
         <value>.*</value>
      </property>
   </bean>
	
   <!-- Advice classes -->
   <bean id="theAroundAdvice"
      class="AroundAdvice"/>

</beans>

According to this springconfig.xml configuration, theAroundAdvisor intercepts all calls to methods on the BusinessLogic class. Next, theAroundAdvisor is attached to theAroundAdvice to indicate that the advice specified in the AroundAdvice class should be used when a method is intercepted. Now that the correct configuration for the around advice has been specified, the next time the MainApplication class is executed, the BusinessLogic bean's foo() method will be intercepted and overridden as shown in Figure 3.

Figure 3
Figure 3. Using around advice to override the call to the foo() method in the BusinessLogic class

The previous example showed that the foo() method in the BusinessLogic class could be entirely overridden by the invoke(..) method in the AroundAdvice class. The original foo() method is not invoked at all by the invoke(..) method. If you actually want to invoke the foo() method from within the around advice, you can use the proceed() method that is available on the MethodInvocation parameter on the invoke(..) method.

public class AroundAdvice 
   implements MethodInterceptor
{
   public Object invoke(
      MethodInvocation invocation) 
      throws Throwable
   {
      System.out.println(
         "Hello world! (by " + 
         this.getClass().getName() + 
         ")");

      invocation.proceed();

      System.out.println("Goodbye! (by " + 
         this.getClass().getName() + 
         ")");

      return null;
   }
}

Figure 4 shows how the call to proceed() affects the sequence of operations when compared to the original around advice execution shown in Figure 3.

Figure 4
Figure 4. Using proceed() from within the around advice to invoke the original method

What you are doing when you make the call to proceed() is instructing the method that has been intercepted, in this case the foo() method, to run using the information contained within the MethodInvocation object. You can affect this information by calling the other methods available on the MethodInvocation class.

You might want to change the information contained within the MethodInvocation class in order to set new values to the parameters on the intercepted method prior to it being invoked using proceed().

The parameters that were originally passed to the intercepted method can be changed by calling the getArguments() method on the MethodInvocation object and then by setting one of the parameter objects in the returned array.

If the foo() method on the IBusinessClass and BusinessLogic classes was changed to take an integer simple type then you can change the value passed to an intercepted call to foo(int) from within the notify(..) method in the AroundAdvice, as shown below.

public class AroundAdvice 
   implements MethodInterceptor
{
   public Object invoke(
      MethodInvocation invocation) 
      throws Throwable
   {
      System.out.println(
         "Hello world! (by " + 
         this.getClass().getName() + 
         ")");
      
      invocation.getArguments()[0] = new Integer(20);
      
      invocation.proceed();
      
      System.out.println(
         "Goodbye! (by " + 
         this.getClass().getName() + 
         ")");
      
      return null;
   }
}

In this example, the first parameter on the method intercepted is assumed to be an int. The arguments themselves are passed as objects, and so the primitive int type parameter is changed to the new value in the corresponding array by wrapping it in an instance of the Integer class. If you set the parameter to a value that is other than an Integer object, then you will get an IllegalArgumentException thrown at runtime.

You will also notice that the invoke(..) method must contain a return statement, as the method requires a return value. However, the foo() method that is being overridden does not return an object, and so the invoke(..) method can be closed by returning a null. If you do return an object, even though the foo() method does not require one, then this object would be ignored.

If the foo() method did require a return value, then you would need to return either an object of the same class, or a subclass, of the foo() method's original return type. If the foo() method returns a simple type, an integer for instance, then you will need to return an object of the Integer class, which will be automatically unboxed by the AOP proxy when the method is overridden, as shown in Figure 5.

Figure 5
Figure 5. Boxing and auto-unboxing of return values from around advice

Using Spring AOP to Implement the Cuckoo's Egg Design Pattern

Aspect-oriented programming is still a fairly young discipline, especially when compared to its evolutionary parent: object-oriented programming. Design patterns are usually understood to be generic solutions to common problems and, as AO is so young, there are relatively few aspect-oriented design patterns yet discovered.

One pattern of usage that is starting to emerge I am calling here the Cuckoo's Egg design pattern. This pattern has other aliases and object-oriented peers including Mock Object and Mock Testing; even the Proxy pattern has certain similarities.

The Cuckoo's Egg aspect-oriented design pattern can be defined as the transparent and modular replacement of a feature within an application's context. Much as a cuckoo deposits its own egg into another bird's nest surreptitiously, the Cuckoo's Egg design pattern deposits a replacement feature implementation in place of an existing feature, causing as little disturbance as possible.

This replacement can be implemented statically, dynamically, partially, fully, across parts of an object, or across component boundaries. Using AO the feature replacement is achieved transparently without the need for any changes throughout the rest of the application. The replacement feature, which is to take the place of an existing feature within the application, fulfills the cuckoo's-egg role. Figure 6 shows the main components of the Cuckoo's Egg design pattern.

Figure 6
Figure 6. The main roles in the Cuckoo's Egg design pattern

The Cuckoo's Egg design pattern relies on the concept of around advice. You need to have the power of the active and intrusive around advice in order to intercept and effectively replace an existing feature within an application.

For more information on the Cuckoo's Egg design pattern, and an alternative implementation in AspectJ, see the AspectJ Cookbook (O'Reilly), due for release in December 2004.

To implement the Cuckoo's Egg design pattern using Spring AOP, you need to declare an around advice that intercepts all of the calls to the feature that is to be replaced. Unlike hot-swappable target sources, a feature of Spring AOP that will be covered in another article in this series, the explicit use of around advice allows your Cuckoo's Egg implementation to effectively cross object boundaries (and therefore bean boundaries) to deal with an entire feature's replacement, as shown in Figure 7.

Figure 7
Figure 7. A component crossing bean boundaries

The following code shows a simple application with two beans where a feature spans multiple areas of the application. The feature to be replaced can be thought of as encompassing the foo() method on the IBusinessLogic bean and the bar() method on the IBusinessLogic2 bean. The baz() method on the IBusinessLogic2 bean is not part of the feature and so is not included in the replacement.

public interface IBusinessLogic
{
   public void foo();
}

public interface IBusinessLogic2
{
   public void bar();
   
   public void baz();
}

The full source code for this example is available for download in the Resources section at the end of this article.

The ReplacementFeature class takes the role of the cuckoo's egg by providing the replacement implementation that is to be transparently introduced into your application. The ReplacementFeature class implements all of the methods that are to be replaced through its introduction.

public class ReplacementFeature
{
   public void foo()
   {
      System.out.println(
         "Inside ReplacementFeature.foo()");
   }
   
   public void bar()
   {
      System.out.println(
         "Inside ReplacementFeature.bar()");
   }
}

You now need to declare some around advice to intercept calls to the cross-bean feature's methods. The CuckoosEgg class provides some around advice that examines the method that has been intercepted and then passes the appropriate method call to the instance of the ReplacementFeature class.

public class CuckoosEgg implements MethodInterceptor
{
   public ReplacementFeature replacementFeature =
       new ReplacementFeature();
   
   public Object invoke(MethodInvocation invocation)
       throws Throwable
   {
      if (invocation.getMethod().getName().equals("foo"))
      {
         replacementFeature.foo();
      }
      else
      {
         replacementFeature.bar();
      }
      
      return null;
   }
}

The details of the Cuckoo's Egg design, as with so much in the Spring framework, is in the springconfig.xml configuration file. The changes to the springconfig.xml file simply ensure that any calls made to the foo() and bar() methods on the IBusinessLogic and IBusinessLogic2 beans are intercepted and channeled to the CuckoosEgg class's around advice.

   ...
   
   <!--CONFIG-->
   <bean id="businesslogicbean" 
      class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="proxyInterfaces">
         <value>IBusinessLogic</value>
      </property>
      <property name="target">
         <ref local="beanTarget"/>
      </property>
      <property name="interceptorNames">
         <list>
            <value>theCuckoosEggAdvisor</value>
         </list>
      </property>
   </bean>
   
   <bean id="businesslogicbean2" 
      class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="proxyInterfaces">
         <value>IBusinessLogic2</value>
      </property>
      <property name="target">
         <ref local="beanTarget2"/>
      </property>
      <property name="interceptorNames">
         <list>
            <value>theCuckoosEgg2Advisor</value>
         </list>
      </property>
   </bean>

   <!--CLASS-->
   <bean id="beanTarget" class="BusinessLogic"/>
   <bean id="beanTarget2" class="BusinessLogic2"/>
   
   <!--ADVISOR-->
   <bean id="theCuckoosEggAdvisor" 
      class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
      <property name="advice">
         <ref local="theReplacementFeaturePart1Advice"/>
      </property>
      <property name="pattern">
         <value>IBusinessLogic.*</value>
      </property>
   </bean>
   
   <bean id="theCuckoosEgg2Advisor" 
      class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
      <property name="advice">
         <ref local="theReplacementFeaturePart2Advice"/>
      </property>
      <property name="pattern">
         <value>IBusinessLogic2.bar*</value>
      </property>
   </bean>
   
   <!--ADVICE-->
   <bean id="theReplacementFeaturePart1Advice" class="CuckoosEgg"/>
   <bean id="theReplacementFeaturePart2Advice" class="CuckoosEgg"/>
   
   ...

When the example application is run using the amended springconfig.xml, the method calls designated as part of the feature to be replaced are properly intercepted and passed to the ReplacementFeature class.

You can always implement a design pattern in different ways, even within the same implementation environment. Another way of implementing the example shown above would be to implement two separate pieces of advice.

Finally, you need to take care that the lifecycle of the feature that you are replacing using the Cuckoo's Egg design pattern, be it cross-bean or within one class, matches the object lifecycle of the feature it is replacing. In the example above there is no problem, as there is only one instance of the feature being replaced, and the singleton Cuckoo's Egg advice maintains only one replacement feature.

This is a very simple example and it is likely that you will have to deal with a number of feature instances that need to be replaced by separate Cuckoo's Egg instances. In this case, an individual aspect instance needs to be tied to an individual instance of the feature being replaced. The next article in this series will attack this problem when it considers the use of aspect lifecycles.

Conclusion

This article has shown how to carefully use the active around form of advice inside of the Spring framework. The around form of advice is commonly used when implementing the Cuckoo's Egg design pattern, and so you were shown one example of how this aspect-oriented design pattern can be implemented using Spring AOP.

In the third article in this series, you will be shown how to use more of the fundamental AOP concepts available in the Spring framework. These concepts will include controlling aspect lifecycles, affecting the static structure of your application using active aspects based on introduction advice, and gaining even finer control over your aspect weaving using the control flow pointcut.

Resources

Russell Miles is a senior consultant for SpringSource in the UK and contributes to various open source projects while working on books for O'Reilly.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.