@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.
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(); }
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.
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 can be bound using xml or 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 { ... }
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.
Just as we can define default and class-level interceptors, we can also bind an interceptor to a particular business method within a 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
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.
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[][])
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>
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().
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;
<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>
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.
<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
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.
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 exitingCalling 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>