PDA

View Full Version : Custom aspect breaks transaction attribute?


Chris Coleman
05-25-2007, 01:36 PM
Hi,

I have a Service class, which itself contains my Business objects. These business objects then in turn contain the DAO's.

I am using Declarative transactinos by means of placing the [Transaction()] attribute above any methods that require a transaction.

All works great with the transactional aspect being applied fine, untill I try to proxy the Service class using a Spring.Aop.Framework.ProxyFactoryObject to apply some advice around each service method call. When this advice is applied then the transactional aspect fails to be applied.

Would this be the expected behaviour, or am I missing something?

Thanks
Chris

Below are my xml definitions of the objects:



<object id="Service" type="EuroBrats.Service.EBrService, EuroBrats.Service">
<constructor-arg name="systemBO" ref="SystemBO" />
<property name="QueriesBO" ref="QueriesBO" />
<property name="SystemBO" ref="SystemBO" />
</object>

<object id="aroundAdvisor" type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="EuroBrats.Service.Aspects.ServiceCallAroundAdvice, EuroBrats.Service" />
</property>
<property name="MappedNames">
<list>
<value></value>
</list>
</property>
</object>

<!-- With this object commented out in the xml all is well -->
<object id="ServiceAOPProxy" type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="Target" ref="Service" />
<property name="InterceptorNames">
<list>
<value>aroundAdvisor</value>
</list>
</property>
</object>

Chris Coleman
05-25-2007, 03:27 PM
When the transaction attribute is applied then I see this in the logs:

2007-05-25 14:41:29,171 [10] INFO Spring.Aop.Framework.AutoProxy.AbstractAutoProxyCr eator [(null)] - Candidate advisor [Spring.Aop.Support.NameMatchMethodPointcutAdvisor] rejected for type [EuroBrats.Business.QueriesBO]
2007-05-25 14:41:29,171 [10] INFO Spring.Aop.Framework.AutoProxy.AbstractAutoProxyCr eator [(null)] - Candidate advisor [Spring.Transaction.Interceptor.TransactionAttribut eSourceAdvisor] accepted for type [EuroBrats.Business.QueriesBO]
2007-05-25 14:41:29,171 [10] INFO Spring.Aop.Framework.AutoProxy.AbstractAutoProxyCr eator [(null)] - Creating implicit proxy for object 'QueriesBO' with 0 common interceptors and 1 specific interceptors

However when I proxy my Service class to apply my advice then I see this in the logs for all my DAO's, BO's and Service instances:

2007-05-25 14:37:46,781 [10] INFO Spring.Context.Support.AbstractApplicationContext [(null)] - Object 'QueriesBO' is not eligible for being processed by all IObjectPostProcessors (for example: not eligible for auto-proxying).

Mark Pollack
05-25-2007, 04:38 PM
Hi,

Quick question, where are you placing the Transaction attribute, only on the BO objects? Also, why are there no method names listed in the MappedNames property of the around advisor?

Mark

Chris Coleman
05-25-2007, 04:43 PM
Hi,

Yes thats right, on the methods of the BO objects.

Cheers
Chris

Chris Coleman
05-25-2007, 04:48 PM
Also....

As a quick test then I just moved the [Transaction()] attribute to the Service method, which was calling the BO method and the result is still the same.

Chris Coleman
05-25-2007, 05:05 PM
I want all methods to be mapped so I left it blank.

I had origionaly assumed that I should put a <value>*<value/> in it, but this caused issues with interception of other methods from another object, this other object is defined as:


<object id="EBrWCFService" type="EuroBrats.Service.WCFService.EBrWFCService, EuroBrats.Service.WCFService" singleton="false">
<constructor-arg name="service" ref="Service" />
</object>


If I change mapped names to contain <value>EuroBrats.Service.EBrService.*</value> e.g. the full namespace+class+.* then it only intercepts methods from the intended class.

The same is true change ref="Service" to ref="ServiceAOPProxy".

Thanks for the quick replies :)

