Interceptors (Enterprise JavaBeans 3.1)

Intercepting Methods

To understand when to use interceptors, we’ll first take a look at an example of poor code reuse.

We’ll be building an online television station, and part of the backing application is a TunerEJB. Incoming requests to get a channel will return an InputStream pointer to read in the content:

tmp97-271_thumbtmp97-272_thumb

Management may have the requirement that we keep track of all incoming requests to the system so that we can analyze its real-world use during the product launch. So, as obedient developers, we might code something into the implementation as follows:

tmp97-273_thumb[1]

Already we have a few lines of code embedded into our business method that have nothing to do with obtaining channel content. We might encourage code reuse by extracting out into some common utilities, but even then every function that needs this behavior will have to explicitly call the shared logic.

Additionally, company management may decide to restrict access to some methods, from some callers, at some times. As you can see, we very quickly get to a situation in which we’re layering unrelated business logic across the system.

In fact, there are a lot of design flaws:

• The getChannel() method is polluted with code that has nothing to do with the business logic of the method. Not only has the developer added seven lines of code to the method, but he has also made the code a bit harder to read.

• It is difficult to turn auditing off and on since you have to comment out the code and recompile the bean class.

• This auditing logic is obviously a template that could be reused across many methods of your application. If this code is littered throughout your EJB methods, you would need to potentially modify a lot of different classes to expand the functionality.

Interceptors define a much cleaner separation for business logic that may run across many modules in your system. They provide a mechanism to encapsulate this business logic and an easy way to apply it to your methods without making the code harder to read. Interceptors provide a structure for this type of behavior so that it can easily be extended and expanded in one class. Finally, they provide a simple, configurable mechanism for applying their behavior wherever you like.

Interceptor Class

Encapsulating this auditing logic into an interceptor is as simple as creating a plain Java class that has a method with the @javax.interceptor.AroundInvoke annotation and the following signature:

tmp97-274_thumb

The @AroundInvoke method in an interceptor class does just what it implies. It wraps around the call to your business method and is actually invoked in the same Java call stack and in the same transaction and security context as the bean method it is intercepting. The javax.interceptor.InvocationContext parameter is a generic representation of the business method the client is invoking. You can obtain information such as the target bean instance on which you are invoking, access to its parameters expressed as an array of objects, and a reference to a java.lang.reflect.Method object that is the generic representation of the actual invoked method. InvocationContext is also used to drive the invocation. Let’s convert our auditing logic into an @AroundInvoke method:

tmp97-275_thumbtmp97-276_thumb[6]

The @AroundInvoke method of our interceptor class is the audit() method. It looks pretty much the same as the code in our getChannel() method, except that the business logic is gone and all that is left is our generic auditing logic. Additionally, we can take advantage of the descriptor given to us (InvocationContext) for the current request rather than construct our own representation. After we’ve recorded the invocation, the InvocationContext.proceed() method is called. If another interceptor must be invoked as part of the method call, then proceed() calls the @AroundInvoke method of that other interceptor. If no other interceptors need executing, then the EJB container calls the bean method on which the client is invoking. Because the getChannel() method is invoked in the same Java call stack as the business method on which you are invoking, proceed() must be called by the interceptor code, or the actual EJB method is not called at all.

The only responsibility of our auditor is to record the incoming request for future analysis. The InvocationContext.getMethod() operation gives us access to the java.lang.reflect.Method object that represents the actual bean method being invoked. Besides getMethod(), the InvocationContext interface has some other interesting methods:

tmp97-277_thumb

The getTarget() method returns a reference to the target bean instance. We could change our audit() method to also print out the parameters of the invoked bean method by using the getParameters() method. The setParameters() method allows you to actually modify the parameters of the method that is being invoked. Use this with care. The getContextData() method returns a Map object that is active for the entire method invocation. Interceptors can use this map to pass contextual data between themselves within the same method invocation.

Applying Interceptors

Now that the interceptor class has been written, it is time to apply it to an EJB. One or more interceptors can be applied to all EJBs within a deployment (default interceptors), to all methods of one EJB, or to a single method of an EJB. Interceptors can be applied via an annotation or by using an XML deployment descriptor. We discuss all options in this section.

