Using EJB3 with Spring

In my previous entry, I talked about how you could easily use Spring from within EJB 3 beans, thanks to the magic of EJB interceptors. But what about the other way? How do you use EJB 3 from Spring?

*Update:* It looks like the Interface21 guys have had similar ideas – check out Project Pitchfork


Well, back in the days of EJB 2, you would make a reference to an EJB using the following incantation:


  
  

Simple enough; the LocalStatelessSessionProxyFactoryBean knows how to create a Stateless Session Bean, via the Home interface, which is what the JNDI entry points out. There’s a couple of different proxy factories, for the various types of EJBs.

In EJB3, you don’t need Home interfaces any more. As a result, you don’t need to use a LocalStatelessSessionProxyFactoryBean either. Instead, you use the standard JndiObjectFactoryBean:


  
  

So this is pretty simple: look the EJB up in the JNDI, register it as a Spring Bean, and then wire it into other Spring Beans as normal. So far so good.

Or is it? Well, this has got a major problem: you need to specify the EJBs. One of the big selling points of EJB 3 is that you don’t _have_ to manage any XML files, and here comes Spring making you do it all over again! How annoying!

Fortunately, there’s a solution: BeanPostProcessors. BeanPostProcessors lets you do things to a bean after its created. Things like looking for setter methods with an @EJB annotation on them, for example.

Here’s the code:

/** Look for EJB3-style annotations, and provide the corresponding values from the container. */
public class EjbBeanPostProcessor implements BeanPostProcessor {
  
  public Object postProcessAfterInitialization(Object bean, String beanName)
  throws BeansException {
    // do nothing; the EJBs are configured before initialization.
    return bean;
  }
  
  public Object postProcessBeforeInitialization(Object bean, String beanName)
  throws BeansException {
    Class beanClazz = bean.getClass();
    for (Method method : beanClazz.getMethods()) {
      if (method.isAnnotationPresent(EJB.class)) {
        setEjbRef(bean, method);
      }
    }
    return bean;
  }
  
  private  void setEjbRef(T bean, Method setter) {
  Class ejbClass = determineBeanClass(setter);
    Object ejbBean = lookupBean(ejbClass, determineJndiName(ejbClass, setter), bean);
    invoke(bean, setter, ejbBean);
  }
  
  /**
  * Determine the JNDI name for the EJB. By default, we assume that it is the simple class
  * name, but we allow the setter method to override this, using the 'mappedName' attribute.
  */
  private String determineJndiName(Class ejbClass, Method setter) {
  EJB ejbAnnotation = setter.getAnnotation(EJB.class);
    if (ejbAnnotation.mappedName().equals("")) {
      return ejbClass.getSimpleName();
    }
    return ejbAnnotation.mappedName();
  }
  
  private Object lookupBean(Class ejbClass, String beanName, Object bean) throws BeansException {
    try {
      return new InitialContext().lookup("java:comp/env/ejb/" + beanName);
    } catch (NamingException e) {
      throw new EjbBeansException(bean, "Could not resolve EJB " + beanName, e);
    }
  }
  
  /**
  * Determine the type of the EJB. This is used to determine the JNDI name to look up. This
  * would normally be the type of the argument to the setter, but the EJB spec allows this
  * to be overriden (presumably to a subclass)
  */
  private Class determineBeanClass(Method setter) {
    EJB ejbRefAnnotation = setter.getAnnotation(EJB.class);
    if (ejbRefAnnotation.beanInterface() != null &&
        Object.class.equals(ejbRefAnnotation.beanInterface()) == false) {
      return ejbRefAnnotation.beanInterface();
    }
    Class otherBeanClass = setter.getParameterTypes()[0];
    return otherBeanClass;
  }
  
  private  void invoke(T bean, Method method, Object ... args) {
    try {
      method.invoke(bean, args);
    } catch (IllegalArgumentException e) {
      throw new EjbBeansException(bean, "Could not set EJB", e);
    } catch (IllegalAccessException e) {
      throw new EjbBeansException(bean, "Could not set EJB", e);
    } catch (InvocationTargetException e) {
      throw new EjbBeansException(bean, "error setting EJB reference", e.getCause());
    }
  }
  
  /** Simple exception detailing the type of problem that occured. */
  private static class EjbBeansException extends BeansException {
    public EjbBeansException(Object bean, String message, Throwable cause) {
      super(message + " [bean class: " + bean.getClass().getName() + "]", cause);
    }
  }
}

Having created this lovely BeanPostProcessor, you enable it in your Spring context file with a simple one-line statement, defining the post-processor:

Now, you simply need to add @EJB references to your Spring beans, and they will magically be set. No need to list the EJBs in your Spring context files or anything – it just works. It also works with anything else using Spring; we have all our WebWork actions configured this way now.

The only caveat to this is that you need to have the JNDI context set up. In a web application, for example, this means you need to have the ejb-ref entries in your web.xml file.

I hope you find this useful; I know I have.

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s