Mark Pollack
05-25-2007, 05:33 PM
Hi,

If nothing is specified in MappedName then it is equivalent to matching no names, so no advice should be applied. Can you try removing the advisor declaration, create an object definition for ServiceCallAroundAdvice and add the name of the advice object to the 'interceptorNames' list? This will proxy all the interface methods on the Service class.

I can't think of any reason off hand why this wouldn't work. I'll make a little test case of my own.

A side comment for the future, after we sort this out, is that you could declare the service class as an inner definition inside the target property of the proxyFactoryObject, this way only the proxied version is visible to the container.

Mark

Chris Coleman
05-25-2007, 06:19 PM
Hi Mark,

Cheers for taking an interest in this.

I'm afraid that I'm not at work anymore, and don't have access to the code off-site. We also have a national holiday on Monday. However I'll look at this first thing Tuesday morning.

Many thanks
Chris

Mark Pollack
05-25-2007, 06:47 PM
Hi,

I'm attaching an example configuration that demonstrates the functionality you are looking for. It is a small change from what you can find in the Spring.Data.Integration.Tests project.

In this example the TestObjectManager corresponds to your business object - i.e. transaction attributes are applied. The class TestCoordinator corresponds to your service class. I was able to apply some simple around advice to the TestCoordinator just fine and also have the transaction management work. If you could post more of you config file, that may help.

Cheers,
Mark

Chris Coleman
05-25-2007, 07:38 PM
Hi,

I did manage to email the config files to my home account before leaving tonight, so have just attached them as a zip to this message.

From a quick look through them, the key difference I can see is that I have used a NameMatchMethodPointcutAdvisor as an interceptor for the advice whereas in the example you have posted then the you applied the advice directly.

I'll try this out on Tuesday when I have access to the full source and report back.

Thanks again
Chris

Chris Coleman
05-29-2007, 09:39 AM
Hi,

If I create an object definition for the aspect and then apply that directly the advice is applied only to the Service interface, but the [Trasaction()] attribute is not applied to the Business objects.

Using the proxy it intercepts methods from a non proxied class, eg the EBrWCFService definition. As a correction to what I said above then if I leave the MappedNames property of the proxy blank then the advice is not applied at all.

By removing the <import resource="DeclarativeTransactionsAttributeDriven.xml"/> line then my custon aspect is applied correctly, but obviously no Transactional stuff happens.

If however I switch to using the TxProxyConfigurationTemplate method then the transactional aspect is applied just fine, and so is my other advice to the service layer (ServiceCallAroundAdvice). Also using this method then the mapped names property of the NameMatchMethodPointcutAdvisor wrapper works as expected too - leaving it empty applies no advice.

I have attached my latest version of the configs.

Cheers
Chris

Bruno Baia
05-29-2007, 01:24 PM
Hi,

However when I proxy my Service class to apply my advice then I see this in the logs for all my DAO's, BO's and Service instances:

2007-05-25 14:37:46,781 [10] INFO Spring.Context.Support.AbstractApplicationContext [(null)] - Object 'QueriesBO' is not eligible for being processed by all IObjectPostProcessors (for example: not eligible for auto-proxying).
Mark, do you understand why this is happenning ?

Bruno

Bruno Baia
06-26-2007, 02:14 PM
Hi,

First, I fixed 2 issues caused by calls to GetObjectsOfType(Type) during IObjectPostProcessor instantiation :
- Due to the DefaultAdvisorAutoProxyCreator when searching candidate IAdvisor instances.
- Due to the autowiring configuration in DeclarativeServicesAttributeDriven.xml

To get the type of a IFactoryObject (like the ProxyFactoryObject), Spring needs to instantiate it and then call IFactoryObject.ObjectType property.
In your example, the target property references another object in the container, so it needs to create it too, etc... that's why those objects was not intercepted by the transaction interceptor.

Object 'QueriesBO' is not eligible for being processed by all IObjectPostProcessors (for example: not eligible for auto-proxying).

Using TargetName property could resolve your problem, but that's not a fix :

<object id="ServiceAOPProxy" type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="TargetName" value="Service" />
...


AutoProxyCreators now uses method ObjectFactoryUtils.ObjectNamesForTypeIncludingAnce stors(IListableObjectFactory factory, Type type, bool includePrototypes, bool includeFactoryObjects) with includeFactoryObjects = false. (Inline with Spring Java).


Then, appears another problem we are talking internally :

<tx:attribute-driven/> or the equivalent DeclarativeServicesAttributeDriven.xml file is using DefaultAdvisorAutoProxyCreator.
DefaultAdvisorAutoProxyCreator applies every IAdvisor to every objects in the Spring container.

I see 2 issues with that :
- A performance issue : The transactionInterceptor will check every objects's methods, so if we have some windows forms or web pages defined in the Spring container this will decrease performance. Nearly 500 properties/methods to check in the Form type is an example.
- If there is another IAdvisor defined, it will be applied to every objects. (Ex: An HibernateTemplate proxied will throw an error at execution.)

It seems the second issue does not exist in Java, so Mark is investigating.


I see 2 solutions to make your code work :

*
Using DeclarativeServicesObjectNameDriven.xml instead and change the ObjectNameAutoProxyCreator configuration to this :

<object name="autoProxyCreator" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxy Creator, Spring.Aop">
<property name="InterceptorNames" value="transactionInterceptor"/>
<property name="ObjectNames">
<list>
<value>*DAO</value>
<value>*BO</value>
</list>
</property>
</object>



*
Move AOP configuration into one unique file Aspects.xml (no need for extra .xml file) and use the new <tx:advice/> element :

<!-- logging advice -->
<object id="ServiceCallAroundAdvice" type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="EuroBrats.Service.Aspects.ServiceCallAroundAdvice, EuroBrats.Service" />
</property>
<property name="MappedNames">
<list>
<value>*</value>
</list>
</property>
</object>

<!-- transaction advice -->
<!-- HibernateTransactionManager defined in DAO.xml but you can move it here -->
<tx:advice transaction-manager="HibernateTransactionManager"/>

<!-- Apply aspects to my Service layer -->
<object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxy Creator, Spring.Aop">
<property name="ObjectNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="InterceptorNames">
<list>
<value>ServiceCallAroundAdvice</value>
</list>
</property>
</object>

<!-- Apply aspects to my business and DAO layer (you can split it) -->
<object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxy Creator, Spring.Aop">
<property name="ObjectNames">
<list>
<value>*DAO</value>
<value>*BO</value>
</list>
</property>
<property name="InterceptorNames">
<list>
<value>txAdvice</value>
</list>
</property>
</object>



HTH,
Bruno

Mark Pollack
07-25-2007, 02:45 PM
Just a small update, going back to the original post, the correct configuration should have been the following


<object id="Service" type="EuroBrats.Service.EBrService, EuroBrats.Service">
<constructor-arg name="systemBO" ref="SystemBO" />
<property name="QueriesBO" ref="QueriesBO" />
<property name="SystemBO" ref="SystemBO" />
</object>
<object id="aroundAdvisor" type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="EuroBrats.Service.Aspects.ServiceCallAroundAdvice, EuroBrats.Service" />
</property>
<property name="MappedNames">
<list>
<value>EuroBrats.Service.EBrService.*</value>
</list>
</property>
</object>


This differs from the original configuraiton in that the definition
of <object id="ServiceAOPProxy" type="Spring.Aop.Framework.ProxyFactoryObject"> is removed and a value is assigned to the MappedNames property. The additional ProxyFactoryObject definition is not needed to create the service proxy because of the presence of the DefaultAdvisorAutoProxyCreator. The autoproxy mechanism will look at all Advisors defined in the context and any advisor whose pointcut matches methods on an object in the container, in this case the aroundAdvisor defined will match your service object.

I've also fixed the second issue that Bruno mentioned, see AopConfiguraiton.cs in Spring.Data.NHibernate.Tests.

Cheers,
Mark