Použití EJB ve Springu
Před časem jsem na projektu řešil, jak použít dependency injection Springu pro použití EJB. V podstatě mi šlo o to, abych nemusel řešit různé lookupy na EJB v kódu, ale aby to vše fungovalo tak nějak „samo“.
V případě EJB 2, je to snadné. Spring poskytuje celkem snadné prostředky:
1 2 3 4 | <bean id="myEjb" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean"> <property name="jndiName" value="java:comp/env/ejb/MyEjb" /> <property name="businessInterface" value="cz.myapp.MyEjb" /> </bean> |
Jednoduché natolik, že LocalStatelessSessionProxyFactoryBean ví, jak vytvořit instanci Beanu, přes Home rozhraní, na které ukazuje JNDI. Existuje několik různých proxy factory, pro různé druhy EJBček.
V EJB3 je situace jiná, protože vůbec nemusíte implementovat Home rozhraní. Důsledkem je, že nemůžete používat LocalStatelessSessionProxyFactoryBean. Namísto toho, používáte standardní JndiObjectFactoryBean:
1 2 3 4 | <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> |
Princip je jednoduchý: najde EJB v JNDI, zaregistruje se jako spring Bean, a pak se přes autowiring předá tato reference do jiného beanu. To zní dobře.
Má to jeden zásadní problém: musíte definovat EJBčka v XML. Jednou z obrovských výhod EJB 3 je to, že nemusíte řešit žádné XML soubory (známe XML hell z EJB 2.x). A to se mi nechce měnit.
Naštěstí je tu řešení: BeanPostProcessors. BeanPostProcessors vám umožňuje dělat věci, nad jednotlivými beany po jeho vytvoření. A to třeba vyhledání metod, anotací apod. Například anotace @EJB
Zde je kód:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | /** 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); } } } |
Tento krásný BeanPostProcessor ve Springu ho aktivujeme celkem snadno:
Teď si prostě přidáte anotaci @EJB odkazy do svého Spring beanu, a on se kouzelně naplní. Do žádného XML není potřeba nic definovat. Jak krásné.