Interceptors

Interceptor functionality is defined in the Java Interceptors specification.

The Interceptors specification defines three kinds of interception points:

  • business method interception,

  • lifecycle callback interception, and

  • timeout method interception (EJB only).

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 { ... }
}

Interceptor bindings

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() { ... }
}

Implementing interceptors

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.

Enabling interceptors

By default, all interceptors are disabled. We need to enable our interceptor. We can do it using beans.xml descriptor of a bean archive. However, this activation only applies to the beans in that archive. From CDI 1.1 onwards the interceptor can be enabled for the whole application using @Priority annotation.

<beans
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.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:

  • it enables us to specify an ordering for the interceptors in our system, ensuring deterministic behavior, and

  • it lets us enable or disable interceptor classes at deployment time.

Having two interceptors without @Priority, we could specify that our security interceptor runs before our transaction interceptor.

<beans
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.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.

It gets quite tricky when used along with interceptors annotated with @Priority. Interceptors enabled using @Priority are called before interceptors enabled using beans.xml, the lower priority values are called first.

Note
An interceptor enabled by @Priority and in the same time listed in beans.xml is only invoked once in the @Priority part of the invocation chain. E.g. the enablement via beans.xml will be ignored.

Interceptor bindings with members

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 {};
}

Multiple interceptor binding annotations

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() { ... }
}

Interceptor binding type inheritance

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.)

Use of @Interceptors

The @Interceptors annotation defined by the Interceptors specification (and used by the Managed Beans 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:

  • the interceptor implementation is hardcoded in business code,

  • interceptors may not be easily disabled at deployment time, and

  • the interceptor ordering is non-global — it is determined by the order in which interceptors are listed at the class level.

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

Enhanced version of jakarta.interceptor.InvocationContext

For even more control over interceptors, Weld offers enhanced version of jakarta.interceptor.InvocationContext - org.jboss.weld.interceptor.WeldInvocationContext.

Note
The functionality described below is deprecated since Weld 6 and will be removed in the future. Users should instead use newly added methods directly from jakarta.interceptor.InvocationContext, hence removing the need to use Weld specific APIs for this purpose.

It comes with two additional methods - getInterceptorBindings and getInterceptorBindingsByType(Class<T> annotationType). You shouldn’t need this in most cases, but it comes handy when working with @Nonbinding values in interceptor binding annotations.

Assume you have the following interceptor binding:

@InterceptorBinding
@Inherited
@Target({ TYPE, METHOD, CONSTRUCTOR})
@Retention(RUNTIME)
public @interface FooBinding {

    @Nonbinding
    String secret() default "";

}

Then, in the interceptor class, you can retrieve the secret String in the following way:

@Priority(value = Interceptor.Priority.APPLICATION)
@Interceptor
@FooBinding(secret = "nonbinding")
public class AroundConstructInterceptor {

    @SuppressWarnings("unchecked")
    @AroundConstruct
    void intercept(InvocationContext ctx) throws Exception {
        if(ctx instanceof WeldInvocationContext) {
            Set<Annotation> bindings =  ((WeldInvocationContext)ctx).getInterceptorBindings();
	    for (Annotation annotation : bindings) {
                if (annotation.annotationType().equals(FooBinding.class)) {
                    FooBinding fooBinding = (FooBinding) annotation;
                    String secret = fooBinding.secret();
                }
            }
        }
        ctx.proceed();
    }
}

WeldInvocationContext can be used with the following interceptor types:

  • @AroundConstruct

  • @PostConstruct

  • @AroundInvoke

Alternatively, you can gain access to these binding directly from InvocationContext as we store them there using a key org.jboss.weld.interceptor.bindings. This key is easily accessible from WeldInvocationContext.INTERCEPTOR_BINDINGS_KEY. Let’s alter the previous example to demonstrate this:

@Priority(value = Interceptor.Priority.APPLICATION)
@Interceptor
@FooBinding(secret = "nonbinding")
public class AroundConstructInterceptor {

    @SuppressWarnings("unchecked")
    @AroundConstruct
    void intercept(InvocationContext ctx) throws Exception {
        // retrieve data directly from InvocationContext
        Set<Annotation> bindings = (Set<Annotation>) ctx.getContextData().get(WeldInvocationContext.INTERCEPTOR_BINDINGS_KEY);
	if (bindings != null) {
            for (Annotation annotation : bindings) {
                if (annotation.annotationType().equals(FooBinding.class)) {
                    FooBinding fooBinding = (FooBinding) annotation;
                    String secret = fooBinding.secret();
                }
            }
        }
        ctx.proceed();
    }
}

Loosening the limitations of InterceptionFactory

Note
This is an experimental feature which goes beyond the scope of what CDI specification requires. While we aim to support this, the behaviour may slightly shift over time.

CDI 2.0 introduced the InterceptionFactory which can be used to intercept a bean created via producer method. The specification only allows users to operate on Java classes and when it comes to interfaces, it states that unportable behaviour results. Weld supports both ways as operating on interfaces effectively allows to bypass proxyability rules of implementation class. This however comes with a price - only annotations added programatically through InterceptionFactory.configure() will be taken into consideration.

Let’s have SomeImpl class that is an implementation of interface SomeInterface and InterceptThis is an InterceptorBinding. Let us also assume that we have secondary implementation of SomeInterface named SomeUnproxyableImpl that has a final method in it. All of the following producer methods will work:

    @Produces
    @ApplicationScoped
    // proxyable implementation which is also the InterceptionFactory paramater
    public SomeImpl produceBeanBasedOnImplClass(InterceptionFactory<SomeImpl> interceptionFactory) {
        interceptionFactory.configure().add(InterceptThis.Literal.INSTANCE);
        return interceptionFactory.createInterceptedInstance(new SomeImpl());
    }

    @Produces
    @ApplicationScoped
    // proxyable implementation but operating on an interface type as InterceptionFactory parameter
    public SomeInterface produceBeanBasedOnInterface(InterceptionFactory<SomeInterface> interceptionFactory) {
        interceptionFactory.configure().add(InterceptThis.Literal.INSTANCE);
        return interceptionFactory.createInterceptedInstance(new SomeImpl());
    }

    @Produces
    @ApplicationScoped
    // unproxyable implementation and operating on an interface type as InterceptionFactory parameter
    public SomeInterface produceBeanBasedOnInterfaceWithUnproxyableImpl(InterceptionFactory<SomeInterface> interceptionFactory) {
        interceptionFactory.configure().add(InterceptThis.Literal.INSTANCE);
        return interceptionFactory.createInterceptedInstance(new SomeUnproxyableImpl());
    }