AOP in the EJB world: interceptors

Have you ever been in a situation in which your requirements changed toward the end of the project and you were asked to add some common missing feature, such as logging or auditing, for EJBs in your application? Adding logging code in each of your EJB classes would be time consuming, and this common type of code also causes maintainability issues and requires you to modify a number ofJava classes. Well, EJB 3 interceptors solve this problem. In our example you simply create a logging interceptor that does the logging, and you can make it the default interceptor for your application. The logging interceptor will be executed when any bean method is executed. If the requirement for logging changes, then you have to change only one class. In this section, you’ll learn how interceptors work.

What is AOP?

It is very likely you have come across the term aspect-oriented programming (AOP). The essential idea behind AOP is that for most applications, common application code repeated across modules not necessarily for solving the core business problem are considered as infrastructure concerns.

The most commonly cited example of this is logging, especially at the basic debugging level. To use our ActionBazaar example, let’s assume that we log the entry into every method in the system. Without AOP, this would mean adding logging statements at the beginning of every single method in the system to log the action of "entering method XX"! Some other common examples where AOP applies are auditing, profiling, and statistics.


The common term used to describe these cases is crosscutting concerns—concerns that cut across application logic. An AOP system allows the separation of crosscutting concerns into their own modules. These modules are then applied across the relevant cross section of application code, such as the beginning of every method call. Tools like AspectJ have made AOP relatively popular. For great coverage of AOP, read AspectJ in Action by Ramnivas Laddad (Manning, 2003).

EJB 3 supports AOP-like functionality by providing the ability to intercept business methods and lifecycle callbacks. Now buckle up and get ready to jump into the world of EJB 3 interceptors, where you’ll learn what interceptors are and how to build business method and lifecycle callback interceptors.

What are interceptors?

Essentially the EJB rendition of AOP, interceptors are objects that are automatically triggered when an EJB method is invoked (interceptors are not new concepts and date back to technologies like CORBA). While EJB 3 interceptors provide sufficient functionality to handle most common crosscutting concerns (such as in our logging example), they do not try to provide the level of functionality that a full-scale AOP package such as AspectJ offers. On the flip side, EJB 3 interceptors are also generally a lot easier to use.

Recall our discussion in section 5.1 on how the EJB object provides services such as transactions and security. In essence, the EJB object is essentially a sophisticated built-in interceptor that makes available a whole host of functionality. If you wanted to, you could create your own EJB-esque services using interceptors.

In the pure AOP world, interception takes place at various points (called point cuts) including at the beginning of a method, at the end of a method, and when an exception is triggered. If you are familiar with AOP, an EJB interceptor is the most general form of interception—it is an around invoke advice. EJB 3 interceptors are triggered at the beginning of a method and are around when the method returns; they can inspect the method return value or any exceptions thrown by the method. Interceptors can be applied to both session and message-driven beans.

Let’s examine business method interceptors further by implementing basic logging on the PlaceBid session bean from topic 2. Once you understand how this works, applying it to an MDB should be a snap. Figure 5.3 shows a business method interceptor that implements common logging code in the ActionBazaar application.

 Business interceptors are typically used to implement common code. The Action-BazaarLogger implements common logging code used by all EJBs in the ActionBazaar system.

Figure 5.3 Business interceptors are typically used to implement common code. The Action-BazaarLogger implements common logging code used by all EJBs in the ActionBazaar system.

Listing 5.2 contains the code for our interceptors. The interceptor attached to the addBid method will print a log message to the console each time the method is invoked. In a real-world application, this could be used as debugging information (and perhaps printed out using java.util.logging or Log4J).

Listing 5.2 EJB business method interceptors

Listing 5.2 EJB business method interceptors

Let’s take a bird’s-eye view of this code before analyzing each feature in detail in the coming sections. The interceptor class, ActionBazaarLogger, is attached to the addBid method of the PlaceBid stateless session bean using the @javax. interceptor.Interceptors annotation b. The ActionBazaarLogger object’s log-MethodEntry method is annotated with @javax.interceptor.AroundInvoke and will be invoked when the addBid method is called Q. The logMethodEntry method prints a log message to the system console, including the method name entered using the javax.interceptor.InvocationContext. Finally, the invocation context’s proceed method is invoked to signal to the container that the addBid invocation can proceed normally.

We will now start a detailed analysis of the code, starting with attaching the interceptor using the @Interceptors annotation.

Specifying interceptors

The @Interceptors annotation allows you to specify one or more interceptor classes for a method or class. In listing 5.2 we attach a single interceptor to the addBid method:

tmp24198_thumb

You can also apply the @Interceptors annotation to an entire class. When you do, the interceptor is triggered if any of the target class’s methods are invoked. For example, if the ActionBazaarLogger is applied at the class level as in the following code, our logMethodEntry method will be invoked when the PlaceBid class’s addBid or addTimeDelayedBid method is called by the client (imagine that the addTimeDelayedBid method adds a bid after a specified interval of time):

tmp24199_thumb

