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é.

You can leave a response,or trackback from your own site.

Leave a Reply