ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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.

  • Type 1: Interface Injection
    This was how most J2EE implementations worked. Your components explicitly conformed to a set of interfaces, with associated configuration metadata, in order to allow the framework to manage them correctly.

  • Type 2: Setter Injection
    External metadata is used to configure how your components can be interacted with. In part one you saw how your Spring component could be configured using a springconfig.xml file using exactly this type of IoC approach.

  • Type 3: Constructor Injection
    Your components are registered with the framework, including the parameters to be used when the components are constructed, and the framework provides instances of the component with all of the specified facilities applied.

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

Pages: 1, 2

Next Pagearrow