Matt Corey has blogged about CDI interceptor bindings, showing a simple example of how you can implement your own @RequiredTx annotation. He also lightly criticizes the use of beans.xml for interceptor enablement. (This was also discussed in the Weld forum.) I really think we have the design just right here, and I'll explain why. But first let me remember why CDI interceptor bindings are much better than the @Interceptors annotation from EJB 3.0.
In EJB 3.0, you could declare the interceptors of a bean by specifying them directly on the bean class. Using Matt's example:
public class UserGetterer {
@Interceptors({RequiredTxInterceptor.class, SecurityInterceptor.class})
public User loadUser() { ... }
}
Now, I count three problems with this:
- It's very ugly to have the bean class depend directly upon the interceptor implementation class.
- Interceptors should be able to vary depending upon the deployment environment - I don't need my transaction interceptor in tests, and I definitely don't want my security interceptor!
- The interceptor ordering is defined by the order that the interceptors are listed - which I suppose is fine if the interceptors are truly orthogonal, but this is rarely the case. Interceptor ordering should be defined more centrally, reducing the possibility for subtle bugs.
(EJB 3.0 also defined an XML-based approach to binding interceptors to beans, which is less problematic, but I prefer to see information like this in my code.)
So, in CDI, we introduce an indirection, called an interceptor binding. An interceptor binding is an annotation - you can see an example in Matt's post - that we use instead of the interceptor class:
public class UserGetterer {
@RequiredTx @Secure
public User loadUser() { ... }
}
Now, our bean class doesn't depend directly upon the interceptor implementation classes, and doesn't specify the ordering in which the interceptors are called. To specify the connection between the bean and the interceptor classes we need to do two more things:
- apply the interceptor bindings to the interceptor classes, and
- enable the interceptors by listing them in the beans.xml file of the bean's module.
Matt objects to the second requirement:
Yeah, I know -- it feels kind of like we're exposing your implementation detail by asking the user to enable the interceptor instead of the annotation... kind of annoying, but I can deal with it...
Well, not really - on the contrary, you're explicitly not doing that. The beans.xml file contains deployment-specific metadata - just the stuff that depends upon your deployment. So in your test environment, you don't specify any interceptors at all in beans.xml. In your production deployment you can enable RequiredTransactionInterceptor. In some other deployment, you can enable DummyRequiredTransactionInterceptor, a different implementation of the @RequiredTx semantic
or aspect
. It's even possible to bind multiple interceptors to the same interceptor binding! So we have truly decoupled the semantic annotation @RequiredTx from its implementation, RequiredTransactionInterceptor.
So we've got an extra level of indirection here that we would not have if it was the annotation we were specifying in beans.xml, as suggested by Matt.
In fact, beans.xml serves two different roles here. It lets you specify:
- any, or no, or multiple implementations of the interceptor binding, for the particular deployment, and
- the interceptor ordering.
Ordering is important. It's very likely that you'll be using interceptors defined by various different portable extensions in your application. These portable extensions don't know anything about each other, so they don't know what order they should be called in. We can't specify the interceptor ordering in the portable extensions that define the interceptors. It's your job, as application developer, to specify the ordering of these interceptors.
Matt says:
another thing to note is that if you pack your interceptors into a shareable library file, all other library files in your application will still need to do this, even if you enable it in your .jar file -- that's because there's no concept of a global interceptor in CDI, but to be safe, each library is forced to enable all interceptors in use, so that they can enforce the ordering of interceptors in a very intuitive manner... I can accept this reasoning simply because they have defined it, rather than letting it be an undefined mess, so again, it's more of annoyance for me -- hopefully there will be other options in the future...
OK, so let's step back a second. If my application is deployed as a single module, there is a global ordering, the ordering defined by the beans.xml file of that module. Now, back in Java EE 5 days, it wasn't possible to deploy an application that used EJBs as a single module. But it is now. A war can now directly contain EJBs. So single-module applications are going to be very common. So that's the simple case out of the way.
Now, for the more complex case of an application that really, truly needs to be deployed, for example, as an ear with multiple war and EJB jar modules, or as a war containing multiple jar modules, it's quite likely that the various modules are going to each define a different list of interceptors, decorators and alternatives. I mean, the whole point of modularizing the application is to decouple the modules.
I suppose I accept that we could allow toplevel
beans.xml file, that defined a global, or default, list of interceptors, decorators and alternatives. This would be very easy to add to the CDI spec, but I think it's a pretty advanced case. Historically, Java EE has not allowed you to put module-level metadata in the ear.