Annotated methods and classes

The @javax.interceptor.Interceptors annotation can be used to apply interceptors to individual methods or to every method in the EJB’s bean class:

tmp97-278_thumbtmp97-279_thumb

When the @Interceptors annotation is applied to an individual method, that interceptor is executed only when that particular method is invoked. If you use the @Interceptors annotation on the bean class, all interceptor classes listed will interpose on every method invocation of every business method of the EJB.

Therefore, auditing all the methods of our TunerEJB is as easy as applying the @Interceptors annotation at the class level:

tmp97-280_thumb

We may also have another aspect in charge of blocking access to a particular channel when management would like to restrict access. This can be declared to run only on specific methods, like so:

tmp97-281_thumb

Whether you want to apply the @Interceptors annotation on the class or the method is really determined by your requirements.

Applying interceptors through XML

Although the @Interceptors annotation allows you to apply the auditing interceptor easily, it does force you to modify and recompile your class every time you want to remove profiling from or add it to a particular method or EJB. Unless an interceptor is a required part of your business logic, it may not be the best idea to annotate your code; using XML bindings instead might be a better approach. Because the EJB 3.x specification supports partial XML deployment descriptors, it is quite painless and easy to apply an interceptor through XML:

tmp97-282_thumbtmp97-283_thumb[1]

The preceding XML is a complete deployment descriptor. The <interceptor-binding> element specifies that we want the RecordingAuditor interceptor to be executed whenever the getChannel() method of the TunerEJB is invoked. Because the getChannel() method is not overloaded in the bean class, the <method-params> element isn’t explicitly required.

If you want to apply an interceptor to every business method of a particular EJB, then leave out the <method-name> and <method-params> elements:

tmp97-284_thumb[1]

As you can see, you can use an XML deployment descriptor only when you need it and leave the rest of the EJB metadata expressed as annotations. In this particular interceptor use case, it makes more sense to use XML than an annotation because auditing is probably something you’d want to do for a limited time.

Default interceptors

XML has some other advantages as well. For instance, the <ejb-name> element in an <interceptor-binding> can take a wildcard. In this case, you are applying one or more interceptors that you declare in the interceptor-binding to every EJB in that particular JAR file deployment:

tmp97-285_thumb[1]tmp97-286_thumb[1]

Disabling interceptors

If you are using default interceptors or class-level interceptors, there may be times when you want to disable them for a particular EJB or for a particular method of an EJB. You can do this via an annotation or through XML. Let’s look at disabling default interceptors first.

In the preceding XML, we have enabled the RecordingAuditor interceptor for every EJB deployed in the particular JAR file in which the XML is placed. Let’s say we do not want the auditor to be executed for our TunerEJB. We can turn off all default interceptors by using the @javax.interceptor.ExcludeDefaultInterceptors annotation:

tmp97-287_thumb

In the preceding example, the RecordingAuditor will not be executed. Just because the @ExcludeDefaultInterceptors annotation has been used, it does not mean we cannot specify an @Interceptors annotation that triggers other interceptor classes. This exclusion can be done within XML as well:

tmp97-288_thumb[1]tmp97-289_thumb

What we’re really doing in the preceding examples is giving the TunerEJB a brand-new interceptor stack that overrides and supersedes any default interceptors.

The same overriding and disabling of interceptors can be done at the method level as well. You can turn off interceptors entirely for a particular method by using the @javax.interceptor.ExcludeDefaultInterceptors and @javax.interceptor.Exclude ClassInterceptors annotations:

tmp97-290_thumb[1]

The @ExcludeClassInterceptors annotation turns off any applied class-level interceptors, and the @ExcludeDefaultInterceptors annotation turns off any default interceptors defined in XML. You could also specify an @Interceptors annotation on the getChannel() method to define a different interceptor stack compared to the rest of the methods in the bean class. This is also available in XML format.

Usually, you will not be concerned with disabling interceptors, but it is good to know you have the tools to do so if you need to.


Interceptors and Injection

Interceptors belong to the same ENC as the EJBs they intercept. Like the EJBs they intercept, interceptor classes have full support for all the injection annotations, as well as injection through XML. So, you can use annotations such as @Resource, @EJB, and @PersistenceContext within your interceptor class if you so desire. Let’s illustrate this in a revised view of our auditing interceptor:

