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."
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
andafterThrowing
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. 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 thefoo()
method on theIBusinessLogic
interface only. This will only select join points on theIBusinessLogic
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.
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.
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
- Source code for this article
- The Spring Java/J2EE Framework
- The Spring AOP Framework
- "Introduction to Aspect-Oriented Programming"
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.
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. 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.
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. 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. 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. 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. 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. 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
- A Spring framework tutorial that shows you how things work under the skin of Spring
- The complete source code to accompany this article
- The Spring Java/J2EE framework
- "Comparing the Inversion of Control and Dependency Injection Design Patterns"
- An explanation of the IoC pattern
- Chapter 5 of the Spring framework documentation, covering the full suite of AOP features in Spring
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