Multiple Around Advices Applied to the Same Join Point

Spring and AspectJ

So, what do you think will happen if you apply two around advices to the same join point? They both call “proceedingJoinPoint.proceed()” which calls their target object.

But then if the target object is the same, then it is going to be called twice.. Hmm.. not something you would want to happen especially if that target object is a service that withdraws money from your bank account..

According to the Spring documentation, you may specify the order in which these advices are applied, using Ordered interface, so in case the target object (that service that withdraws money from your account) is called twice, you have an opportunity to specify the order of who calls it first:)

To free the minds of many from unnecessary worries, I’ll show you what really happens when two around advices are applied to the same join point.

Here is this target object (transfer money service):

public class WireTransferMoneyService implements TransferMoneyService {
 
	public void transferMoney( Money money, 
			         Account sourceAccount,
			         Account targetAccount) {
 
		// Start the "Wire Transfer" manager
		// ...
 
		System.err.println( this.getClass().getSimpleName() + " is called" );
 
		sourceAccount.withdraw( money );
		targetAccount.deposit( money );	
	}
}

And here are the two around advices that are applied to this transfer service:

@Around("com.xmen.iii.aspect.SystemArchitecture.businessService()")
public Object doSomethingOnTransfer(ProceedingJoinPoint pjp) throws Throwable {
 
	System.err.println( Thread.currentThread().getStackTrace()[1].getMethodName() +
			" \t\twas called before " + pjp.getTarget().getClass().getSimpleName() );
 
	Object retVal = pjp.proceed();
 
	System.err.println( Thread.currentThread().getStackTrace()[1].getMethodName() +
			" \t\twas called after " + pjp.getTarget().getClass().getSimpleName());
 
	return retVal;
}
 
@Around("com.xmen.iii.aspect.SystemArchitecture.businessService()")
public Object doSomethingElseOnTransfer(ProceedingJoinPoint pjp) throws Throwable {
 
	System.err.println( Thread.currentThread().getStackTrace()[1].getMethodName() +
			" \twas called before " + pjp.getTarget().getClass().getSimpleName() );
 
	Object retVal = pjp.proceed();
 
	System.err.println( Thread.currentThread().getStackTrace()[1].getMethodName() +
			" \twas called after " + pjp.getTarget().getClass().getSimpleName() );
 
	return retVal;
}

Now it’s simple: let’s run it and see what happens… And that is what happens:

2010-02-10 19:52:47,542 INFO [org.springframework.test.context.TestContextManager] - <@TestExecutionListeners is not present for class [class com.xmen.iii.integration.TransferLoggingAspectTest]: using defaults.>
2010-02-10 19:52:47,667 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [com/xmen/iii/integration/TransferLoggingAspectTest-context.xml]>
2010-02-10 19:52:47,808 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/aspect-context.xml]>
2010-02-10 19:52:47,839 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/app-context.xml]>
2010-02-10 19:52:47,917 INFO [org.springframework.context.support.GenericApplicationContext] - <Refreshing org.springframework.context.support.GenericApplicationContext@1820dda: display name [org.springframework.context.support.GenericApplicationContext@1820dda]; startup date [Wed Feb 10 19:52:47 EST 2010]; root of context hierarchy>
2010-02-10 19:52:47,917 INFO [org.springframework.context.support.GenericApplicationContext] - <Bean factory for application context [org.springframework.context.support.GenericApplicationContext@1820dda]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1126b07>
2010-02-10 19:52:48,355 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1126b07: defining beans [transferMoneyService,messageSource,org.springframework.aop.config.internalAutoProxyCreator,com.xmen.iii.aspect.logging.ServiceLoggingAspect#0,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor]; root of factory hierarchy>
"
doSomethingOnTransfer                   was called before WireTransferMoneyService
doSomethingElseOnTransfer               was called before WireTransferMoneyService
WireTransferMoneyService is called
doSomethingElseOnTransfer               was called after WireTransferMoneyService
doSomethingOnTransfer                   was called after WireTransferMoneyService
"
2010-02-10 19:52:48,433 INFO [org.springframework.context.support.GenericApplicationContext] - <Closing org.springframework.context.support.GenericApplicationContext@1820dda: display name [org.springframework.context.support.GenericApplicationContext@1820dda]; startup date [Wed Feb 10 19:52:47 EST 2010]; root of context hierarchy>
2010-02-10 19:52:48,433 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1126b07: defining beans [transferMoneyService,messageSource,org.springframework.aop.config.internalAutoProxyCreator,com.xmen.iii.aspect.logging.ServiceLoggingAspect#0,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor]; root of factory hierarchy>

So, as you can see, AspectJ and Spring are modest but smart, they chain those around advices for you, which is of course niice.

Happy Advising!