tmp97291_thumb_thumbtmp97292_thumb1_thumb1

The purpose of this interceptor is to log in a persistent view every method invocation done on a particular bean so that an audit trail is created. From this audit trail, system administrators can research security breaches or replay the actions of a particular user. The interceptor obtains the calling user by invoking getCallerPrincipal() on the javax.ejb.EJBContext injected into the beanContext member variable. It allocates an AuditedInvocation object and sets properties such as the method being invoked and the calling principal. We could even fashion AuditedInvocation as an entity bean, and persist to the database by an injected EntityManager. Any members the container may inject into a session or message-driven bean is also available to interceptors.

As with bean classes, interceptor injection annotations create additional entries in the ENC of the EJB to which the interceptor class is bound.

As always, if you do not want to use annotations to inject dependencies into your interceptor classes, the XML alternative is to define an <interceptor> element within your ejb-jar.xml deployment descriptor.

Intercepting Lifecycle Events

Not only can you intercept EJB method invocations, but you can also intercept EJB lifecycle events. These callbacks can be used to initialize the state of your EJB bean classes, as well as the interceptor class itself. Lifecycle interception looks very similar to the @AroundInvoke style:

tmp97293_thumb_thumb

To intercept an EJB callback, define a method within your interceptor class that is annotated with the callback in which you are interested. The return value of the method must be void because EJB callbacks have no return value. The method name can be anything and must not throw any checked exceptions (no throws clause). Invocation Context is the only parameter to this method. As with @AroundInvoke methods, callback interception is invoked in one big Java call stack. This means you must call Invocation Context.proceed() to complete the lifecycle event. When calling proceed(), the next interceptor class that has the same callback is invoked. If there are no other interceptors, then the callback method of the EJB’s bean class is invoked, if one exists. If the EJB has no callback method, then proceed() is a no-op. Because there may be no callback method, InvocationContext.getMethod() always returns null.

Custom Injection Annotations

Why would you want to intercept an EJB callback? One concrete example is when you want to create and define your own injection annotations. The EJB specification has a bunch of annotations for injecting Java EE resources, services, and EJB references into your bean classes. Some application servers or applications like to use JNDI as a global registry for configuration or for non-Java EE services. Unfortunately, the specification defines no way to inject something directly from global JNDI into your beans. What we can do is define our own annotation for providing this functionality and implement it as an interceptor.

The first thing we must do is to define the annotation we will use to inject from JNDI:

tmp97294_thumb_thumb

The value() attribute of the @org.ejb3.annotations.JndiInjected annotation is the global JNDI name of the object we want injected into our field or setter method. Here is an example of how we might use this custom annotation:

tmp97295_thumb_thumb

Some applications might be interested in obtaining a reference to the Java EE JTA Transaction Manager service. Many application servers store a reference to this service in global JNDI. In this instance, we use the @JndiInjected annotation to pull a reference to the Transaction Manager directly into the field of our session bean. Now that we have defined our custom injection annotation and we’ve defined how it might be used, we need to code the interceptor class that implements this behavior:

tmp97296_thumb1_thumb1tmp97297_thumb_thumb

The jndiInject() method is annotated with @javax.annotation.PostConstruct to tell the EJB container that the JndiInjector interceptor is interested in intercepting that particular EJB callback. The method begins by obtaining a reference to the bean instance it is intercepting. It then reflects on the object to find all methods and fields that are annotated with @JndiInjected, looks up the referenced JNDI name, and initializes the field or method of the target bean instance. Notice that this is done in a try/catch block. When intercepting a callback method, you can never throw a checked exception; therefore, all checked exceptions must be caught and wrapped in an EJBException.

Now that the interceptor class has been implemented, we can apply this interceptor to our EJBs just as we’ve done before.

One particularly interesting thing about this example is that it shows that you can use EJB interceptors as a framework for writing custom annotations that add your own customer behavior to your EJBs. Default interceptors, through XML, give you a clean, simple way of applying the interceptors that implement the behavior of your annotations. Finally, this custom behavior is portable and can be used in any vendor implementation. Not only is EJB 3.x easy to use, but it is easy to extend as well.

