Setting up ColdSpring AOP and interesting behavior with return types
ColdFusion, Technology
This weekend I had a task in which I needed to add a behavior onto a method in an existing production method in a service object. The new behavior is more or less notifying a 3rd party application whenever a particular method was being called. Considering that it is not really an essential part of the process, it didn't really belong inline as part of that method, nor did I really want to affect existing "sealed" production code. Although I understood that the AOP functionality of ColdSpring was designed for exactly that type of work, I had never actually used it before this task. I put together a little proof-of-concept application that validated what I wanted to do, then applied it to my application.
My goal was to set up an "afterReturningAdvice" object/method - that being a method which will be called at the completion of the target method, which is Member.saveMember() - where I could externalize the new behavior without touching the service itself. While I did get it to perform essentially how I expected, there were a couple of gotchas about this process. One of which makes good sense, the other... not so much.
For starters, my MemberService was defined in ColdSpring like this...
In order to make this change without causing broad sweeping changes throughout the application (in theory!), we are going to create a proxy object that masquerades as the MemberService that will pass requests onto the original MemberService.
As you can see, our MemberService is now actually of type coldspring.aop.framework.ProxyFactoryBean. We have defined its target as a bean named MemberServiceTarget which points to our original MemberService that has been renamed. it now has an "interceptors" property that basically wires in a new bean named RecordUpdatedMemberAfterReturningAdvisor that will serve as an interceptor anytime the "MemberService" is accessed.
So lets take a look at our interceptor definition:
You can see that theRecordUpdatedMemberAfterReturningAdvisor that we declared in our previous snippet is defined here as type coldspring.aop.support.NamedMethodPointcutAdvisor, which is a another built-in type in the ColdSpring framework. We have defined a property "mappedNames" which will list any methods in our target object that we would like this interceptor to act on. In our case we are only wanting to act on the saveMember() method, which you see in the property. The "advice" property tells the interceptor that whenever our saveMember() method is called, that we need to notify our RecordUpdatedMemberAfterReturningAdvice. This is an object at we create that extends the coldspring.aop.AfterReturningAdvice abstract class that is included with the framework. When you use this advice, you must have a concrete implementation of the afterReturning() method, which is were we will actually put our code that notifies the 3rd party application. That method will contain the arguments: returnVal, method, args, and target, which will let you know all about the target method that was just called. Pretty cool huh?
Here is what that RecordUpdatedMemberAfterReturningAdvice looks like:
You can see that our required afterReturning() method is what finally does the work that we were trying to implement. And all of this with no changes to our original application!
Well.... not exactly.
While this is certainly a pretty low impact change to our application, the idea that it would not affect any existing code wasn't the actual reality for a couple of reasons.
My goal was to set up an "afterReturningAdvice" object/method - that being a method which will be called at the completion of the target method, which is Member.saveMember() - where I could externalize the new behavior without touching the service itself. While I did get it to perform essentially how I expected, there were a couple of gotchas about this process. One of which makes good sense, the other... not so much.
For starters, my MemberService was defined in ColdSpring like this...
<bean id="MemberService" class="my.objects.MemberService"> <property name="someService"> <ref bean="SomeService"/> </property> </bean>
In order to make this change without causing broad sweeping changes throughout the application (in theory!), we are going to create a proxy object that masquerades as the MemberService that will pass requests onto the original MemberService.
<bean id="MemberServiceTarget" class="my.objects.MemberService">
<property name="someService">
<ref bean="SomeService"/>
</property>
</bean>
<bean id="MemberService" class="coldspring.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="MemberServiceTarget" />
</property>
<property name="interceptorNames">
<list>
<value>RecordUpdatedMemberAfterReturningAdvisor</value>
</list>
</property>
</bean> As you can see, our MemberService is now actually of type coldspring.aop.framework.ProxyFactoryBean. We have defined its target as a bean named MemberServiceTarget which points to our original MemberService that has been renamed. it now has an "interceptors" property that basically wires in a new bean named RecordUpdatedMemberAfterReturningAdvisor that will serve as an interceptor anytime the "MemberService" is accessed.
So lets take a look at our interceptor definition:
<bean id="RecordUpdatedMemberAfterReturningAdvice" class="my.objects.aop.RecordUpdatedMemberAfterSave">
<property name="someNecessaryService">
<ref bean="SomeNecessaryService" />
</property>
</bean>
<bean id="RecordUpdatedMemberAfterReturningAdvisor" class="coldspring.aop.support.NamedMethodPointcutAdvisor">
<property name="advice">
<ref bean="RecordUpdatedMemberAfterReturningAdvice" />
</property>
<property name="mappedNames">
<value>saveMember</value>
</property>
</bean> You can see that theRecordUpdatedMemberAfterReturningAdvisor that we declared in our previous snippet is defined here as type coldspring.aop.support.NamedMethodPointcutAdvisor, which is a another built-in type in the ColdSpring framework. We have defined a property "mappedNames" which will list any methods in our target object that we would like this interceptor to act on. In our case we are only wanting to act on the saveMember() method, which you see in the
Here is what that
<cfcomponent output="false" name="RecordUpdatedMemberAfterSave" extends="coldspring.aop.AfterReturningAdvice">
<cffunction name="init" access="public" output="false" returntype="RecordUpdatedMemberAfterSave">
<cfreturn this />
</cffunction>
<cffunction name="setSomeNecessaryService" access="public" output="false" returntype="void">
<cfargument name="someNecessaryService" type="_components.drmlabs.cd1.member.updatedmemberqueue.SomeNecessaryService" required="true" />
<cfset variables.someNecessaryService = arguments.someNecessaryService />
</cffunction>
<cffunction name="getSomeNecessaryService" access="private" output="false" returntype="_components.drmlabs.cd1.member.updatedmemberqueue.SomeNecessaryService">
<cfreturn variables.someNecessaryService />
</cffunction>
<cffunction name="afterReturning" access="public" output="false" returntype="void">
<cfargument name="returnVal" type="any" required="false" />
<cfargument name="method" type="coldspring.aop.Method" required="false" />
<cfargument name="args" type="struct" required="false" />
<cfargument name="target" type="any" required="false" />
<!--- notify our 3rd party app --->
<cfset getSomeNecessaryService().goDoTheWork(arguments.args.Member) />
<cfreturn />
</cffunction>
</cfcomponent>You can see that our required afterReturning() method is what finally does the work that we were trying to implement. And all of this with no changes to our original application!
Well.... not exactly.
While this is certainly a pretty low impact change to our application, the idea that it would not affect any existing code wasn't the actual reality for a couple of reasons.
- MemberService is no longer a MemberService! In about eleventy thousand places in my application, I had services that were wired with a dependency of our MemberService. Each of these had a getter/setter that looked something like this:
<cffunction name="setMemberService" access="public" output="false" returntype="void" > <cfargument name="memberService" type="my.objects.MemberService" required="true" /> <cfset variables.memberService = arguments.memberService /> <cfreturn /> </cffunction> <cffunction name="getMemberService" access="public" output="false" returntype="my.objects.MemberService"> <cfreturn variables.memberService /> </cffunction>
With the changes that we have implemented, our MemberService is a completely different animal! Every single one of my getters and setters failed since my MemberService is now actually of type coldspring.aop.framework.ProxyFactoryBean. I altered them all to have type "any" (in case I remove this AOP piece later!) and all works. I suppose it is a worthwhile change, but it would be nicer if this change was truly transparent to my application. - This one is the head scratcher, and as of the time of this blog posting I have no explanation whatsoever.... but a Member isn't always a Member?!? In my MemberService, I have a getMember() method which returns a type my.objects.Member. In my initial testing I had no problems with this at all. However, once I rolled it into production, there was a process that called the MemberService.getMember() method that I had not hit in my initial tests. When it was called I received the following exception:
Message:The value returned from function fnct_673EC50CBDBD5294E06289EE40D44A BE() is not of type Member
Our proxy MemberService creates a dynamic method that forwards requests onto the original target getMember() method. For whatever reason, in this one case, it was unable to validate that the returned object was indeed a Member. I modified my original MemberService to make the returntype of getMember() "any" an all is now working well. If anyone has an explanation for that one I am all ears!





Loading....