EJB Interceptors

The EJB 3.0 spec defines the ability to apply custom made interceptors to the business methods of your session and message driven beans (and of course to the JBoss @Service and @Consumer beans). EJB 3.0 interceptors take the form of methods annotated with the @javax.ejb.AroundInvoke annotation. These methods must have the following signature:

   @javax.ejb.AroundInvoke
   public Object <Arbitrary method name>(javax.ejb.InvocationContext ctx) throws java.lang.Exception

You can either define an interceptor method in the bean class itself, or in separate classes. There can only be one interceptor method per class.

Interceptor method in bean class

Take a look at EmailMDB.java. It contains this method:

   @AroundInvoke
   public Object mdbInterceptor(InvocationContext ctx) throws Exception
   {
      System.out.println("*** Intercepting call to EmailMDB.mdbInterceptor()");
      return ctx.proceed();
   }

This method will wrap the call to EmailMDB.onMessage(). The call to ctx.proceed() causes the next object in the chain of interceptors to get invoked. At the end of the chain of interceptors, the actual bean method gets called.

Similarly EmailSystemBean.java has a method annotated with @AroundInvoke

   @AroundInvoke
   public Object myBeanInterceptor(InvocationContext ctx) throws Exception
   {
      if (ctx.getMethod().getName().equals("emailLostPassword"))
      {
         System.out.println("*** EmailSystemBean.myBeanInterceptor - username: " + ctx.getParameters()[0]);
      }

      return ctx.proceed();
   }

External interceptors

The rest of this example will be looking at adding interceptors external to the bean class, more specifically to EmailSystemBean. Interceptors can be bound in three different ways

An external interceptor instance follows the lifecycle of the target bean instance.

Default interceptors

We will see how class and method-level interceptors can be bound to a bean or a bean's method using annotations or xml. Default interceptors can only be bound via xml. A Default interceptor is an interceptor that is invoked whenever a business method is invoked on any bean within the deployment.

DefaultInterceptor.java defines an @AroundInvoke method with the required method signature.

   @AroundInvoke
   public Object intercept(InvocationContext ctx) throws Exception
   {
      System.out.println("*** DefaultInterceptor intercepting " + ctx.getMethod().getName());
      try
      {
         return ctx.proceed();
      }
      finally
      {
         System.out.println("*** DefaultInterceptor exiting");
      }
   }

As mentioned, default interceptors can only be applied via xml.

In ejb-jar.xml we have the following:

   <assembly-descriptor>
      <!-- Default interceptor that will apply to all methods for all beans in deployment -->
      <interceptor-binding>
         <ejb-name>*</ejb-name>
         <interceptor-class>org.jboss.tutorial.interceptor.bean.DefaultInterceptor</interceptor-class>
      </interceptor-binding>
      ...
   </assembly-descriptor>

Using * for the ejb-name says that DefaultInterceptor is a default interceptor, i.e. it should intercept all calls to all beans within the deployment unit. We could have added more interceptor-class entries to bind more interceptors, as shown here:

   <assembly-descriptor>
      <!-- Default interceptor that will apply to all methods for all beans in deployment -->
      <interceptor-binding>
         <ejb-name>*</ejb-name>
         <interceptor-class>org.jboss.tutorial.interceptor.bean.DefaultInterceptor</interceptor-class>
         <interceptor-class>org.jboss.tutorial.interceptor.bean.AnotherInterceptor</interceptor-class>
      </interceptor-binding>
      ...
   </assembly-descriptor>

In this case DefaultInterceptor would be invoked before AnotherInterceptor.

Class-level interceptors

Class-level interceptors can be bound using xml or annotations.

Class-level using annotations

EmailSystemBean.java has been annotated:

   @Stateless
   @Interceptors ({TracingInterceptor.class})
   public class EmailSystemBean
   {
      ...
   }

This says that the @AroundInvoke annotated method of TracingInterceptor.java should wrap all calls to EmailSystemBean's business methods. The @Interceptors annotation can take an array of classes, so you can bind more than one class-level interceptor this way, e.g.

   @Stateless
   @Interceptors ({TracingInterceptor.class, SomeInterceptor.class})
   public class EmailSystemBean
   {
      ...
   }

Class-level using xml

In ejb-jar.xml we have the following:

   <assembly-descriptor>
      ...
      <!-- Class interceptor that will apply to all methods for EmailSystemBean -->
      <interceptor-binding>
         <ejb-name>org.jboss.tutorial.interceptor.bean.EmailSystemBean</ejb-name>
         <interceptor-class>org.jboss.tutorial.interceptor.bean.OtherInterceptor</interceptor-class>
      </interceptor-binding>
      ...
   </assembly-descriptor>

