An Asynchronous AOP Advice Example

2009 November 27
tags: AOP · ColdFusion · ColdSpring
by Paul Marcotte
Over the past year, I have dabbled more and more with leveraging AOP in my applications,primarily for logging and caching. Recently, I needed to analyze a system for performance and determined that for some operations, there were several points where third party integrations are managed. These points slowed the request lifecyle dramatically and were targeted to be re-written using cfthread. I love a good refactoring challenge as much as the next guy, but I also love a good abstraction. Thus, the AsynchronousAroundAdvice was born. When would you use an Asynchronous Advice? Any void method where you want to "set it and forget it" is a good candidate for asynchronous invocation. In fact, since the alternative would produce unexpected results, the AsynchronousAroundAdvice is designed to invoke a method asynchronously only if the method returns void. Here's the code. <cfcomponent name="AsynchronousAroundAdvice" extends="coldspring.aop.MethodInterceptor" hint="Applies asychronous method invocation when returntype is void.">

<cfset variables.metadata = structNew()>

<cffunction name="invokeMethod" access="public" returntype="any" output="true">
<cfargument name="mi" type="coldspring.aop.MethodInvocation" required="true" />
<cfset var methodName = arguments.mi.getMethod().getMethodName()>
<cfset var target = arguments.mi.getTarget()>
<cfset var threadName = GenerateSecretKey("DESEDE")>
<cfif getReturnType(methodName,target) IS "void">
<cfthread name="#threadName#" action="run" target="#arguments.mi#">
<cfinvoke component="#target#" method="proceed"></cfinvoke>
</cfthread>
<cfelse>
<cfreturn arguments.mi.proceed()>
</cfif>
</cffunction>

<cffunction name="getReturnType" access="private" output="false" returntype="string" hint="returns the method returntype or any if not found">
<cfargument name="methodName" type="string" required="true">
<cfargument name="target" type="any" required="true">
<cfset var tempArr = []>
<cfset var i = 0>
<cfif not structKeyExists(variables.metadata,arguments.methodName)>
<cfset variables.metadata[arguments.methodName] = "any">
<cfset tempArr = GetMetadata(arguments.target).functions>
<cfloop from="1" to="#ArrayLen(tempArr)#" index="i">
<cfif (tempArr[i].name is arguments.methodName)>
<cfset variables.metadata[arguments.methodName] = tempArr[i].returntype>
</cfif>
</cfloop>
</cfif>
<cfreturn variables.metadata[arguments.methodName]>
</cffunction>

</cfcomponent>
Without going into the internals of ColdSpring AOP, the advice accepts a MethodInvocation instance, which is, for lack of a better description, a proxy to the original target method. By determining the return type for the original method, I can either safely fire the method using cfthread, or return the result. The cfthread tag accepts accepts metadata attributes and takes a deep copy of the arguments passed to that attribute. So the target="#arguments.mi#" part of the cfthread tag above enables the advice to fire completely thread safe. Note that the advice should be applied to a single target since the method returntype are cached by method name. And here's how one would wire up the ColdSpring for a fictitious Target object with a void method named "doSomething". <beans>

<bean id="AsyncAroundAdvice" class="test.AsynchronousAroundAdvice" />

<bean id="TargetAsynchronousAdvisor" class="coldspring.aop.support.NamedMethodPointcutAdvisor">
<property name="advice">
<ref bean="AsyncAroundAdvice" />
</property>
<property name="mappedNames">
<value>doSomething</value>
</property>
</bean>

<bean id="Target" class="coldspring.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="test.Target" />
</property>
<property name="interceptorNames">
<list>
<value>TargetAsynchronousAdvisor</value>
</list>
</property>
</bean>

</beans>