9/09/2010

Injecting EJB to POJO Using Google Guice






Google Guice is a lightweight Dependency Injection Framework that can be used by Applications where Relation-ship/Dependency between Business Objects have to be maintained manually in the Application code. Guice takes benefit of Java 5.0 annotations.

Java EE5 supports EJB injection to some extend. We can inject EJB references only to certain components like EJB, Servlets etc. Java EE5 EJB injection doesn't work with normal POJO classes. In this article I am explaining how to inject EJB references to any class with the help of Google Guice.

In Java EE5 EJB can be injected using @EJB annotation. First we are also going to create a custom annotation for EJB injection.

Here is the of custom @EJB annotation.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@BindingAnnotation
public @interface EJB {
 String name();
 String type() default "remote";
 
 @SuppressWarnings("unchecked")
 Class home() default EJBHome.class;
}
In @EJB annotation name will refer the JNDI name of the EJB you want to inject. type refers the type of the EJB whether it is local or remote (default value). home refers the Home interface class (only for EJB 1.x & 2.x).

Next we are going to extend explore Custom Injection feature in Google Guice.In addition to the standard @Inject driven injections, Guice includes hooks for custom injections. Each custom injection requires a type listener, an injection listener, and registration of each.

TypeListeners get notified of the types that Guice injects. MembersInjectors and InjectionListeners can be used to receive a callback after Guice has injected an instance. The instance is first injected by Guice, then by the custom members injectors, and finally the injection listeners are notified

We will start our custom injection by registering a type Listener EJBTypeListener in Google Guice Module.

public class EJBTypeListener implements TypeListener {

 @Override
 public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
  for (Field field : typeLiteral.getRawType().getDeclaredFields()) {
   if (field.isAnnotationPresent(EJB.class)) {
    EJB dsan = field.getAnnotation(EJB.class);
    typeEncounter.register(new EJBMemberInjector<I>(field,dsan.jndiName()));
   }
  }
 }
}
EJBTypeListener will scan through a types fields looking for a field annotated with @EJB annotation. For each field that's found we register a EJBMemberInjector.

Finally we are implementing EJBMemberInjector to set the EJB reference.

public class EJBMemberInjector<T> implements MembersInjector<T> {
 
 private final Field field;
 private String jndiName;
 private final String type;
 
 @SuppressWarnings("unchecked")
 private final Class home;
 
 EJBMemberInjector(Field f) {
  this.field = f;
  InjectEJB annotation = f.getAnnotation(EJB.class);
  this.type = annotation.type();
  this.jndiName = annotation.name();
  this.home = annotation.home();
  this.field.setAccessible(true);
 }
 
 @SuppressWarnings("unchecked")
 private Object findRemoteEJB() {
  Context initialContext = null;
  Object remoteRef = null;

  if(remoteRef == null) {
   try {
    initialContext = new InitialContext();
    
    Object obj = initialContext.lookup(this.jndiName);
    
    Class clazz =  this.field.getType();
    remoteRef = PortableRemoteObject.narrow(obj, clazz);
   } catch (Exception e) {
    e.printStackTrace();
    throw new CustomException(e);
   } finally {
    try {
     if(initialContext != null) initialContext.close();
    } catch(Exception e) {}
   }
  }
  
  return remoteRef;
 }
 
 private Object findLocalEJB() {
  Context initialContext = null;
  Object localRef = null;

  if(localRef == null) {
   try {
    initialContext = new InitialContext();
    
    localRef = initialContext.lookup(this.jndiName);
   } catch (Exception e) {
    e.printStackTrace();
    throw new CustomException(e);
   } finally {
    try {
     if(initialContext != null) initialContext.close();
    } catch(Exception e) {}
   }
  }
  
  return localRef;
 }
 
 @SuppressWarnings("unchecked")
 private Object findRemoteEJB20() {
  Context initialContext = null;
  Object remoteHomeRef = null, remoteObj = null;
  
  Class remoteHomeClazz = this.home;
  try {
   initialContext = new InitialContext();
   
   Object remoteHome = initialContext.lookup(this.jndiName);
   remoteHomeRef = PortableRemoteObject.narrow(remoteHome, remoteHomeClazz);
   
   Method createMethod = remoteHomeClazz.getMethod("create");
   remoteObj = createMethod.invoke(remoteHomeRef);
   
  } catch (Exception e) {
   e.printStackTrace();
   throw new CustomAppException(e);
  } finally {
   try {
    if(initialContext != null) initialContext.close();
   } catch(Exception e) {}
  }
  return remoteObj;
 }
 
 @SuppressWarnings("unchecked")
 private Object findLocalEJB20() {
  Context initialContext = null;
  Object localRef = null, localObj = null;
  
  Class localHomeClazz = this.home;
  try {
   initialContext = new InitialContext();

   Object obj = initialContext.lookup(this.jndiName);
   localRef = PortableRemoteObject.narrow(obj, localHomeClazz);
   
   Method createMethod = localHomeClazz.getMethod("create");
   localObj = createMethod.invoke(localRef);
  } catch (Exception e) {
   e.printStackTrace();
   throw new CustomException(e);
  } finally {
   try {
    if(initialContext != null) initialContext.close();
   } catch(Exception e) {}
  }
  
  return localObj;
 }
 
 @Override
 public void injectMembers(T t) {
  try {
   if(EJBHome.class != this.home && EJBLocalHome.class != this.home) {
    if("remote".compareToIgnoreCase(this.type) == 0)
     this.field.set(t, this.findRemoteEJB20());
    else
     this.field.set(t, findLocalEJB20());
   } else {
    if("remote".compareToIgnoreCase(this.type) == 0)
     this.field.set(t, this.findRemoteEJB());
    else
     this.field.set(t, findLocalEJB());
   }
  } catch (Exception e) {
   e.printStackTrace();
   throw new CustomException(e);
  } 
 }
}


For EJB2.1 you can even cache Home interface. You can use a WeakHashMap inside EJBMemberInjector and store all home interfaces and do a map lookup before doing EJB JNDI lookup.

In Google Guice module register EJBTypeListener
public class MyModule implements Module {
 
 @Override
 public void configure(Binder binder) {
  binder.bindListener(Matchers.any(), new EJBTypeListener());
 }
}

Inside you POJO:

public class MyPOJO {

 // Injecting Remote EJB 3.0
 @EJB(name="ejb/MyRemoteBean")
 private MyRemote remote;
 
 // Injecting Local EJB 3.0
 @EJB(name="ejb/MyLocalBean", type="local")
 private MyLocal local;
 
 // Injecting Remote EJB 2.1
 @EJB(name="ejb/MyRemoteBean21", home=MyRemoteHome.class)
 private MyRemote21 remote1;
 
 // Injecting Local EJB 2.1
 @EJB(name="ejb/MyLocalBean21", type="local", home=MyLocalHome.class)
 private MyLocal21 local1;
 
 
 public MyPOJO() {
  Injector injector = Guice.createInjector(new MyModule());
  injector.injectMembers(this);
 }
 
 public void test() {
  remote.test();
  local.test();
  remote1.test();
  local1.test();
 }
}

When I released this API, most of the time developers keep forgetting to call injector.injectMembers(this);. So I decided to remove this call. Finally I got that working with the help of AspectJ. I will explain this in my next blog.

No comments:

Post a Comment