SeamFramework.orgCommunity Documentation

Chapter 9. Interceptors

9.1. Interceptor bindings
9.2. Implementing interceptors
9.3. Enabling interceptors
9.4. Interceptor bindings with members
9.5. Multiple interceptor binding annotations
9.6. Interceptor binding type inheritance
9.7. Use of @Interceptors

Interceptor functionality is defined in the Java Interceptors specification. CDI enhances this functionality with a more sophisticated, semantic, annotation-based approach to binding interceptors to beans.

The Interceptors specification defines two kinds of interception points:

In addition, the EJB specification defines timeout method interception.

A business method interceptor applies to invocations of methods of the bean by clients of the bean:

public class TransactionInterceptor {

   @AroundInvoke 
   public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}

A lifecycle callback interceptor applies to invocations of lifecycle callbacks by the container:

public class DependencyInjectionInterceptor {

   @PostConstruct 
   public void injectDependencies(InvocationContext ctx) { ... }
}

An interceptor class may intercept both lifecycle callbacks and business methods.

A timeout method interceptor applies to invocations of EJB timeout methods by the container:

public class TimeoutInterceptor {

   @AroundTimeout 
   public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}

Suppose we want to declare that some of our beans are transactional. The first thing we need is an interceptor binding type to specify exactly which beans we're interested in:

@InterceptorBinding

@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {}

Now we can easily specify that our ShoppingCart is a transactional object:

@Transactional

public class ShoppingCart { ... }

Or, if we prefer, we can specify that just one method is transactional:

public class ShoppingCart {

   @Transactional public void checkout() { ... }
}

That's great, but somewhere along the line we're going to have to actually implement the interceptor that provides this transaction management aspect. All we need to do is create a standard interceptor, and annotate it @Interceptor and @Transactional.

@Transactional @Interceptor

public class TransactionInterceptor {
   @AroundInvoke 
   public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}

Interceptors can take advantage of dependency injection:

@Transactional @Interceptor

public class TransactionInterceptor {
    @Resource UserTransaction transaction;
    @AroundInvoke 
    public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
    
}

Multiple interceptors may use the same interceptor binding type.

By default, all interceptors are disabled. We need to enable our interceptor in the beans.xml descriptor of a bean archive. This activation only applies to the beans in that archive.


<beans
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
   <interceptors>
      <class>org.mycompany.myapp.TransactionInterceptor</class>
   </interceptors>
</beans>

Whoah! Why the angle bracket stew?

Well, having the XML declaration is actually a good thing. It solves two problems:

For example, we could specify that our security interceptor runs before our transaction interceptor.


<beans
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
   <interceptors>
      <class>org.mycompany.myapp.SecurityInterceptor</class>
      <class>org.mycompany.myapp.TransactionInterceptor</class>
   </interceptors>
</beans>

Or we could turn them both off in our test environment by simply not mentioning them in beans.xml! Ah, so simple.

Suppose we want to add some extra information to our @Transactional annotation:

@InterceptorBinding

@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {
   boolean requiresNew() default false;
}

CDI will use the value of requiresNew to choose between two different interceptors, TransactionInterceptor and RequiresNewTransactionInterceptor.

@Transactional(requiresNew = true) @Interceptor

public class RequiresNewTransactionInterceptor {
   @AroundInvoke 
   public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}

Now we can use RequiresNewTransactionInterceptor like this:

@Transactional(requiresNew = true)

public class ShoppingCart { ... }

But what if we only have one interceptor and we want the container to ignore the value of requiresNew when binding interceptors? Perhaps this information is only useful for the interceptor implementation. We can use the @Nonbinding annotation:

@InterceptorBinding

@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Secure {
   @Nonbinding String[] rolesAllowed() default {};
}

Usually we use combinations of interceptor bindings types to bind multiple interceptors to a bean. For example, the following declaration would be used to bind TransactionInterceptor and SecurityInterceptor to the same bean:

@Secure(rolesAllowed="admin") @Transactional

public class ShoppingCart { ... }

However, in very complex cases, an interceptor itself may specify some combination of interceptor binding types:

@Transactional @Secure @Interceptor

public class TransactionalSecureInterceptor { ... }

Then this interceptor could be bound to the checkout() method using any one of the following combinations:

public class ShoppingCart {

   @Transactional @Secure public void checkout() { ... }
}
@Secure

public class ShoppingCart {
   @Transactional public void checkout() { ... }
}
@Transactional

public class ShoppingCart {
   @Secure public void checkout() { ... }
}
@Transactional @Secure

public class ShoppingCart {
   public void checkout() { ... }
}

One limitation of the Java language support for annotations is the lack of annotation inheritance. Really, annotations should have reuse built in, to allow this kind of thing to work:

public @interface Action extends Transactional, Secure { ... }

Well, fortunately, CDI works around this missing feature of Java. We may annotate one interceptor binding type with other interceptor binding types (termed a meta-annotation). The interceptor bindings are transitive — any bean with the first interceptor binding inherits the interceptor bindings declared as meta-annotations.

@Transactional @Secure

@InterceptorBinding
@Target(TYPE)
@Retention(RUNTIME)
public @interface Action { ... }

Now, any bean annotated @Action will be bound to both TransactionInterceptor and SecurityInterceptor. (And even TransactionalSecureInterceptor, if it exists.)

The @Interceptors annotation defined by the interceptor specification (and used by the managed bean and EJB specifications) is still supported in CDI.

@Interceptors({TransactionInterceptor.class, SecurityInterceptor.class})

public class ShoppingCart {
   public void checkout() { ... }
}

However, this approach suffers the following drawbacks:

Therefore, we recommend the use of CDI-style interceptor bindings.