Spring ignores CGLib subclasses when detecting @Transactional

Starting with spring version 3.0.4, cglib sub-classes are ignored when the spring code analyzes a class for @Transactional annotations.

Below is the method from AbstractFallbackTransactionAttributeSource class which determines if the transaction attribute is present on the method invocation. If the transaction attribute is present spring wraps the class around a proxy.

private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
		// Don't allow no-public methods as required.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		// Ignore CGLIB subclasses - introspect the actual user class.
               Class<?> userClass = ClassUtils.getUserClass(targetClass);

		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
		Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
		// If we are dealing with method with generic parameters, find the original method.
		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

		// First try is the method in the target class.
		TransactionAttribute txAtt = findTransactionAttribute(specificMethod);
		if (txAtt != null) {
			return txAtt;
		}

		// Second try is the transaction attribute on the target class.
		txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());
		if (txAtt != null) {
			return txAtt;
		}

		if (specificMethod != method) {
			// Fallback is to look at the original method.
			txAtt = findTransactionAttribute(method);
			if (txAtt != null) {
				return txAtt;
			}
			// Last fallback is the class of the original method.
			return findTransactionAttribute(method.getDeclaringClass());
		}
		return null;
	}

I traced this out after I found that creation of the application context for my junit test cases failed with spring 3.0.4 jars but passed when spring 3.0.3 jars were used. My spring context xml file for the junit test cases looked similar to

    <context:component-scan base-package="sample">
        <context:exclude-filter type="regex" expression="sample.SomeService" />
    </context:component-scan>

    <bean id = "entityBasePackages" class = "java.lang.String">
        <constructor-arg value = "sample"/>
    </bean>

The junit testcase wanted to mock the class SomeService which contained @Transactional annotations hence it was excluded from the component scan.

@Service
public class SomeService {

    @Transactional
    public void transactionalMethod() {
        System.out.println("Transactional Method");
        //perform some database operations
    }
}

When the test case was executed with spring 3.0.4 jars it failed with the below error

Caused by: java.lang.IllegalArgumentException: Can not set sample.SomeService field sample.MyService.someService to $Proxy26
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:146)
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:150)
	at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
	at java.lang.reflect.Field.set(Field.java:657)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:503)
	... 43 more

The application context creation failed because spring ignored the cglib subclass which was created by EasyMock for SomeService class. Hence a JDK proxy over a CGlib proxy was created which lead to type cast failure as indicated from the error above.

The resolution to the above failure could be any of the two options below
1. Creating an interface for SomeService and replacing the class reference variable in MyService class. This would make the type cast succeed because the JDK proxy created by spring would now include the new interface in the list of proxy interfaces.

2. Remove the @Transactional annotations from the SomeService class so that spring does not create any proxies.

Advertisements

Posted on June 14, 2011, in spring and tagged , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: