EJB 3 Interceptors

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 (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

Default
Class-level
Method-level
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:

*
org.jboss.tutorial.interceptor.bean.DefaultInterceptor

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:

*
org.jboss.tutorial.interceptor.bean.DefaultInterceptor
org.jboss.tutorial.interceptor.bean.AnotherInterceptor

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:


org.jboss.tutorial.interceptor.bean.EmailSystemBean
org.jboss.tutorial.interceptor.bean.OtherInterceptor

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

DefaultInterceptor (default)
TracingInterceptor (class-level)
OtherIntercetor (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.

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:

org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor

sendCancelMessage

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:


org.jboss.tutorial.interceptor.bean.EmailSystemBean
org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
sendBookingCancellationMessage

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:


MyBean
SomeInterceptor
overLoadedMethod

int
java.lang.String[][]

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(“”);
}

}}

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

org.jboss.tutorial.interceptor.bean.EmailSystemBean

noop2

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;

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 injects this into the field em
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.

org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor

sendCancelMessage

jms/ConnFactory
javax.jms.QueueConnectionFactory
Container
java:/ConnectionFactory

org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
cf

accountsQueue
javax.jms.Queue
Container
queue/tutorial/accounts

org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
queue

the Container looks up java:/ConnectionFactory in JNDI and creates the java:comp/env/jms/ConnFactory entry in the beans ENC, and following bean creation it injects the connction 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 beans ENC, and following bean creation it injects the connction factory into AccountsCancelInterceptor’s queue field.
Interceptor Ordering

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
Bean class interceptor method
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

org.jboss.tutorial.interceptor.bean.EmailSystemBean
sendBookingCancellationMessage

org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
org.jboss.tutorial.interceptor.bean.DefaultInterceptor
org.jboss.tutorial.interceptor.bean.OtherInterceptor
org.jboss.tutorial.interceptor.bean.TracingInterceptor

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

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=
Windows: $ set JBOSS_HOME=
$ 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]
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]
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]
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]
Calling EmailSystemBean.noop2()
17:35:21,640 INFO [STDOUT]

via: https://docs.jboss.org/ejb3/app-server/tutorial/interceptor/interceptor.html

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s