Note that here we are specifying the ejb-name of the bean we want to apply the interceptor to. Hence, the @AroundInvoke annotated method of OtherInterceptor.java will wrap all calls to EmailSystemBean's business methods.

Method-level interceptors

Just as we can define default and class-level interceptors, we can also bind an interceptor to a particular business method within a bean

Method-level using annotations

EmailSystemBean's sendBookingConfirmationMessage() method has been annotated with the @Interceptors annotation specifying interceptors that will intercept when this perticular method is invoked.

   @Interceptors({AccountsConfirmInterceptor.class})
   public void sendBookingConfirmationMessage(long orderId)
   {
      ...
   }

Method-level interceptors are in addition to default and class-level interceptors, meaning that when we call EmailSystemBean.sendBookingConfirmationMessage() we get intercepted by

An interceptor method can choose to abort an invocation, by not calling InvocationContext.proceed() as is done in AccountConfirmInterceptor's interceptor method when a confirmation already exists.

Method-level using xml

We can also bind interceptors to a single business method within a bean using xml. But first let's define an interceptor using xml. All the examples we have looked at so far have used the @AroundInvoke annotation to mark the interceptor methods.

In ejb-jar.xml we have the following:

   <interceptors>
      <interceptor>
         <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class>
         <around-invoke-method>
            <method-name>sendCancelMessage</method-name>
         </around-invoke-method>
         ...
   </interceptors>

This tells us that the sendCancelMessage() method of AccountsCancelInterceptor is the interceptor method of this interceptor class. (We will see what the other sub-elements of interceptor mean in the 'injection' section.

To bind interceptors to a particular method using xml, is much the same as how this was done for class-level interceptors apart from that we specify the method-name of the method we want to intercept. From our ejb-jar.xml:

   <assembly-descriptor>
      ...
	<!-- Method interceptor will apply to sendBookingCancellationMessage for EmailSystemBean -->
      <interceptor-binding>
         <ejb-name>org.jboss.tutorial.interceptor.bean.EmailSystemBean</ejb-name>
         <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class>
         <method-name>sendBookingCancellationMessage</method-name>
      </interceptor-binding>
   </assembly-descriptor>

In the example just shown, if we had several business methods with the name, the interceptor would get applied to all of them. In the case of overloaded methods, we can further narrow down the method by specifying the method parameters, as in this example:

   <assembly-descriptor>
      ...
	<!-- Method interceptor will apply to sendBookingCancellationMessage for EmailSystemBean -->
      <interceptor-binding>
         <ejb-name>MyBean</ejb-name>
         <interceptor-class>SomeInterceptor</interceptor-class>
         <method-name>overLoadedMethod</method-name>
	  <method-params>
		 <method-param>int</method-param>
		 <method-param>java.lang.String[][]</method-param>
	  </method-params>

      </interceptor-binding>
   </assembly-descriptor>

If MyBean has several methods called overLoadedMethod, SomeInterceptor will only get applied to the one that matches the signature, i.e.

overLoadedMethod(int, java.lang.String[][])

Exclusion of default and class interceptors

For some beans we may want to exclude the default interceptors configured for the deployment, and for some methods with in a bean class we may want to exclude the class interceptors for the bean (and/or the default interceptors)

Annotations

The @javax.ejb.ExcludeDefaultInterceptors annotation can be applied to a bean class or a method. If applied to a bean class, default interceptors will not get invoked for any of that bean class's methods. If applied to a bean business method, default interceptors will not get invoked when calling that method.

The @javax.ejb.ExcludeClassInterceptors annotation can be applied to a bean method, and if used class-level interceptors will not get invoked when calling that method.

EmailMDB.java defines

   @ExcludeDefaultInterceptors        
   public class EmailMDB implements MessageListener
   {
      ...
   }
so DefaultInterceptor is not invoked when invoking the EmailMDB.onMessage()

EmailSystemBean's noop method is annotated with both @ExcludeClassInterceptors and @ExcludeDefaultInterceptors and so will not get intercepted by any of these.

   @ExcludeClassInterceptors
   @ExcludeDefaultInterceptors
   public void noop()
   {
      System.out.println("<In EmailSystemBean.noop business method");
      System.out.println("Exiting EmailSystemBean.noop business method>");
   }

}}


