Sunday, May 5, 2013

Spring annotation on interface or class implementing the interface??

A returning question with regard to Spring is whether to annotate interfaces or concrete classes with for instance the @Transactional annotation (and similar annotations, e.g. @Cacheable).

In the Spring documentation it states the following:

Spring recommends that you only annotate concrete classes (and methods of 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 works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), then the transaction settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a transactional proxy, which would be decidedly bad.

But what does that really mean? Let me illustrate with a simple example:
public class Main {

    public static void main(String... args) {

        ApplicationContext ctx = new ClassPathXmlApplicationContext("file:src/main/resources/META-INF/spring/app-context.xml");
        WithAnnotation bean = ctx.getBean(WithAnnotation.class);
        bean.someMethod();
    }
}


// I 'll play around with this
// @Transactional
interface WithAnnotation {
    void someMethod();
}

// I 'll play around with this
// @Transactional
@Component
class ConcreteClass implements WithAnnotation {

    public void someMethod() {
        System.err.println("In some method: " + TransactionInterceptor.currentTransactionStatus());
    };
}

Default configuration: proxy-target-class="false"

With a transaction configuration like this (the default):







If we run the program with the @Transactional uncommented on the interface we get:
...
In some method: org.springframework.transaction.support.DefaultTransactionStatus@5c76911d1
...

If we run the program with the @Transactional uncommented on the class (commenting out @Transactional on the interface again) we get:
...
In some method: org.springframework.transaction.support.DefaultTransactionStatus@5c76911d
...

In both cases the code executes in a transactional context as we would expect.

Non default configuration: proxy-target-class="true"

Now let's change the transaction configuration to this (the non default):


If we run the program with the @Transactional uncommented on the interface (commenting out @Transactional on the class again) we get:
...
Exception in thread "main" org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
 at org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus(TransactionAspectSupport.java:110)
...

If we run the program with the @Transactional uncommented on the class (commenting out @Transactional on the interface again) we get:
...
In some method: org.springframework.transaction.support.DefaultTransactionStatus@4869a267
...

What have we learned?

If Spring can't create a JDK proxy (or we force it to create a cglib proxy by setting the proxy-target-class to "true") then the code will not execute in a transactional context (just like the Spring documentation stated).

A warning: if you remove the call to TransactionInterceptor.currentTransactionStatus() the code will run without trowing an exception, but it will not run in a transactional context. Yes, you read it: no exception, no warning and no transactional context even though you have got the @Transactional annotation on the interface. I guess that is what is meant by ...the object will not be wrapped in a transactional proxy, which would be decidedly bad :)