EJB 3: From legacy technology to secret weapon

Four factors that streamline and modernize EJB 3 development

1 2 3 4 5 6 Page 6
Page 6 of 6

Interceptors

EJB 3 also gives you a way to do lightweight aspect-oriented programming (AOP). The principle of AOP is as simple as the principle of DI. The idea behind AOP is strict separation between pure business logic and nonfunctional code. You provide the nonfunctional code -- the aspects -- once and reuse it in different places. The reuse, however, happens externally, in the sense that the business-logic classes do not even know that they are decorated with additional functionality. In fact AOP could be explained as a flexible and configurable decorator that can be applied to methods, attributes, or constructors in declarative manner.

The EJB container comes with some built-in, highly reusable aspects. The availability of remote EJBs via SOAP-based Web services, RESTful services, or even Internet Inter-ORB Protocol (IIOP) remoting is nothing other than decoration of existing functionality with the crosscutting aspect of "remoting." Transaction and exception handling, security, concurrency, state management, and even persistence are classical aspects that the J2EE platform has supported from the beginning -- long before the term AOP became popular. Application-defined aspects were introduced with the EJB 3.0 specification. Before then, only the Web container could intercept incoming requests with the servlet filter.

In a well-designed Java EE application, session beans implement the major portion of business logic. This logic is functional or even procedural. Session beans are the natural place for the use of AOP ideas. Session beans mainly consist of methods and a default constructor. Attributes are very rare and mostly used in stateful session beans. All other attributes are not client specific and are shared by all instances. References to EntityManager, JMS resources, or other EJBs are typical examples for such technical state.

The Java EE aspects are called interceptors. Comparing them to fully fledged AOP frameworks is not accurate, because they are optimized for use with EJBs and so are rather limited. An EJB interceptor can be applied only to a particular EJB's methods. You can't apply interceptors to constructor invocations or attribute access. Interceptors are nevertheless absolutely sufficient for the most common use cases. You can enable an interceptor either with an annotation or in a deployment descriptor. (In this respect, interceptors are conceptually identical to the DI approach.)

An EJB 3 interceptor is just a Java class with one annotated method, as in Listing 15:

Listing 15. A tracing interceptor

public class TracingInterceptor {
@AroundInvoke
public Object trace(InvocationContext invocationContext) throws Exception{
System.out.println("Method: " + invocationContext.getMethod());
return invocationContext.proceed();
}
}

The actual call to the EJB is performed after the interceptor's execution. The interceptor has full control of the execution. It can invoke the method several times, change the parameters and return values, transform exceptions, and so on. The implementation of the interceptor is independent of the business logic. You activate an interceptor for a particular EJB by using the @Interceptors annotation, as in Listing 16:

Listing 16. Declaring an interceptor using an annotation

@Stateless
@Interceptors({TracingInterceptor.class, PerformanceMeasurement.class})
public class HelloBean implements Hello {

public String sayHello() {
return "Hello from Bean";
}
}

The annotation's value is an array of classes. The array's order is equivalent to the interception order.

Declaration of interceptors with annotations is convenient because it can fully leverage IDE support. Because the @Interceptors annotation refers to the interceptors as classes and not as Strings, your IDE and the compiler provide a higher comfort level by ensuring consistency and providing auto-completion.

On the other hand, because the interceptors are declared in the business code, every change requires recompilation and redeployment of the whole application. This could become a problem for "utility" interceptors that are only needed in the development phase or only activated for troubleshooting.

Fortunately, you can declare and apply interceptors in the standard deployment descriptors as well, as in Listing 17:

Listing 17. Declaring an interceptor in the XML deployment descriptor

<ejb-jar ...>
<!-- declaration (needed once) -->
   <interceptors>
      <interceptor>
         <interceptor-class>...PerformanceMeasurement</interceptor-class>
      </interceptor>
   </interceptors>

<!-- interceptor activation -->
   <assembly-descriptor>
      <interceptor-binding>
         <ejb-name>HelloBean</ejb-name>
            <interceptor-order>
               <interceptor-class>...PerformanceMeasurement</interceptor-class>
            </interceptor-order>
      </interceptor-binding>
   </assembly-descriptor>
</ejb-jar>

The first part of the configuration in Listing 17 is the actual declaration of the interceptor. It is needed only once. In the second part, the already declared interceptor can be applied to all beans from the ejb-jar, a particular bean, or even a specific, overloaded method.

The XML configuration is not only more flexible but also more powerful. Only in the deployment descriptor can you apply "default" interceptors that can intercept all beans in the module. But you should have additional good reasons for introducing XML deployment descriptors. They are harder to create and maintain. The XML configuration must be versioned together with source code; it's equally important and must be maintained with the same care. And although the application is more flexible and configurable with externalized XML configuration, this advantage loses its importance after deployment. No one will change the XML configuration in production without a test phase, so the additional flexibility is useful only in the development phase.