As we explained, the @Interceptors annotation is fully capable of attaching more than one interceptor either at a class or method level. All you have to do is provide a comma-separated list as a parameter to the annotation. For example, a generic logger and a bidding statistics tracker could be added to the PlaceBid session bean as follows:

tmp24-200_thumb

Besides specifying method- and class-level interceptors, you can create what is called a default interceptor. A default interceptor is essentially a catchall mechanism that attaches to all the methods of every bean in the EJB module. Unfortunately, you cannot specify these kinds of interceptors by using annotations and must use deployment descriptor settings instead. We won’t discuss deployment descriptors in any great detail at this point, but we’ll show you how setting the ActionBazaarLogger class as a default interceptor for the ActionBazaar application might look:

tmp24201_thumbtmp24202_thumb

An interesting question that might have already crossed your mind is what would happen if you specified default, class-, and method-level interceptors for a specific target method (yes, this is perfectly legal). In which order do you think the interceptors would be triggered?

Somewhat counterintuitive to how Java scoping typically works, the interceptors are called from the larger scope to the smaller scope. That is, the default interceptor is triggered first, then the class-level interceptor, and finally the method-level interceptor. Figure 5.4 shows this behavior.

 The order in which business method interceptors are invoked. Default interceptors apply to all methods of all EJBs in an ejb-jar package. Class-level interceptors apply to all methods of a specific class. Method-level interceptors apply to one specific method in a class. Default application-level interceptors are invoked first, then class-level interceptors, then method-level interceptors.

Figure 5.4 The order in which business method interceptors are invoked. Default interceptors apply to all methods of all EJBs in an ejb-jar package. Class-level interceptors apply to all methods of a specific class. Method-level interceptors apply to one specific method in a class. Default application-level interceptors are invoked first, then class-level interceptors, then method-level interceptors.

If more than one interceptor is applied at any given level, they are executed in the order in which they are specified. In our ActionBazaar-Logger and BidStatisticsTracker example, the ActionBazaarLogger is executed first since it appears first in the comma-separated list in the @interceptors annotation:

tmp24204_thumb

Unfortunately, the only way to alter this execution order is to use the interceptor-order element in the deployment descriptor; there are no annotations for changing interceptor order. However, you can disable interceptors at the default or class levels if you need to. Applying the @javax.interceptor.Exclude-Defaultinterceptors annotation on either a class or a method disables all default interceptors on the class or method. Similarly the @javax.interceptor.Exclude-Classinterceptors annotation disables class-level interceptors for a method. For example, both default and class-level interceptors may be disabled for the addBid method using the following code:

tmp24205_thumbtmp24206_thumb

Next we’ll take a detailed look at the interceptor classes themselves.

Implementing business interceptors

Like the EJB lifecycle callback methods that we discussed in topics 3 and 4, business interceptors can be implemented either in the bean class itself or in separate classes. However, we recommend that you create interceptor methods external to the bean class, because that approach allows you to separate crosscutting concerns from business logic and you can share the methods among multiple beans. After all, isn’t that the whole point of AOP?

As you can see in listing 5.2, following the general EJB 3 philosophy, an interceptor class is simply a POJO that may have a few annotations.

Around invoke methods

It’s important to realize that an interceptor must always have only one method that is designated as the around invoke (AroundInvoke) method. Around invoke methods must not be business methods, which means that they should not be public methods in the bean’s business interface(s).

An around invoke method is automatically triggered by the container when a client invokes a method that has designated it to be its interceptor. In listing 5.2, the triggered method is marked with the @AroundInvoke annotation:

tmp24207_thumb

In effect, this means that the logMethodEntry method will be executed whenever the ActionBazaarLogger interceptor is triggered. As you might gather from this code, any method designated AroundInvoke must follow this pattern:

tmp24208_thumb

The InvocationContext interface passed in as the single parameter to the method provides a number of features that makes the AOP mechanism extremely flexible. The logMethodEntry method uses just two of the methods included in the interface. The getMethod().getName() call returns the name of the method being intercepted—addBid in our case.

The call to the proceed method is extremely critical to the functioning of the interceptor. In our case, we always return the object returned by Invocation-Context.proceed in the logMethodEntry method. This tells the container that it should proceed to the next interceptor in the execution chain or call the intercepted business method. On the other hand, not calling the proceed method will bring processing to a halt and avoid the business method (and any other interceptor down the execution chain) from being called.

This feature can be extremely useful for procedures like security validation. For example, the following interceptor method prevents the intercepted business method from being executed if security validation fails:

tmp24209_thumb

The InvocationContext interface

The InvocationContext interface has a number of other useful methods. Here is the definition of the interface:

tmp24210_thumb

The getTarget method retrieves the bean instance that the intercepted method belongs to. This method is particularly valuable for checking the current state of the bean through its instance variables or accessor methods.

The getMethod method returns the method of the bean class for which the interceptor was invoked. For AroundInvoke methods, this is the business method on the bean class; for lifecycle callback interceptor methods, getMethod returns null.

