In reply to this post, Matt writes:
Where I still have a bit of trouble with this is if we implement each of the six EJB TransactionAttributeTypes with separate interceptors and bindings, and package them in a .jar file... I'm ok with having some kind of enablement in the beans.xml file for my .war, but to have the user enter all six interceptors is awkward for a library developer to force on someone...
For a couple of hours I thought he had me on this one, but it turns out that CDI has a fairly neat solution.
What Matt is trying to do is have a different annotation for each transaction propagation style, for example @RequiresTransaction, @RequiresNewTransaction, @MandatoryTransaction, etc, instead of having a single @Transactional annotation which specifies the propagation style using an annotation member. He's worried that CDI will force him to use a separate interceptor for each annotation, and then declare all these interceptor classes in beans.xml.
But there's a better way :-)
First, let's create an interceptor binding type that specifies the propagation style using a member. Note that we're not going to use this directly in our beans.
@Inherited
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Transactional {
@Nonbinding
TransactionPropagation value() default REQUIRED;
}
Where TransactionPropagation is an enumeration of propagation styles:
public enum TransactionPropagation { REQUIRED, REQUIRES_NEW, MANDATORY, ... }
CDI interceptor bindings can be inherited by other interceptor bindings. This feature allows us to use our @Transactional annotation as a meta-annotation. So let's apply @Transactional to Matt's annotations, for example:
@Inherited
@Transactional(REQUIRED)
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface RequiresTransaction {}
@Inherited
@Transactional(REQUIRES_NEW)
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface RequiresNewTransaction {}
We use these interceptor bindings by annotating the bean class, for example:
@RequiresTransaction
public class Users {
@RequiresNewTransaction
public void login() { ... }
}
An annotation at the method level should override an annotation at the class level.
Now we can implement transaction management using a single interceptor class:
@Transactional @Interceptor
class TransactionInterceptor {
@AroundInvoke
public Object manageTransaction(InvocationContext ctx) throws Exception {
TransactionPropagation tp = getTransactionPropagation(ctx.getMethod());
...
}
/**
* Get the TransactionPropagation for the meta-annotation
*/
private TransactionPropagation getTransactionPropagation(Method m) {
//first look at method-level annotations, since they take priority
for (Annotation a: m.getAnnotations()) {
if (a.annotationType().isAnnotationPresent(Transactional.class)) {
return a.annotationType().getAnnotation(Transactional.class).value();
}
}
//now try class-level annotations
for (Annotation a: m.getDeclaringClass().getAnnotations()) {
if (a.annotationType().isAnnotationPresent(Transactional.class)) {
return a.annotationType().getAnnotation(Transactional.class).value();
}
}
return null;
}
}
This interceptor class that implements all of the transaction propagation styles. Neat, huh?