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.