18
votes

The Spring documentation recommends against putting @Transactional annotations on interface methods because interface annotations are not inherited by classes. However, with Java 8 we can provide a concrete default implementation in the interface. If such a default interface method needs to be the transactional boundary, we have no other choice: we have to put the @Transactional annotation on the interface method.

Will this work (i.e. will spring respect the transaction boundary in this case)? If so, are there any hidden pitfalls to this approach?

1
If you need a default implementation of a method, why not create an abstract class instead of an interface? The Java 8 API docs lead me to believe that interface default methods are primarily intended to allow the addition of new methods to an interface without breaking older implementations of that interface. - VGR
Interface default methods allow you to create mixins, which are a much more powerful programming construct than traditional java interfaces. Whether there is a valid reason to use a mixin in a Service interface with Transactions is another issue, but it's not inconceivable. Using abstract classes will also work, but constrains you to a linear object hierarchy. Mixins are much more flexible. - JMB

1 Answers

15
votes

Spring uses (among others) a BeanFactoryTransactionAttributeSourceAdvisor as an Advisor when generating a proxy bean for classes annotated with or containing methods annotated with @Transactional.

When the time comes to proxy it, it uses the bean's class type (with CGLIB) to generate the proxy. So we want to see if the default method annotated with @Transactional will be visible from the implementing class' point of view.

Here's a Java 8 SSCCE

public static void main(String[] args) throws Exception{
    Class<?> randomImplClass = RandomImpl.class;
    System.out.println(randomImplClass);
    Easy annotation = randomImplClass.getAnnotation(Easy.class);
    System.out.println("Class: " + randomImplClass);
    System.out.println("Class Annotation: " + annotation);

    Method method = randomImplClass.getMethod("doRandom");
    annotation = method.getAnnotation(Easy.class);
    System.out.println("Method: " + method);
    System.out.println("Method Annotation: " + annotation);
}

public static class RandomImpl implements Random{}
@Easy
interface Random {
    @Easy
    default void doRandom() {System.out.println("testing");};
}

@Target(value = {METHOD, TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Easy {}

which prints

class TestEnhancer$RandomImpl
Class: class TestEnhancer$RandomImpl
Class Annotation: null
Method: public default void TestEnhancer$Random.doRandom()
Method Annotation: @TestEnhancer$Easy()

Indicating that the annotation was inherited for the interface's method. It seems, therefore, that Spring will be able to add @Transactional behavior when the class has not overriden the default method. If it has overriden it, then annotations are not inherited.