When does your Spring @Transactional attribute apply on CgLib proxies

Testing transactional aspect of your application is not easy as we usually use Springs' transaction rollback on tear down testing approach. Though there are solutions to test aspect oriented logic it's not without a price. More than that - we very much got used relying on easy-to-use Spring @Transaction annotation so that we don't usually take an effort to do it. There is a few standard Spring rules for rollbacking transaction in relation to method resolution:

  • transaction is automatically rollbacked only on unchecked exeption (RuntimeException)
  • rollback will also occur for exception types specified in rollback-for attribute of the annotation or XML element
  • rollback will not occur for exception types specified in no-rollback-for attribute of the annotation or XML element
  • transaction demarcation will aplly only to the external calls of the bean method (consider use-case when external logic calls a public method on your bean, that is not annotated with @Transactionaidl and this method will call in its body another public method of the same class that has @Transaction annotation - in such case Spring will not start and manage transaction)

But there is yet another one - your @Transactional annotation must be resolved by Spring bean post processing logic in the first place. Usually it is, but when using CgLib based proxies (proxy-target-class) there is a catch which will cause omitting @Transactional annotation on your method declaration.

Please read carefully following part of Spring documentation:

The Spring team's recommendation is that you only annotate concrete classes with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this will only work as you would expect it to if you are using interface-based proxies. The fact that annotations are not inherited means that if you are using class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj") then the transaction
settings will not be recognised by the proxying/weaving infrastructure and the object will not be wrapped in a transactional proxy (which would be decidedly bad). So please do take the Spring team's advice and only annotate concrete classes (and the methods of concrete classes) with the @Transactional annotation.

You wouldn't probably understand to this paragraph entirely unless you run into the mentioned catch. I've prepared a test suite (download from GitHub) that shows this fact in real code situation. When you run the test suite you'll see that the test ProgramaticFactoryBeanUserManagerIntegrationTest won't pass. In this test we use a object created by our FactoryBean that returns not directly instance of our class but a CgLib derived class extension instance. CgLib creates a new class that extends our class and overrides all methods making them final. Unfortunatelly when overriding methods it doesn't copy their annotations specified in the parent class (there is an long time open issue). Furthermore annotations placed on the methods shouldn't get resolved in the inheritance chain (compared to the annotations placed on classes) as mentioned in Jurgen Hoellers' resolved issue.

Note: As you can see other @Transactional use-cases work well - either @Transactional annotation declared on the class (case of ClassWideAnnotationUserManager class) or @Transactional annotation placed on methods of interface, that is afterwards implemented by our manager (case of InterfaceBasedUserManager class). Both of these cases are resolved ok.

So the Spring transactional infracture in our case considers only methods on the CgLib derived class where annotations are missing. And that is the exact point of this paragraph in the documentation and the main problem of mine as I use this in my codebase a lot. Luckily there is simple undocumented solution to this problem.

Solution for the hopeless

In order to trigger Spring @Transactional annotation resolution we usually use following statement in our Spring XML configuration:


<tx:annotation-driven proxy-target-class="true"/>

In such case standard Spring resolution logic will aplly, which would lead us into the error situation. This decaration could be also rewritten in more explicit way:


<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="proxyTargetClass" value="true"/>
</bean>
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource">
		<bean class="cz.novoj.business.transactionalRelover.CglibOptimizedAnnotationTransactionAttributeSource"/>
	</property>
</bean>

As you see in this configuration we can redefine transactionAttributeSource property of the transactionInterceptor that finally does the annotation resolution. Then we could slightly modify original Spring logic for annotation resolving in following way:


public class CglibOptimizedAnnotationTransactionAttributeSource extends AnnotationTransactionAttributeSource {
	private static final Log log = LogFactory.getLog(CglibOptimizedAnnotationTransactionAttributeSource.class);
	@Override
	protected TransactionAttribute findTransactionAttribute(Method method) {
		Class declaringClass = method.getDeclaringClass();
		if (AopUtils.isCglibProxyClass(declaringClass)) {
			try {
				//find appropriate method on parent class
				Method superMethod = declaringClass.getSuperclass().getMethod(method.getName(), method.getParameterTypes());
				return super.findTransactionAttribute(superMethod);
			} catch (Exception ex) {
				if(log.isWarnEnabled()) {
					log.warn("Cannot find superclass method for Cglib method: " + method.toGenericString());
				}
				return super.findTransactionAttribute(method);
			}
		} else {
			return super.findTransactionAttribute(method);
		}
	}
}

As you can see - in case that we run at CgLib generated proxy we won't search for the annotation on that particular class (as we know that CgLib doesn't copy annotation definitions), but instead on the parent class relevant methods. This way our @Transactional annotation on all methods gets resolved and transactions will start to work.

Note: You can test this solution by changing linked Spring configuration file in AbstractUserManagerTransactionalTest class
from:classpath:spring/spring-transaction.xml
to: classpath:spring/spring-transaction-solution.xml

I've also setup an issue in the Spring Framework issue tracker, that suggests to adopt this solution into the standard Spring codebase. We'll see how the Spring guys will react. Until then you could take advantage of this solution.

Resources