JBoss.org Community Documentation
The EJB 3.0 spec defines the ability to apply custom made interceptors
to the business methods
of your session and message driven 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 apply interceptors to JBoss specific @Service and @Consumer beans
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.
Take a look at
org.jboss.tutorial.interceptor.bean.EmailMDB
. 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
org.jboss.tutorial.interceptor.bean.EmailSystemBean
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(); }
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:
Default
Class level
Method level
An external interceptor instance follows the lifecycle of the target bean instance.
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.
org.jboss.tutorial.interceptor.bean.DefaultInterceptor
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
META-INF/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 can be bound using xml or annotations.
org.jbos.tutorial.interceptor.EmailSystemBean
has been annotated:
@Stateless @Interceptors ({TracingInterceptor.class}) public class EmailSystemBean { ... }
This says that the @AroundInvoke
annotated method of org.jboss.tutorial.interceptor.bean.TracingInterceptor
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 { ... }
In the META-INF/ejb-jar.xml
we have the following:
<assembly-descriptor> ... <!-- Class interceptor that will apply to all methods for EmailSystemBean --> <interceptor-binding> <ejb-name>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 org.jboss.tutorial.interceptor.bean.OtherInterceptor
will wrap all calls to EmailSystemBean
's business methods.
Just as we can define default and class-level interceptors, we can also bind an interceptor to a particular business method of a bean
org.jboss.tutorial.interceptor.bean.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
DefaultInterceptor (default)
TracingInterceptor (class-level)
OtherInterceptor (class-level)
AccountsConfirmInterceptor (method-level)
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.
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 META-INF/ejb-jar.xml
we have the following:
<interceptors> <interceptor> <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class> <around-invoke> <method-name>sendCancelMessage</method-name> </around-invoke> ... </interceptor> </interceptors>
This tells us that the sendCancelMessage()
method of org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
is the interceptor method of this interceptor class. 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 META-INF/ejb-jar.xml
:
<assembly-descriptor> ... <!-- Method interceptor will apply to sendBookingCancellationMessage for EmailSystemBean --> <interceptor-binding> <ejb-name>EmailSystemBean</ejb-name> <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class> <method> <method-name>sendBookingCancellationMessage</method-name> </method> </interceptor-binding> ... </assembly-descriptor>
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>SomeBean</ejb-name> <interceptor-class>SomeInterceptor</interceptor-class> <method> <method-name>methodWithParam</method-name> <method-params> <method-param>int</method-param> <method-param>java.lang.String[]</method-param> </method-params> </method> </interceptor-binding> </assembly-descriptor>
SomeInterceptor
will only get applied to the method of SomeBean
that matches the signature,
i.e. methodWithParam(int, java.lang.String[])
For some beans we may want to exclude the default interceptors configured for the deployment, and for some methods within a bean class we may want to exclude the class interceptors for the bean (and/or the default interceptors)
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. When this annotation
is used class-level interceptors will not get invoked when calling that method.
org.jboss.tutorial.interceptor.bean.EmailMDB
defines
@ExcludeDefaultInterceptors public class EmailMDB implements MessageListener { ... }
so DefaultInterceptor
is not invoked when invoking the EmailMDB.onMessage()
org.jboss.tutorial.interceptor.bean.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>"); }
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>EmailSystemBean</ejb-name> <exclude-default-interceptors>true</exclude-default-interceptors> <exclude-class-interceptors>true</exclude-class-interceptors> <method> <method-name>noop2</method-name> </method> </interceptor-binding> ... </assembly-descriptor>
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) the superclass method is not invoked.
Both org.jboss.tutorial.interceptor.bean.AccountsConfirmInterceptor
and
org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
inherit from
org.jboss.tutorial.interceptor.bean.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()
.
Just like a bean class, an interceptor can be the target of dependency injection (see the "injection" tutorial for details about 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.
org.jboss.tutorial.interceptor.bean.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:
Looks up java:ConnectionFactory
, binds it in the ENC and injects this into the field cf
Looks up queue/tutorial/accounts
, binds it in the ENC and injects this into the field queue
Obtains the default EntityManager and injects this into the field em
Injection, into interceptors, 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-name>sendCancelMessage</method-name> </around-invoke> <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> <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>
The container looks up java:/ConnectionFactory
in JNDI and creates the java:comp/env/jms/ConnFactory
entry in the bean's ENC, and following bean creation it injects the connection factory into AccountsCancelInterceptor
's
cf
field.
The container looks up queue/tutorial/accounts
in JNDI and creates the java:comp/env/accountsQueue
entry in the bean's ENC, and following bean creation it injects the queue into AccountsCancelInterceptor
's
queue
field.
By default the ordering of interceptors when invoking a method are
External interceptors
Default interceptors, if present
Class interceptors, if present
Method interceptors, if present
Interceptor method on the bean class (using @AroundInvoke)
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.
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>EmailSystemBean</ejb-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> <method> <method-name>sendBookingCancellationMessage</method-name> </method> </interceptor-binding> ... </assembly-descriptor>
So when invoking
EmailSystemBean.sendBookingCancellationMessage
we will get the following interception order,
quite different from the default sort order
AccountsCancelInterceptor (method-level)
DefaultInterceptor (default interceptor)
OtherInterceptor (2nd class-level interceptor)
TracingInterceptor (1st class-level interceptor)
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.interceptor; public interface InvocationContext { public java.lang.Object getTarget(); public java.lang.reflect.Method getMethod(); public java.lang.Object[] getParameters(); public void setParameters(java.lang.Object[] arg0); public java.util.Map getContextData(); public java.lang.Object proceed() throws java.lang.Exception; }
getBean()
- returns the bean instance on which we are calling a method
getMethod()
- returns the method we are calling on the bean instance
getParameters()
- returns the parameters for the method call
setParameters()
- allows you to modify the parameters for the method call
getContextData
- The EJB interceptions are stateless, but the same InvocationContext instance
is used for each interceptor method in a chain. If you want to pass values between interceptor methods in the
business method invocation you can store them in the Map retured by getContextData().
proceed
- As discussed, invokes the next interceptor, or if we are in the last interceptor
invokes the target bean method.
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.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "interceptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Starting [java] [java] Calling emailLostPassword [java] Waiting 2 seconds [java] [java] Calling sendBookingConfirmationMessage [java] Waiting 2 seconds [java] [java] Calling sendBookingConfirmationMessage [java] Waiting 2 seconds [java] [java] Calling sendBookingCancellationMessage [java] Waiting 2 seconds [java] [java] Calling noop [java] Waiting 2 seconds [java] [java] Calling noop2 [java] Waiting 2 seconds [java] Done
$ mvn clean install -PRunSingleTutorial
21:49:01,888 INFO [STDOUT] *** DefaultInterceptor intercepting emailLostPassword 21:49:01,889 INFO [STDOUT] *** TracingInterceptor intercepting emailLostPassword 21:49:01,889 INFO [STDOUT] *** OtherInterceptor intercepting emailLostPassword 21:49:01,889 INFO [STDOUT] *** EmailSystemBean.myBeanInterceptor - username: whatever 21:49:01,889 INFO [STDOUT] <In EmailSystemBean.emailLostPassword business method 21:49:02,083 INFO [STDOUT] Message sent successfully to remote queue. 21:49:02,130 INFO [STDOUT] Exiting EmailSystemBean.emailLostPassword business method> 21:49:02,130 INFO [STDOUT] *** OtherInterceptor exiting 21:49:02,130 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.emailLostPassword() took 241ms 21:49:02,130 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:02,179 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:02,180 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:02,185 INFO [STDOUT] *** EmailMDB.mdbInterceptor intercepting 21:49:02,185 INFO [STDOUT] ---------------- EMailMDB - Got message, sending email ---------------- 21:49:04,251 INFO [STDOUT] *** DefaultInterceptor intercepting sendBookingConfirmationMessage 21:49:04,251 INFO [STDOUT] *** TracingInterceptor intercepting sendBookingConfirmationMessage 21:49:04,251 INFO [STDOUT] *** OtherInterceptor intercepting sendBookingConfirmationMessage 21:49:04,251 INFO [STDOUT] *** AccountsConfirmInterceptor intercepting 21:49:04,395 INFO [STDOUT] *** AccountsConfirmInterceptor - recording confirmation 21:49:04,431 INFO [STDOUT] *** AccountsConfirmInterceptor - notifying accounts dept sendBookingConfirmationMessage 21:49:04,440 INFO [STDOUT] <In EmailSystemBean.sendBookingConfirmationMessage business method 21:49:04,455 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:04,455 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:04,458 INFO [STDOUT] *** DefaultInterceptor intercepting onMessage 21:49:04,459 INFO [STDOUT] ---------------- AccountsMDB - Got message Confirming order 100 ---------------- 21:49:04,459 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:04,466 INFO [STDOUT] Message sent successfully to remote queue. 21:49:04,467 INFO [STDOUT] Exiting EmailSystemBean.sendBookingConfirmationMessage business method> 21:49:04,467 INFO [STDOUT] *** AccountsConfirmInterceptor exiting 21:49:04,467 INFO [STDOUT] *** OtherInterceptor exiting 21:49:04,467 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingConfirmationMessage() took 216ms 21:49:04,467 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:04,478 INFO [STDOUT] *** EmailMDB.mdbInterceptor intercepting 21:49:04,478 INFO [STDOUT] ---------------- EMailMDB - Got message, sending email ---------------- 21:49:06,533 INFO [STDOUT] *** DefaultInterceptor intercepting sendBookingConfirmationMessage 21:49:06,533 INFO [STDOUT] *** TracingInterceptor intercepting sendBookingConfirmationMessage 21:49:06,533 INFO [STDOUT] *** OtherInterceptor intercepting sendBookingConfirmationMessage 21:49:06,533 INFO [STDOUT] *** AccountsConfirmInterceptor intercepting 21:49:06,547 INFO [STDOUT] *** AccountsConfirmInterceptor - order has already been confirmed aborting 21:49:06,548 INFO [STDOUT] *** AccountsConfirmInterceptor exiting 21:49:06,548 INFO [STDOUT] *** OtherInterceptor exiting 21:49:06,548 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingConfirmationMessage() took 15ms 21:49:06,548 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:08,577 INFO [STDOUT] *** AccountsInterceptor intercepting sendBookingCancellationMessage 21:49:08,577 INFO [STDOUT] *** AccountsCancelInterceptor intercepting sendBookingCancellationMessage 21:49:08,577 INFO [STDOUT] *** AccountsConfirmInterceptor - notifying accounts dept 21:49:08,593 INFO [STDOUT] *** DefaultInterceptor intercepting sendBookingCancellationMessage 21:49:08,593 INFO [STDOUT] *** OtherInterceptor intercepting sendBookingCancellationMessage 21:49:08,593 INFO [STDOUT] *** TracingInterceptor intercepting sendBookingCancellationMessage 21:49:08,593 INFO [STDOUT] <In EmailSystemBean.sendBookingCancellationMessage business method 21:49:08,605 INFO [STDOUT] Message sent successfully to remote queue. 21:49:08,606 INFO [STDOUT] Exiting EmailSystemBean.sendBookingCancellationMessage business method> 21:49:08,606 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingCancellationMessage() took 13ms 21:49:08,606 INFO [STDOUT] *** OtherInterceptor exiting 21:49:08,606 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:08,606 INFO [STDOUT] *** AccountsCancelInterceptor exiting 21:49:08,606 INFO [STDOUT] *** AccountsInterceptor exiting 21:49:08,617 INFO [STDOUT] *** EmailMDB.mdbInterceptor intercepting 21:49:08,617 INFO [STDOUT] ---------------- EMailMDB - Got message, sending email ---------------- 21:49:08,620 INFO [STDOUT] *** DefaultInterceptor intercepting onMessage 21:49:08,620 INFO [STDOUT] ---------------- AccountsMDB - Got message Cancelling order 100 ---------------- 21:49:08,620 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:10,628 INFO [STDOUT] <In EmailSystemBean.noop business method 21:49:10,628 INFO [STDOUT] Exiting EmailSystemBean.noop business method> 21:49:12,648 INFO [STDOUT] <In EmailSystemBean.noop2 business method 21:49:12,648 INFO [STDOUT] Exiting EmailSystemBean.noop2 business method>
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.
You can ignore the [WARN] messages:
21:49:02,179 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:02,180 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container