Another argument for the annotation-driven approach is the lower amount of "magic." The relation between the main concern (the session bean) and the cross-cutting concern (the interceptor) is self-documented. Refactoring of the interceptor -- such as renaming or moving it to other package -- can be performed in any average IDE without specific Java EE or EJB support.

Versatility of interceptors

Interceptors are not just POJOs; they come with some interesting features. They participate in transactions and support DI. So an interceptor can even use the functionality of another EJB directly, via DI, as the auditing code in Listing 18 does.

Listing 18. Injecting an EJB into an interceptor

public class AuditInterceptor {

@EJB
private Audit audit;

@AroundInvoke
public Object trace(InvocationContext invocationContext) throws Exception{
String info = ("Method: " + invocationContext.getMethod() + "\n");
info += ("Object: " + invocationContext.getTarget().getClass().getName());
this.audit.audit(info);
return invocationContext.proceed();
}
}

This feature makes it convenient to access an EJB's existing logic with the services it provides, such as transactions, concurrency, or security. This DI itself can be configured in the same manner: with annotations, with XML, with both combined, or by relying on convention and using the default implementation. Audit is a ubiquitous and rather trivial example for AOP. Interceptors can be used for a variety of other real-world purposes. Some ideas include:

Interceptors and AOP

Interceptors help you to realize nonfunctional requirements without touching the business code. They aren't as powerful as fully-fledged AOP languages but are sufficient for most use cases. AOP and interceptors in the real world are less common than you might think. The Java EE container comes with a bunch of useful aspects that can be enabled declaratively without any coding. Java EE developers don't even need to know that persistence, transactions, concurrency, or conversational state are actually aspects.

  • Ensuring preconditions: Interceptors have access to the intercepted EJB -- not only to the methods, but to their parameters with annotations as well. It is relatively easy to evaluate the annotations and ensure the parameters' validity. For example, the annotation @NotNull already implies that the method should be invoked with a non-null parameter. With this approach, validation of the preconditions could be easily moved from the EJB into a reusable interceptor.
  • Ensuring service level agreements (SLAs):This case is similar to ensuring the preconditions. You could measure the performance of a method and escalate all too-slow invocations using, for example, a JMS topic wrapped in a session bean. This lightweight monitoring capability is especially important in service-oriented architecture (SOA) environments, where multiple clients must rely on the availability of a single, reusable service.
  • Enhancing DI capabilities: An interceptor can access the intercepted EJB instance directly, so it can access its fields and search for annotations. Additional values can be injected directly into a session bean's fields or methods. The Seam framework uses this approach. Integration with Google Guice can be approached in this way too.
  • Transforming and filtering exceptions: An interceptor invokes a method of the next participant in the chain. It wraps the invocation completely, so it can catch any encountered exceptions, swallow them, or rethrow clean versions. This is particular useful for dealing with legacy connectors with their own exceptions in the cause. (Not all exceptions, however, can be easily caught in an interceptor. Some exceptions, for example the javax.persistence.OptimistickLockException, might occur at the end of transaction. Because the interceptors are involved in the same transactions, it's impossible for them to catch such exceptions.)

In conclusion

EJB 3 -- especially EJB 3.1, with its support for no-interface views, WAR deployment, and singletons -- is even interesting for small and midrange applications. It is so streamlined that it's hard to slim it down any further. I don't know of any other framework that can implement simple CRUD functionality with less overhead than what you see in Listing 19.

Listing 19. CRUD with EJB 3.1 and without a business interface

@Stateless
public class BookMmgr{

    @PersistenceContext
    private EntityManager em;

    public void create(Book t) {
        this.em.persist(t);
    }

    public Book find(String id){
       return this.em.find(Book.class, id);
    }

    public void delete(Book t) {
       t = this.em.merge(t);
       this.em.remove(t);
    }

    public Book update(Book t) {
        return this.em.merge(t);
    }
}

Note that this example requires no XML. Only the EntityManager needs a few lines of configuration in the persistence.xml file.

New design patterns emerge from the synergy that comes from using EJB 3 with JPA entities. Even better, EJB 3 lets you do without most of the J2EE "best practices" that made that framework so complex to use.

See the Resources section to learn more about EJB technology then and now.

Consultant and author Adam Bien is an Expert Group member for the Java EE 6, EJB 3.1, and JPA 2.0 JSRs; a NetBeans Dream Team Member; and a Java Champion. He has edited several books about Java and J2EE technology. He is an architect and developer on Java EE 5 projects and participates in several open source projects as committer and owner. He is working on Productive Java EE - Rethinking the Best Practices, a new book for O'Reilly Media. Visit Adam's blog.

Learn more about this topic

More

1 2 3 4 5 6 Page 6
Page 6 of 6