Exception Handling

Exception handling with interceptors is simple yet powerful. Since interceptors sit directly in the Java call stack of the bean method or callback that is being invoked, you can put a try/catch/finally block around the InvocationContext.proceed() method. You can abort an invocation before it reaches the actual bean method by throwing an exception within the @AroundInvoke or callback method. You are also allowed to catch a bean-method-thrown exception and throw a different exception or suppress the exception. With @AroundInvoke interception, you are even allowed to retry the bean method call after catching an exception from the bean method. Let’s look at some examples.

Aborting a Method Invocation

It’s conceivable that the management of our online television station would like to restrict access to a premium channel at their discretion. In this case, we might short-circuit an incoming request to instead return a descriptive Exception:

tmp97298_thumb1_thumb1

Here we’ve encapsulated the logic determining whether we should allow the request to continue along for processing, ultimately to return content for Channel 2. In this example we’re, in effect, implementing our own custom security framework. EJB security is pretty basic and sometimes you have more specific contextual requirements. For instance, you may want to integrate your EJB with a rules engine that analyzes the user as well as the method and parameters to determine whether the user is allowed to invoke the method. This can also be done from within an interceptor.

Catch and Rethrow Exceptions

Besides aborting a given method invocation, you can also catch exceptions thrown by the bean method within the interceptor’s @AroundInvoke method. For example, you can use interceptor classes as an abstraction mechanism to create exception-handling frameworks. Consider JDBC and the java.sql.SQLException. When an SQLException is thrown, your code, programmatically, does not know the cause of the exception without looking at the error number or the message of the exception. Unfortunately, error codes and messages differ per database vendor and thus, if you wanted to handle certain exceptions in certain ways, your code would be nonportable between database vendors.

Let’s take two common SQLExceptions: deadlocking and cursor not available. First, we will create concrete exceptions that extend SQLException:

tmp97299_thumb_thumb

With these exceptions, we have abstracted away the dependency on the error number to determine the actual database error that occurred. Our client code can use these exceptions in a portable way and not be concerned with the underlying database vendor. But before we can use these exceptions, we need to write the interceptor class that does the exception handling:

tmp97300_thumb_thumb

The @AroundInvoke method simply catches any SQLException thrown by the bean method and converts it to an appropriate exception type that you can catch in your client code. Of course, there would be one exception-handler interceptor class per database vendor. Here’s how your application code could then take advantage of this interceptor behavior:

tmp97301_thumb_thumbtmp97302_thumb_thumb

So, combining the exception handler interceptor with EJB invocations allows you to have specific code that handles specific database errors such as deadlock, without having to worry that your code is not portable between vendors.

Interceptor Lifecycle

Interceptor classes have the same lifecycles as the EJBs they intercept. Consider an interceptor class as an extension of the EJB’s bean instance. They are created along with bean instances. They are destroyed, passivated, and activated along with their bean instances as well. Also, it is important to note that interceptor classes have the same restrictions as the beans to which they are attached. For instance, you cannot inject an extended persistence context into an interceptor class if that interceptor does not intercept a stateful session bean.

Because interceptors have lifecycles and hook into lifecycle events, they can also hold internal state. This might be extremely useful when you want the interceptor class to obtain an open connection to a remote system and then close that connection at destroy time. You may also be interested in maintaining state that is particular to the bean instance on which the interceptor class is intercepted. Maybe you have a custom injection annotation that you have built and it needs special cleanup after the bean instance is destroyed. You can hold internal state within the interceptor class and do the cleanup when the interceptor and bean instance are destroyed.

Bean Class @AroundInvoke Method

This topic has mostly discussed interceptor classes. @AroundInvoke methods can also exist inside EJB bean classes. When used inside a bean class, the @AroundInvoke method will be the last “interceptor” to be invoked before the actual bean method:

tmp97303_thumb_thumbtmp97304_thumb_thumb

This is a simple example of a bean class @AroundInvoke method. For what sorts of things would you want to use it? You may want to have a dynamic implementation of your bean class, or you may have an interceptor whose logic is specific to the bean.

Next post:

Previous post: