« Comments re-enabled | Main | Why external Maven repositories are a bad idea »

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:

<bean id="myEjb" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
  <property name="jndiName" value="java:comp/env/ejb/MyEjb" />
  <property name="businessInterface" value="net.twasink.ejb.MyEjb" />
</bean>

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:

<bean id="myEjb" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="java:comp/env/ejb/MyEjb" />
  <property name="expectedType" value="net.twasink.ejb.MyEjb" />
</bean>

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 <T> 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 <T> 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:

<bean class="net.twasink.EjbBeanPostProcessor" />

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.

Post a comment


About

This page contains a single entry from the blog posted on February 12, 2007 10:43 PM.

The previous post in this blog was Comments re-enabled.

The next post in this blog is Why external Maven repositories are a bad idea.

Many more can be found on the main index page or by looking through the archives.

Creative Commons License
This weblog is licensed under a Creative Commons License.
Powered by
Movable Type 3.35