!!XML
We can also exclude class and method-level interceptors within an {{interceptor-binding}} by specifying the {{exclude-class-interceptors}} and {{exclude-default-interceptors}} elements.
{{{
   <assembly-descriptor>
      ...
      <interceptor-binding>
         <ejb-name>org.jboss.tutorial.interceptor.bean.EmailSystemBean</ejb-name>
         <exclude-class-interceptors/>
         <exclude-default-interceptors/>
         <method-name>noop2</method-name>
      </interceptor-binding>
      ...
   </assembly-descriptor>

Inheritance Ordering

If an interceptor (or bean class) inherits from another class which has a method declared to be an interceptor method, the interceptor methods from the super class will be invoked first. However, if the interceptor method in the superclass has been overridden (regardless of if the overriding method is declared to be an interceptor method or not) it is not invoked.

Both AccountsConfirmInterceptor and AccountsCancelInterceptor inherit from AccountsInterceptor.

AccountsInterceptor declares the following interceptor method:

   @AroundInvoke
   public Object intercept(InvocationContext ctx) throws Exception
   {
      System.out.println("*** AccountsInterceptor intercepting " + ctx.getMethod().getName());
      try
      {
         return ctx.proceed();
      }
      finally
      {
         System.out.println("*** AccountsInterceptor exiting");
      }
   }

This method is overridden by AccountsConfirmInterceptor, even though it is not AccountsConfirmInterceptor's interceptor method:

public class AccountsConfirmInterceptor extends AccountsInterceptor
{
   ...
   public Object intercept(InvocationContext ctx) throws Exception
   {
      //overrides AccountsInterceptor.intercept() so that will not be invoked
      return null;
   }

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

So when invoking AccountsConfirmInterceptor, we simply invoke its sendConfirmMessage() method. AccountsCancelInterceptor, on the other hand, does not override AccountsInterceptor.intercept(), so when invoking AccountsCancelInterceptor, we first get intercepted by AccountsInterceptor.intercept() followed by AccountsCancelInterceptor.sendCancelMessage().

Injection

Annotations

Just like a bean class, an interceptor can be the target of Dependency injection. The format for how this works is the same, and the injection works off the same ENC as the bean to which the interceptor is bound.

AccountsConfirmInterceptor has dependency injection set up using annotations:

public class AccountsConfirmInterceptor extends AccountsInterceptor
{
   @Resource(mappedName="java:ConnectionFactory")
   QueueConnectionFactory cf;
   
   @Resource(mappedName="queue/tutorial/accounts")
   Queue queue;
   
   @PersistenceContext
   EntityManager em;
   
   ...

Remember that interceptors follow the same lifecycle as the bean they are bound to. The interceptors are created at the same time as the bean instance is created, and dependecy injection occurs before the first business method is called. In the example just shown, the container;

XML

Injection can also be configured via xml, using the traditional ejb-ref, ejb-local-ref, resource-ref, resource-env-ref etc. which bind the global names. The only difference from beans is that we put this into the interceptors element defining the interceptor.

   <interceptors>
      <interceptor>
         <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class>
         <around-invoke-method>
            <method-name>sendCancelMessage</method-name>
         </around-invoke-method>
         <resource-ref>
            <res-ref-name>jms/ConnFactory</res-ref-name>
            <res-type>javax.jms.QueueConnectionFactory</res-type>
            <res-auth>Container</res-auth>
            <mapped-name>java:/ConnectionFactory</mapped-name>
            <injection-target>
               <injection-target-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</injection-target-class>
               <injection-target-name>cf</injection-target-name>
            </injection-target>
         </resource-ref>
         <resource-env-ref>
            <resource-env-ref-name>accountsQueue</resource-env-ref-name>
            <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
            <res-auth>Container</res-auth>
            <mapped-name>queue/tutorial/accounts</mapped-name>
            <injection-target>
               <injection-target-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</injection-target-class>
               <injection-target-name>queue</injection-target-name>
            </injection-target>
         </resource-env-ref>
      </interceptor>   
   </interceptors>

Interceptor Ordering

By default the ordering of interceptors when invoking a method are

Within each group (default, class, method) the order of the interceptors are from left to right as defined in the @Interceptors annotation, and then the xml interceptors.

Overriding interceptor ordering

You can override the default sort order of the external interceptors by specifiying an interceptor-binding with an interceptor-order specifying the order of the interceptors
   <assembly-descriptor>
      ...
      <interceptor-binding>
         <ejb-name>org.jboss.tutorial.interceptor.bean.EmailSystemBean</ejb-name>
         <method-name>sendBookingCancellationMessage</method-name>
         <interceptor-order>
            <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class>
            <interceptor-class>org.jboss.tutorial.interceptor.bean.DefaultInterceptor</interceptor-class>
            <interceptor-class>org.jboss.tutorial.interceptor.bean.OtherInterceptor</interceptor-class>
            <interceptor-class>org.jboss.tutorial.interceptor.bean.TracingInterceptor</interceptor-class>
         </interceptor-order>
      </interceptor-binding>
   </assembly-descriptor>

So when invoking EmailSystemBean.sendBookingCancellationMessage we will get the following interception order, quite different from the default sort order

InvocationContext

As you have seen the @AroundInvoke annotated interceptor methods all take a parameter of type javax.ejb.InvocationContext. The definition of InvocationContext is:
   package javax.ejb;

   public interface InvocationContext {
      public Object getBean();
      public java.lang.reflect.Method getMethod();
      public Object[] getParameters();
      public void setParameters(Object[] params);
      public java.util.Map getContextData();
      public Object proceed() throws Exception;
   }

In some case we might want access to the EJBContext, this can be injected into the interceptor instance using the @Resource annotation.The AroundInvoke methods share the JNDI name space of the bean for whose methods they are invoked and execute within the same transaction and security context as the business methods for which they are invoked.

Building and Running

To build and run the example, make sure you have ejb3.deployer installed in JBoss 4.0.x and have JBoss running. See the reference manual on how to install EJB 3.0.
Unix:    $ export JBOSS_HOME=<where your jboss 4.0 distribution is>
Windows: $ set JBOSS_HOME=<where your jboss 4.0 distribution is>
$ ant
$ ant run

Look at the JBoss console window to see the output of the interceptions taking place, the EmailMDB and AccountsMDB might occur in slightly different places since they execute asynchronously

17:34:54,468 INFO  [EJB3Deployer] Deployed: file:/C:/cygwin/home/Kabir/cvs/jboss-head/build/output/jboss-5.0.0alpha/server/all/dep
loy/tutorial.jar

Calling EmailSystemBean.emailLostPassword()

17:35:02,328 INFO  [STDOUT] *** DefaultInterceptor intercepting emailLostPassword
17:35:02,328 INFO  [STDOUT] *** TracingInterceptor intercepting emailLostPassword
17:35:02,328 INFO  [STDOUT] *** OtherInterceptor intercepting emailLostPassword
17:35:02,328 INFO  [STDOUT] *** EmailSystemBean.myBeanInterceptor - username: whatever
17:35:02,328 INFO  [STDOUT] <In EmailSystemBean.emailLostPassword business method
17:35:05,343 INFO  [STDOUT] Message sent successfully to remote queue.
17:35:05,343 INFO  [STDOUT] Exiting EmailSystemBean.emailLostPassword business method>
17:35:05,343 INFO  [STDOUT] *** OtherInterceptor exiting
17:35:05,343 INFO  [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.emailLostPass
word() took 3015ms
17:35:05,375 INFO  [STDOUT] *** EmailMDB.mdbInterceptor intercepting
17:35:05,375 INFO  [STDOUT]
----------------
EMailMDB - Got message, sending email
----------------
17:35:05,375 INFO  [STDOUT] *** DefaultInterceptor exiting

Calling EmailSystemBean.sendBookingConfirmationMessage()

17:35:07,421 INFO  [STDOUT] *** DefaultInterceptor intercepting sendBookingConfirmationMessage
17:35:07,421 INFO  [STDOUT] *** TracingInterceptor intercepting sendBookingConfirmationMessage
17:35:07,437 INFO  [STDOUT] *** OtherInterceptor intercepting sendBookingConfirmationMessage
17:35:07,437 INFO  [STDOUT] *** AccountsConfirmInterceptor intercepting
17:35:07,437 INFO  [STDOUT] *** AccountsConfirmInterceptor - recording confirmation
17:35:07,437 INFO  [STDOUT] *** AccountsConfirmInterceptor - notifying accounts dept sendBookingConfirmationMessage
17:35:07,437 INFO  [STDOUT] <In EmailSystemBean.sendBookingConfirmationMessage business method
17:35:07,453 INFO  [STDOUT] *** DefaultInterceptor intercepting onMessage
17:35:07,453 INFO  [STDOUT]
----------------
AccountsMDB - Got message Confirming order 100
----------------
17:35:07,453 INFO  [STDOUT] *** DefaultInterceptor exiting
17:35:10,468 INFO  [STDOUT] Message sent successfully to remote queue.
17:35:10,468 INFO  [STDOUT] Exiting EmailSystemBean.sendBookingConfirmationMessage business method>
17:35:10,468 INFO  [STDOUT] *** AccountsConfirmInterceptor exiting
17:35:10,468 INFO  [STDOUT] *** OtherInterceptor exiting
17:35:10,468 INFO  [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingCo
nfirmationMessage() took 3031ms
17:35:10,468 INFO  [STDOUT] *** DefaultInterceptor exiting
17:35:10,468 INFO  [STDOUT] *** EmailMDB.mdbInterceptor intercepting
17:35:10,468 INFO  [STDOUT]
----------------
EMailMDB - Got message, sending email
----------------
Calling EmailSystemBean.sendBookingConfirmationMessage(), this will be aborted
17:35:12,484 INFO  [STDOUT] *** DefaultInterceptor intercepting sendBookingConfirmationMessage
17:35:12,484 INFO  [STDOUT] *** TracingInterceptor intercepting sendBookingConfirmationMessage
17:35:12,484 INFO  [STDOUT] *** OtherInterceptor intercepting sendBookingConfirmationMessage
17:35:12,484 INFO  [STDOUT] *** AccountsConfirmInterceptor intercepting
17:35:12,484 INFO  [STDOUT] *** AccountsConfirmInterceptor - order has already been confirmed aborting
17:35:12,500 INFO  [STDOUT] *** AccountsConfirmInterceptor exiting
17:35:12,500 INFO  [STDOUT] *** OtherInterceptor exiting
17:35:12,500 INFO  [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingCo
nfirmationMessage() took 16ms
17:35:12,500 INFO  [STDOUT] *** DefaultInterceptor exiting
Calling EmailSystemBean.sendBookingCancellationMessage(), this will be aborted
17:35:14,500 INFO  [STDOUT] *** AccountsInterceptor intercepting sendBookingCancellationMessage
17:35:14,500 INFO  [STDOUT] *** AccountsCancelInterceptor intercepting sendBookingCancellationMessage
17:35:14,500 INFO  [STDOUT] *** AccountsConfirmInterceptor - notifying accounts dept
17:35:14,500 INFO  [STDOUT] *** DefaultInterceptor intercepting sendBookingCancellationMessage
17:35:14,515 INFO  [STDOUT] *** OtherInterceptor intercepting sendBookingCancellationMessage
17:35:14,515 INFO  [STDOUT] *** TracingInterceptor intercepting sendBookingCancellationMessage
17:35:14,515 INFO  [STDOUT] <In EmailSystemBean.sendBookingCancellationMessage business method
17:35:14,515 INFO  [STDOUT] *** DefaultInterceptor intercepting onMessage
17:35:14,515 INFO  [STDOUT]
----------------
AccountsMDB - Got message Cancelling order 100
----------------
17:35:14,515 INFO  [STDOUT] *** DefaultInterceptor exiting
17:35:17,625 INFO  [STDOUT] Message sent successfully to remote queue.
17:35:17,625 INFO  [STDOUT] Exiting EmailSystemBean.sendBookingCancellationMessage business method>
17:35:17,625 INFO  [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingCa
ncellationMessage() took 3110ms
17:35:17,625 INFO  [STDOUT] *** OtherInterceptor exiting
17:35:17,625 INFO  [STDOUT] *** DefaultInterceptor exiting
17:35:17,625 INFO  [STDOUT] *** AccountsCancelInterceptor exiting
17:35:17,640 INFO  [STDOUT] *** AccountsInterceptor exiting
17:35:17,640 INFO  [STDOUT] *** EmailMDB.mdbInterceptor intercepting
17:35:17,640 INFO  [STDOUT]
----------------
EMailMDB - Got message, sending email
----------------
Calling EmailSystemBean.noop()

17:35:19,640 INFO  [STDOUT] <In EmailSystemBean.noop business method
17:35:19,640 INFO  [STDOUT] Exiting EmailSystemBean.noop business method>
Calling EmailSystemBean.noop2()
17:35:21,640 INFO  [STDOUT] <In EmailSystemBean.noop2 business method
17:35:21,640 INFO  [STDOUT] Exiting EmailSystemBean.noop2 business method>