无思

本BLOG只用于个人资料收藏,内容如非注明,均为转贴资料,无意侵犯版权,特此声明!

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
by Russell Miles
07/14/2004

This article, part one of this introduction to Aspect-Oriented Programming (AOP) with the Spring framework, covers the basics to get you quickly developing using aspect orientation in Spring. Using tracing and logging aspect examples, the HelloWorlds of aspect orientation, this article shows how to declare pointcuts and advice in order to apply aspects using the unique facilities that the Spring framework provides. Part two of this series will take things further by explaining how to exercise the full range of advice types and pointcuts available in Spring in order to implement more practical aspects and aspect-oriented design patterns. For a more general introduction to AOP, check out Graham O'Regan's ONJava article, "Introduction to Aspect-Oriented Programming."

This is not an article that aims to exercise all of the many great elements that make up the modular J2EE system that is the Spring framework. Rather, we focus on just the AO facilities provided by Spring. Due to the modular approach to Spring's design, it is possible to use just the AOP elements of the framework without worrying too much about the other modules that make up the Spring framework.

What Does Spring Provide, in Terms of AOP?

"The aim is not to provide the most complete AOP implementation (although Spring AOP is quite capable); it is rather to provide a close integration between AOP implementation and Spring IoC to help solve common problems in enterprise applications."
The Spring Framework Reference Documentation

In order to meet this aim, the Spring framework currently supports a selection of AOP concepts, ranging from pointcuts to advice. This article will show you how to use the following AOP concepts as they are implemented in the Spring framework:

  • Advice: How to declare before, afterReturning and afterThrowing advice as beans.
  • Pointcuts: How to declare static pointcut logic to tie everything together in the XML Spring Bean Configuration files.
  • Advisors: The way to associate pointcut definitions with advice beans.

Setting the Scene: An Example Simple Application

"In general, Spring isn't prescriptive. While it makes it easy to apply good practices, it avoids forcing a particular approach."
The Spring Framework Reference Documentation

The first step in trying out the AOP capabilities of the Spring framework is to create a simple Java application. The IBusinessLogic interface and BusinessLogic class provide the simple building blocks for a bean within the Spring framework. Although the interface is not necessarily required by the logic of our simple application, it is a good practice that is promoted by the Spring framework.

public interface IBusinessLogic
{
    public void foo();
}

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

A MainApplication class can be written to exercise the BusinessLogic bean's public methods.

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();
    }
}

There's nothing too worrying in the BusinessLogic class or its associated interface. However, the MainApplication class does something interesting in how it initializes the BusinessLogic object. By using the ctx.getBean("businesslogicbean") call, the MainApplication hands off the loading and management of bean instance of the BusinessLogic class to the Spring framework.

Allowing Spring to control the initialization of the BusinessLogic bean gives the Spring runtime the opportunity to do all of the management tasks required of a J2EE system for the bean before it is handed back to the application. The configuration of the Spring runtime can then decide which tasks and modules are to be applied to the bean. This configuration information is provided by an XML file similar to the one shown below.

<?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>
   </bean>
   <!-- Bean Classes -->
   <bean id="beanTarget"
   class="BusinessLogic"/>

</beans>

This configuration file, saved as springconfig.xml, specifies that a bean is to be loaded whose interface matches IBusinessLogic. The bean is then tied to the implementation class BusinessLogic. This may seem like a lot of effort just to get a simple bean loaded and to call a single method, but remember that this configuration file is an integral piece of the puzzle that enables the Spring framework to transparently apply its components to your application.

Figure 1 shows the basic sequence diagram when the MainApplication is executed as it stands, with no aspects applied.

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

See the references at the end of this article for the source code to this simple Spring application.

Applying a Method Tracing Aspect

Possibly the most basic stalwart of aspects is the Method Tracing aspect. This passive aspect is about as simple an aspect as you will find, and so is the best place to start when investigating a new implementation of AOP.

A Method Tracing aspect captures calls to, and returns from, methods being traced within a target application and displays this information in some way. In AO, the before and after types of advice are used to capture these types of join points as they can be triggered before and after a method call join point. Using the Spring framework, the before advice for the Method Tracing aspect is declared in the TracingBeforeAdvice class.

import java.lang.reflect.Method;
import org.springframework.aop. MethodBeforeAdvice;

public class TracingBeforeAdvice 
   implements MethodBeforeAdvice
{
    public void before(Method m, 
                     Object[] args, 
                     Object target) 
                     throws Throwable
    {
        System.out.println(
          "Hello world! (by " + 
          this.getClass().getName() + 
          ")");
    }
}

Similarly, the after advice can be declared in the TracingAfterAdvice class.

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;

public class TracingAfterAdvice 
   implements AfterReturningAdvice
{
    public void afterReturning(Object object, 
                             Method m, 
                             Object[] args, 
                             Object target) 
                             throws Throwable
    {
        System.out.println(
          "Hello world! (by " + 
          this.getClass().getName() + 
          ")");
    }
}

Both classes represent a specific piece of advice by implementing the appropriate advice interface from the Spring framework. Each type of advice specifies that either the before(..) or afterReturning(..) methods are implemented to enable the Spring runtime to notify the advice when an appropriate join point has been reached. It is worth noting that the TracingAfterAdvice actually extends from AfterReturningAdvice, which means that the advice will only be run if the join point that is encountered returns without an exception.

In order to attach the advice to the appropriate join points in your application you must make a few amendments to the springconfig.xml.