The getParameters method returns the parameters passed to the intercepted method as an array of objects. The setParameters method, on the other hand, allows us to change these values at runtime before they are passed to the method.

These two methods are helpful for interceptors that manipulate bean parameters to change behavior at runtime.

An interceptor in ActionBazaar that transparently rounds off all monetary values to two decimal places for all methods across the application could use the getParameters and setParameters methods to accomplish its task.

The key to understanding the need for the invocationContext.getContext-Data method is the fact that contexts are shared across the interceptor chain for a given method. As a result, data attached to an invocationContext can be used to communicate between interceptors. For example, assume that our security validation interceptor stores the member status into invocation context data after the user is validated:

tmp24211_thumb

As you can see, the invocation context data is simply a Map used to store name-value pairs. Another interceptor in the invocation chain can now retrieve this data and take specific actions based on the member status. For example, a discount calculator interceptor can reduce the ActionBazaar item listing charges for a Gold member. The code to retrieve the member status would look like this:

tmp24-212_thumb

The following is the Aroundinvoke method of the DiscountVerifierinterceptor that actually uses the invocation context as well as most of the methods we discussed earlier:

tmp24-213_thumb

You can throw or handle a runtime or checked exception in a business method interceptor. If a business method interceptor throws an exception before invoking the proceed method, the processing of other interceptors in the invocation chain and the target business method will be terminated.

Recall our discussion on lifecycle callback methods in topics 3 and 4. This isn’t readily obvious, but lifecycle callbacks are a form of interception as well. While method interceptors are triggered when a business method is invoked, life-cycle callbacks are triggered when a bean transitions from one lifecycle state to another. Although this was not the case in our previous lifecycle examples, in some cases such methods can be used for crosscutting concerns (e.g., logging and profiling) that can be shared across beans. For this reason, you can define lifecycle callbacks in interceptor classes in addition to business method interceptors. Let’s take a look at how to do this next.

Lifecycle callback methods in the interceptor class

Recall that the @PostConstruct, @PrePassivate, @PostActivate, and @PreDestroy annotations can be applied to bean methods to receive lifecycle callbacks. When applied to interceptor class methods, lifecycle callbacks work in exactly the same way. Lifecycle callbacks defined in an interceptor class are known as lifecycle callback interceptors or lifecycle callback listeners. When the target bean transitions lifecy-cles, annotated methods in the interceptor class are triggered.

The following interceptor class logs when ActionBazaar beans allocate and release resources when beans instances are constructed and destroyed:

tmp24214_thumb

As the code sample shows, lifecycle interceptor methods cannot throw checked exceptions (it doesn’t make sense since there is no client for lifecycle callbacks to bubble a problem up to).

Note that a bean can have the same lifecycle callbacks both in the bean itself as well as in one or more interceptors. That is the whole point of calling the invoca-tionContext.proceed method in lifecycle interceptor methods as in the resource logger code. This ensures that the next lifecycle interceptor method in the invocation chain or the bean lifecycle method is triggered. There is absolutely no difference between applying an interceptor class with or without lifecycle callbacks. The resource logger, for example, is applied as follows:

tmp24215_thumb

You might find that you will use lifecycle callbacks as bean methods to manage resources a lot more often than you use interceptor lifecycle callbacks to encapsulate crosscutting concerns such as logging, auditing, and profiling. However, interceptor callbacks are extremely useful when you need them.

As a recap, table 5.3 contains a summary of both business method interceptors and lifecycle callbacks.

Table 5.3 Differences between lifecycle and business method interceptors. Lifecycle interceptors are created to handle EJB lifecycle callbacks. Business method interceptors are associated with business methods, and are automatically invoked when a user invokes the business method.

Supported Feature

Lifecycle Callback Methods

Business Method Interceptor

Invocation

Gets invoked when a certain lifecycle event occurs.

Gets invoked when a business method is called by a client.

Location

In a separate Interceptor class or in the bean class.

In the class or an interceptor class.

Method signature

tmp24-216 tmp24-217

Annotation

tmp24-218 tmp24-219

Exception handling

May throw runtime exceptions but must not throw checked exceptions. May catch and swallow exceptions. No other lifecycle callback methods are called if an exception is thrown.

May throw application or runtime exception.

May catch and swallow runtime exceptions.

No other business interceptor methods or the business method itself are called if an exception is thrown before calling the proceed method.

Supported Feature

Lifecycle Callback Methods

Business Method Interceptor

Transaction and security context

No security and transaction context. Transaction and security are discussed in chapter 6.

Share the same security and transaction context within which the original business method was invoked.

This is all we want to say about interceptors right now. Clearly, interceptors are an extremely important addition to EJB. It is likely that the AOP features in future releases of EJB will grow more and more robust. Interceptors certainly have the potential to evolve into a robust way of extending the EJB platform itself, with vendors offering new out-of-the-box interceptor-based services.

Let’s move on to the final vital EJB 3 feature we’ll cover in this topic: the timer service. Timers can be used only by stateless session beans and MDBs.

Next post:

Previous post: