I’ve been playing around with Spring 2.0 declarative transactions to better understand them, with a view to converting all our existing EJB services to POJOs. I’m running into behavior I don’t understand where a service method is intercepted if the pointcut is defined on its interface, but not if it’s defined on its class. In both cases, logs suggest proxies are correctly set up based on interface (documented behavior for a class-based pointcut where CGLIB isn’t used, as is so here). Is it expected behavior for the class-pointcut that a proxy would be set up for the interface but wouldn't kick in for the bean? I'm really trying to get a good handle on this before converting over all our services.
Here’s what I’ve got…
I’ve an extremely basic service and service interface:
For which I’m attempting to check a transaction starts based on the following Spring configuration:Code:public interface FakeService { public void doOperation(); } public class FakeConcreteService implements FakeService { public void doOperation() { } }
I’ve an extremely basic fake PlatformTransactionManager extending AbstractPlatformTransactionManager which counts how many times doBegin is invoked and dummies out all abstract operations:Code:<bean id="serviceBean" class="com.s1.arch.test.FakeConcreteService"/> <tx:advice id="serviceTxAdvice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="allServiceOperations" expression="execution(* com.s1.arch.test.FakeConcreteService.*(..))" /> <aop:advisor advice-ref="serviceTxAdvice" pointcut-ref="allServiceOperations" /> </aop:config> <bean id="transactionManager" class="com.s1.arch.test.FakeSpringTransactionManager"> </bean>
And a JUnit test that pulls this together:Code:public class FakeSpringTransactionManager extends AbstractPlatformTransactionManager { static private int doBeginCalls; static private TransactionDefinition doBeginCallArg; static public int getDoBeginCalls() { return doBeginCalls; } static public TransactionDefinition getDoBeginCallArg() { return doBeginCallArg; } static public void reset() { doBeginCalls = 0; doBeginCallArg = null; } protected Object doGetTransaction() throws TransactionException { return null; } protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { doBeginCalls++; doBeginCallArg = definition; } protected void doCommit(DefaultTransactionStatus status) throws TransactionException { } protected void doRollback(DefaultTransactionStatus status) throws TransactionException { } }
When I run this test, the first assert fails as FakeSpringTransactionManager.getDoBeginCalls() == 0, plus there are no additional transaction initiation logs. If I change my Spring configuration to use an interface-based pointcut, the test succeeds and I can see transaction initialization in the logs:Code:public void testTransactionAdviceApplied() { FakeSpringTransactionManager.reset(); BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config//test-springproducer-upstream.xml"); FakeService bean = (FakeService) beanFactory.getBean("serviceBean"); bean.doOperation(); assertEquals(1, FakeSpringTransactionManager.getDoBeginCalls()); assertEquals(TransactionDefinition.PROPAGATION_REQUIRED, FakeSpringTransactionManager.getDoBeginCallArg() .getPropagationBehavior()); }
The crux seems to be line 79 in AdvisorChainFactoryUtils. calculateInterceptorsAndDynamicInterceptionAdvice, which returns true for the interface-pointcut but false for the class-based pointcut:Code:<aop:pointcut id="allServiceOperations" expression="execution(* com.s1.arch.test.FakeService.*(..))" />
If anybody could explain why this happens this way, that would be brilliant. Thanks!Code:MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); if (mm.matches(method, targetClass)) { … etc. …


Reply With Quote
