Intercepting Life Cycle Events




Intercepting Life Cycle Events

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

@<callback-annotation> void 
 <method-name>(InvocationContext
 
 ctx);

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). InvocationContext 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 InvocationContext.proceed( ) to complete the life cycle 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:

package com.titan.annotations;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JndiInjected {
   String value( );
}

The value( ) attribute of the @com.titan.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:

@Stateless
public class MySessionBean implements MySession {
   @JndiInject("java:/TransactionManager")
   private javax.transaction.TransactionManager tm;

...
}

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:

package com.titan.interceptors;

import java.lang.reflect.*;
import com.titan.annotations.JndiInjected;
import javax.ejb.*;
import javax.naming.*;
import javax.interceptor.*;
import javax.annotation.*;

public class JndiInjector 
 {

   @PostConstruct
   public void jndiInject(InvocationContext invocation) {
      Object target = invocation.getTarget( );
      Field[] fields = target.getClass().getDeclaredFields( );
      Method[] methods = target.getClass().getDeclaredMethods( );

      // find all @JndiInjected 
 fields/methods and set them
      try {
         InitialContext ctx = new InitialContext( );
         for (Method method : methods) {
            JndiInjected inject = method.getAnnotation(JndiInjected.class);
            if (inject != null) {
               Object obj = ctx.lookup(inject.value( ));
               method.setAccessible(true);
               method.invoke(target, obj);
            }
         }
         for (Field field : fields) {
            JndiInjected inject = field.getAnnotation(JndiInjected.class);
            if (inject != null) {
               Object obj = ctx.lookup(inject.value( ));
               field.setAccessible(true);
               field.set(target, obj);
            }
         }
         invocation.proceed( );
      } catch (Exception ex) {
         throw new EJBException 

("Failed to execute @JndiInjected", ex);
      }
   }
}

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:

<ejb-jar>
   <assembly-descriptor>
      <interceptor-binding>
         <ejb-name>*</ejb-name>
         <interceptor-class>com.titan.interceptors.JndiInjector
 
</interceptor-class>
      </interceptor-binding>
   </assembly-descriptor>
</ejb-jar>

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.0 easy to use, but it is now finally easy to extend as well.