<?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>theTracingBeforeAdvisor</value>
            <value>theTracingAfterAdvisor</value>
         </list>
         </property>
   </bean>
   <!-- Bean Classes -->
   <bean id="beanTarget"
   class="BusinessLogic"/>

   <!-- Advisor pointcut definition for before advice -->
   <bean id="theTracingBeforeAdvisor"
      class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
      <property name="advice">
         <ref local="theTracingBeforeAdvice"/>
      </property>
      <property name="pattern">
         <value>.*</value>
      </property>
   </bean>
	
   <!-- Advisor pointcut definition for after advice -->
   <bean id="theTracingAfterAdvisor"
      class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
      <property name="advice">
         <ref local="theTracingAfterAdvice"/>
      </property>
      <property name="pattern">
         <value>.*</value>
      </property>
   </bean<

   <!-- Advice classes -->
   <bean id="theTracingBeforeAdvice"
      class="TracingBeforeAdvice"/>
   <bean id="theTracingAfterAdvice"
      class="TracingAfterAdvice"/>

</beans>

The theTracingBeforeAdvisor and theTracingAfterAdvisor advisors are added to the previously declared businesslogicbean. Each advisor potentially intercepts all join points on the beans to which they are attached. The advisors are beans themselves, and their sole job is to tie together pointcut definitions and advice beans. The pointcut definitions in this example are regular expressions specifying the join points of interest within the static object hierarchy.

Since the org.springframework.aop.support.RegexpMethodPointcutAdvisor pointcut advisor is being used in this example, the pointcut logic is specified using a regular expression. The regular expression is used to identify the join points on the public interface to the IBusinessLogic interface. Some simple examples of regular expressions that could have been used to specify different collections of join points on the IBusinessLogic interface are:

  • <value>.*</value>: This expression selects all join points on the bean or beans to which the advisor has been attached.

  • <value>./IBusinessLogic/.foo</value>: This expression selects join points on the foo() method on the IBusinessLogic interface only. This will only select join points on the IBusinessLogic interface if it is one of the beans to which the advisor has been attached.

The final bean declarations in the springconfig.xml file specify the classes that implement the advice beans.

Now that the correct configuration for the tracing aspects has been specified, the next time the MainApplication is executed, the aspects will be woven in during the initialization, and all methods within the BusinessLogic bean will be traced, as shown in Figure 2.

Click for larger view
Figure 2. Sequence diagram showing the Method Tracing aspect being applied to the BusinessLogic bean (click for full-size image)

The source code for the Method Tracing aspect and example application is available for download in the references section at the end of this article.

Reusing Aspects

The Method Tracing aspect can be extended to provide a slightly more complex Logging aspect. The Logging aspect provides a good example of reuse, as many of the characteristics needed of a Logging aspect have already been developed in the Method Tracing aspect.



In this example the Logging aspect extends the Method Tracing aspect in order to display additional information concerning exceptions that have been raised during an application's execution.

A few changes are needed to the application in order to fully exercise the Logging aspect. The BusinessLogicException exception class provides an exception that can be raised by the new void bar() method on the IBusinessLogicInterface interface and BusinessLogic implementation class.

public class BusinessLogicException 
   extends Exception
{

}

public interface IBusinessLogic
{
    public void foo();
   
    public void bar() 
      throws BusinessLogicException;
}

public class BusinessLogic 
   implements IBusinessLogic
{
    public void foo() 
    {
        System.out.println(
          "Inside BusinessLogic.foo()");
    }
     
    public void bar() 
       throws BusinessLogicException
    {
        System.out.println(
        "Inside BusinessLogic.bar()");
          throw new BusinessLogicException();
    }
}

The MainApplication class now makes an additional call to the void bar() method and handles the checked exceptions that are potentially raised by that method.

import org.springframeworkcontext.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 methods of the bean
       testObject.foo();
      
       try
       {
           testObject.bar();
       }
       catch(BusinessLogicException ble)
       {
           System.out.println(
             "Caught BusinessLogicException");
       }
    }
}

The TracingBeforeAdvice and TracingAfterAdvice advice from the Method Tracing aspect can be entirely reused. The LoggingThrowsAdvice class provides the advice for the new exception logging.

import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;

public class LoggingThrowsAdvice 
   implements ThrowsAdvice
{
    public void afterThrowing(Method method, 
                            Object[] args, 
                            Object target, 
                            Throwable subclass)
    {
       System.out.println(
         "Logging that a " + 
         subclass + 
         "Exception was thrown.");
    }
}

The final step in applying the Logging aspect is to amend the springconfig.xml configuration to incorporate the additional new LoggingThrowsAdvice advice.

Figure 3 shows the UML sequence diagram for when the MainApplication is run and the Logging aspect has been applied using the Spring framework.

Click for larger view
Figure 3. Sequence diagram once the Logging aspect is applied to the BusinessLogic bean (click for full-size image)

The Logging aspect shown here effectively demonstrates how to reuse parts of existing aspects and how to use the throws form of advice within the Spring framework. More complex logging can be achieved by declaring new advice for the before and after advice to override the existing Method Tracing aspect implementation, in order to log to a more complex logging framework such as LOG4J. For the source code for the Logging aspect and example application see the references section at the end of this article.

Conclusion

This article has shown some simple aspects being applied using the fundamental AOP constructs available from the Spring framework. In the next part of this series, we'll get into some more practical aspects, exploring aspect lifecycles, using the Spring framework's around advice, and apply AOP patterns using Spring.

References

Russell Miles works as a software engineer for General Dynamics UK, where he works with Java and distributed systems, although his passion at the moment is aspect orientation and, in particular, AspectJ


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

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 works as a software engineer for General Dynamics UK, where he works with Java and distributed systems, although his passion at the moment is aspect orientation and, in particular, AspectJ

posted on 2004-10-29 16:22  kavenmo  阅读(898)  评论(0编辑  收藏  举报