Help

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?

8 comments:
 
11. Dec 2009, 06:18 CET | Link
Arbi Sookazian

So is this solution applicable to POJOs and EJBs as well?

What's the advantage of using:

@RequiresNewTransaction
    public void login() { ... }

over EE6's version:

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void login() { ... }

other than less typing ???

@Transactional reminds me of Seam...

ReplyQuote
 
11. Dec 2009, 11:26 CET | Link
For a couple of hours I thought he had me on this one, but it turns out that CDI has a fairly neat solution.

Damn, I was so close!

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.

Not quite -- actually, my first pass at this was just the opposite... I had a single annotation with a parameter to define the propagation, but six interceptors to do the work -- I actually like using the separate interceptors because it keeps my code clean, and I can basically allow the CDI engine to act as my 'if-statement' :)... what I don't like is having to force the user to register all six annotations, when it would be simpler for them to register them as a block... As for using six annotations versus a single annotation, I could go either way -- @RequiredTx does seem a little nicer than @Transactional(Required), though :)

Your solution does sound like it will work, but for the problem I'm trying to solve -- simply enabling six interceptors with one 'name' -- it feels like overkill... I'll give it some thought, though... perhaps something could be done with an extension...

M

 
11. Dec 2009, 14:45 CET | Link
alberto gori
Your solution does sound like it will work, but for the problem I'm trying to solve -- simply enabling six interceptors with one 'name' -- it feels like overkill... I'll give it some thought, though... perhaps something could be done with an extension...

Maybe this is not a best practice because of performance. Surely Gavin can tell us something about this.

About interceptor configuration, I like the solution implemented in JSF2.0. You can define a name for every configuration file so that it's possible to import a whole bunch of components defined in another faces-config.xml using this name. What about using the same technique for CDI interceptors?

 
11. Dec 2009, 15:13 CET | Link

I haven't done any benchmarks on extensions but concidering that annotations processing and bean building is a relatively heavy process, you'd have to do some pretty heavy magic in order to get a 10% increase at boot time (and fortunately, it's the only place where the overhead would occur)

 
11. Dec 2009, 23:17 CET | Link
Maybe this is not a best practice because of performance. Surely Gavin can tell us something about this.

I don't think performance is a salient issue here.

About interceptor configuration, I like the solution implemented in JSF2.0. You can define a name for every configuration file so that it's possible to import a whole bunch of components defined in another faces-config.xml using this name. What about using the same technique for CDI interceptors?

I guess I don't really buy this solution. The configuration file for a module contains various things that are not really related to each other in terms of how they should be ordered with respect to other things. I don't see how their ordering can be defined as a single chunk. What I'm saying is that ordering modules isn't really meaningful. A module doesn't have an order. The things in the module do. If a module defines two interceptors and one decorator, why should they be ordered as a single unit? Doesn't make much sense to me.

 
11. Dec 2009, 23:22 CET | Link

Matt, I guess I think it's almost always going to be a simpler implementation to have one instance of TransactionInterceptor that does transaction management for all methods of the bean, than potentially multiple interceptor instances (TransactionRequiredInterceptor, TransactionRequiresNewInterceptor, TransactionMandatoryInterceptor, etc) depending upon precisely which propagation styles are used in the bean.

 
11. Dec 2009, 23:24 CET | Link
Gavin King wrote on Dec 11, 2009 17:17:
Maybe this is not a best practice because of performance. Surely Gavin can tell us something about this. I don't think performance is a salient issue here.

Actually, on second thoughts, I think Alberto does have a point. We do want to limit the number of interceptor instances in the interceptor stack for the bean - this does affect performance.

 
12. Dec 2009, 07:29 CET | Link

Had a thought today about a scenario where the single interceptor analogy might not work -- or could at least be pretty ugly... consider a scenario where someone creates a simple framework by piecing different libraries together... so they might have a library with transactional interceptors, one with security interceptors, one with remoting interceptors (ignore the fact that this last one is dumb -- I'm out of ideas :) )... perhaps the framework itself only contains a single Stereotype named @Stateful -- in order to use this framework, the end users would be required to register at least three interceptor classes, all which may have very different names and packages, if they're sourced from different authors... that would be best case -- worst case would be eight if the tx library used six different interceptors...

In this case, I believe the single interceptor would have to contain the logic to fire off the individual interceptors, would have to be able to react to changes in the API (since the '@AroundInvoke' method can be named anything), etc. I suppose it's possible, but not particularly nice...

What I'm thinking of is the ability for a beans.xml file to export a set of interceptors (and possibly alternates and decorators -- I dunno) under a name... so it might look something like this:

<beans>
   <interceptors>
      <export name="com.fakeejb.Stateful">
         <class>com.sec.SecurityInt</class>
         <class>com.somewhere.RemoteInter</class>
         <class>com.tx.RequiredTxIntercptor</class>
         <class>com.tx.RequiresNewTxIntercptor</class>
         <class>com.tx.SupportsTxIntercptor</class>
         <class>com.tx.NeverTxIntercptor</class>
         <class>com.tx.MandatoryTxIntercptor</class>
         <class>com.tx.NotSupportedTxIntercptor</class>
      </export>
   </interceptors>
</beans>

Other libraries would then be able to use com.fakeejb.Stateful as a regular entry for interceptors (or perhaps an 'import' tag is added alongside the 'class' element)... This way ordering could be maintained according to the ordering in the export definition, without needing to export an entire module (which I agree isn't a great solution)... Seems like a much simpler solution, more flexible, and potentially easy enough to add to the spec in a maintenance update...

M

Post Comment