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.

Author: Robert Watkins

My name is Robert Watkins. I am a software developer and have been for over 20 years now. I currently work for people, but my opinions here are in no way endorsed by them (which is cool; their opinions aren’t endorsed by me either). My main professional interests are in Java development, using Agile methods, with a historical focus on building web based applications. I’m also a Mac-fan and love my iPhone, which I’m currently learning how to code for. I live and work in Brisbane, Australia, but I grew up in the Northern Territory, and still find Brisbane too cold (after 22 years here). I’m married, with two children and one cat. My politics are socialist in tendency, my religious affiliation is atheist (aka “none of the above”), my attitude is condescending and my moral standing is lying down.

Leave a comment