lunes, 24 de noviembre de 2014

Inyección de EJBs con Spring (Spring Integration)

 Enterprise JavaBeans (EJB) integration

1 Introducción

Como contenedor ligero, Spring se considera a menudo un sustituto a la tecnología EJB. Creemos que para muchas si no la mayoría de los casos de uso en una aplicación, un contenedor Spring, combinado junto con una rica funcionalidad en el area de transacciones, ORM y JDBC, es una mejor elección que implementar una funcionalidad equivalente a partir de un contenedor EJB.

Sin embargo, es importante tener en cuenta que Spring no impide el uso de EJBs. De hecho, como veremos a lo largo del artículo, Spring hace más fácil el acceso a los EJBs, permitiendo dicha funcionalidad. Además usar Spring para el acceso a los servicios que aporta la tecnología EJBs, nos permite implementar dichos servicios y para más adelante poder intercambiarlos entre EJBs locales o remotos o bien una variante POJO (plain old Java object), con la ventaja de no tener que modificar el código.

En este capítulo, veremos como Spring puede ayudarnos a acceder e implementar EJBs junto al valor añadido que Spring aporta en el acceso a los stateless session beans (SLSBs)..

2 Acceso a EJBs

2.1 Conceptos

Para invocar un método de un stateless session bean local o remoto, el código cliente ha de realizar una llamada al método JNDI lookup para obtener el objeto EJB Home (local ó remote), para a continuación llamar al método create method en dicho objeto y obtener la verdadera referencia al objeto (local ó remote) EJB. Una vez hecho esto, los métodos se invocan en el EJB.

Para poder evitar esta ejecución repetida de código de bajo nivel, muchas aplicaciones EJB utilizan los patrones "Service Locator" y "Business Delegate". Esto es mejor que lanzar JNDI lookups dentro del código cliente, pero el uso repetido de esta implementación tiene grandes desventajas. Por ejemplo:

  • El codigo para usar EJBs está asociado típicamente a la creación de "Singletons" a través de "Service Locator" o " Business Delegate", esto los hace difíciles de testear.
  • En el caso del patrón "Service Locator" usado sin "Business Delegate", el código de la aplicación debe además realizar una llamada al método "create()" del EJB Home, y manejar las posibles excepciones. Por esto, se aproxima al API EJB y la complejidad del dicho modelo de programación EJB.
  • Implementar el patrón "Business Delegate" resulta a menudo engorroso o redundante en código, cuando por ejemplo, necesitamos realizar una simple llamada a un servicio EJB.

La aproximación Spring, consiste en permitir la creación y el uso de objetos Proxy, que son generalmente se configuran dentor del contenedor Spring, que actúan como business delegates de código simplificado. Esto nos evita reescribir Service Locator, JNDI lookup, o duplicar métodos codificados a mano dentro de "Business Delegate", a menos que sea necesaria una verdadera modificación de la funcionalidad básica.

2.2 Acceso a Session Stateless local Beans

Supongamos que tiene un controlador web que necesita usar un EJB. Seguiremos una buena prática de EJB Business Methods Interface pattern, de manera que la interfaz local EJB’s hereda de la interfaz de EJB - la cual no es específicamente una interfaz de business methods. Llamemos esta interfaz no específica EJB MyComponent.

public interface MyComponent {
    ...
}

Una de las principales razones para utilizar el patrón Business Methods Interface, es asegurarnos que "la sincronización entre llamadas a métodos en la interfaz local y la implementación de dicho bean sea automática". Otra razón, es que, más adelante se haga mucho más sencillo para nosotros intercambiar entre una implementación POJO (plain old Java object) de nuestro servicio, si es necesario. Además debemos, implementar la interfaz local home y la clase que implementa el SessionBean con los métodos de la interfaz de negocio de MyComponent. De esta manera, el único código Java que debemos modifiar dentro del controlador de la capa web para la invocación de EJB es crear un métod setter de MyComponent. Esto guardará la referencia como una variable de instancia dentro del controlador.

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
    this.myComponent = myComponent;
}

Podemos de esta manera usar dicha variable de instancia y cualquier método de negocio dentro del controlador.

Ahora, teniendo en cuenta que estamos recuperando un objeto de controlador a partir del contenedor Spring, podemos (de la mísma manera) configurar una instancia de tipo LocalStatelessSessionProxyFactoryBean, que sera un objeto proxy EJB. La configuración de dicho proxy, y el set de la propiedad myComponent del controlador se realiza de la forma siguiente:

<bean id="myComponent"
        class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/myBean"/>
    <property name="businessInterface" value="com.mycom.MyComponent"/>
</bean>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

Lo que sucede por detrás, es gracias al framework "Spring AOP". La definición del bean myComponent crea un proxy para el EJB, que implementa la interfaz del método de negocio. La interfaz local EJB, se cachea en el arraque, así que solo existe un sólo JNDI lookup. Cada vez se llama a un EJB, el proxy realiza una llamada a un método classname dentro del EJB local y a continuación se llama al correspondiente método de negocio del EJB.

La definición del bean myController establece la propiedad property de myComponent en dicho controlador al proxy EJB.

De manera altenativa (y de preferencia en caso de existir muchas de dichas definiciones de proxy), podemos usar la configuración <jee:local-slsb> dentro del "namespace" Spring "jee":

<jee:local-slsb id="myComponent" jndi-name="ejb/myBean"
        business-interface="com.mycom.MyComponent"/>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

Este acceso a los EJB proporciona una gran simplificación del código de la capa web (o cualquier otro código EJB cliente), para eliminar la dependencia al API propia cuando usamos EJBs. Si queremos reemplazar esta referencia a EJB con una referecia a POJO o un objeto mock u otro tipo de referencia a objeto para realización de test, esto sería posible cambiando la definición del bean myComponent sin cambiar una sola línea de código Java. Además, no tendríamos que escribir ni una sola línea de llamadas JNDI lookup u otro tipo de código comúnmente usado en la tecnología EJB, para nuestra aplicación.

En cuanto al rendimiento, los test y la experiencia, demuestran que la carga cuando referenciamos EJB a partir de Sping (lo que implica una llamada por "reflexión" a el EJB) es mínima y apenas se diferencia de la realización de una llamada clásica a EJB (lo que intentamos evitar además de evitar el coste asociado con la infraestructura del contenedor EJB en el servidor de aplicación).

Existe un punto respecto al JNDI lookup. En un contenedor bean, esta clase se usa de forma más eficiente como Sigleton (no existe una razón para crearla como Prototipo). Sin embargo, si este contenedor bean realiza pre-instanciaciones de Singletons (tal como hacen las diferentes versiones del XML ApplicationContext ), podemos tener un problema "si el contenedor bean se carga antes que el contenedor EJB haya cargado el EJB que referenciamos". Esto ocurre porque la llamada a JNDI lookup se realiza en el método init() de esta clase y a continuación se guarda en memoria, sin embargo el stub EJB no se habrá cargado aún en el destino. La solución pasa por no pre- instanciar este método factory object, sino permitir, "que se cree en la primera llamada". En los contenedores XML, esto se controla a través del atributo lazy-init.

Aunque esto no es relevante en el manejo de Spring en la mayoría de los casos, este problema de sincronización (entre cargas de contenedores y creación ) planteado, se resuelve a través de "AOP junto con EJBs", a través de la clase Interceptor: LocalSlsbInvokerInterceptor.

2.3 Acceso a remote State Less Session Bean

El acceso a un EJB de tipo remote es esencialmente idéntico al acceso a un EJB de tipo local, excepto que debemos usar la configuración SimpleRemoteStatelessSessionProxyFactoryBean or <jee:remote-slsb>. Desde luego, con o sin Spring, en una invocación remota se usa la semántica propia, "una llamada a un método de un objeto dentro de otra VM que se encuentra en una máquina distinta", debe tratarse a menudo de forma diferente según los distintos escenarios y el manejo de errores.

El soporte de Spring’s para los EJB cliente añade una ventaja sobre la aproximación no Spring. Normalmente, el código cliente de un EJB presenta complejidad a la hora de intercambiar entre invocaciones locales y remotas. Esto es debido a que los métodos de la interfaz remota junto a su invocación han de capturar una excepción de tipo RemoteException, el código cliente debe por lo tanto manejar esto, mientras que una invocación a un EJB local no. El código cliente escrito para los EJB de tipo local, puede o bien dejarlo igual pero haciendo un innecesario manejo de excepciones remotas, o bien necesita modificarse para eliminar este código. Con el proxy Spring EJB remoto, es posible evitar declarar la excepción de tipo RemoteException en su "Business Method Interface" y tener que implementar código EJB, ya que en Spring declarar una interfaz remota hace que por defecto lance una excepción de tipo RemoteException. De esta manera, podemos centrarnos en el proxy para tratar de forma dinámica las dos interfaces ya que ambas son la misma. Es decir, el código cliente no ha de gestionar con la indicada RemoteException. Ninguna RemoteException que se lance durante la invocación del EJB se re- lanzará como si fuese la no indicada RemoteAccessException class, lo que es una subclase de RunTimeException. Además el servicio que deseamos invocar puede ser intercambiado según deseemos, ya sea como una implementación EJB local , remote o un POJO, sin que tener que preocuparnos de conocer la implementación del código cliente. Desde luego, esto es opcional, no hay impedimento en declarar por nuestra parte una excepción RemoteException en la interfaz de negocio.

2.4 Acceso a EJB 2.x SLSBs versus EJB 3 SLSBs

El acceso a EJB 2.x beans de Session y EJB 3 de beans de Session a través de Spring es ampliamente transparente. La forma de acceder a Spring’s EJB, incluyendo el <jee:local-slsb> y <jee:remote-slsb> utilidades, adapta de forma transparente al verdadero componente en tiempo de ejecución. Se maneja un interfaz home (EJB 2.x style), o realizar invocaciones directas al componente si no existe ningún interfaz disponible (EJB 3 style).

Nota: Para los beans de session EJB 3, podemos efectivamente usar también un JndiObjectFactoryBean / <jee:jndi-lookup>, ya que se trata de un componente completamente disponible para los JNDI lookups. Declarar explicitamente lookups lo podemos hacer a través de <jee:local-slsb> / <jee:remote-slsb> sencillamente aporta una forma consistente y de forma más explícita acceso a la configuración EJB.

3 Usar las clases de soporte Spring’s EJB implementation

3.1 Interceptor para la inyección de EJB 3

Para los beans de session EJB 3 y los Message-Driven Beans, Spring trae un interceptor adecuado que resuelve la anotación Spring’s @Autowired en el componente EJB: org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor. Este interceptor puede aplicarse a través de la anotación @Interceptors en el componente EJB, o a través de un binding interceptor declarado a través de interceptor-binding XML en el descriptor de despliegue EJB.

@Stateless
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class MyFacadeEJB implements MyFacadeLocal {

    // automatically injected with a matching Spring bean
    @Autowired
    private MyComponent myComp;

    // for business method, delegate to POJO service impl.
    public String myFacadeMethod(...) {
        return myComp.myMethod(...);
    }

    ...

}

SpringBeanAutowiringInterceptor por defecto obtiene beans a partir del ContextSingletonBeanFactoryLocator, con el contexto declarado en el fichero "beanRefContext.xml". Por defecto, se espera una sola definicion, lo que se obtiene por tipo en lugar de por nombre. Sin embargo, si necesitamos elegir entre varias definicions de contexto, es necesario una clave "locator". La clave locator (por ejemplo el nombre de la definición de contexto en beanRefContext.xml), lo que podemos declarar de forma explicita sobrecargando el método getBeanFactoryLocatorKey en una subclase de SpringBeanAutowiringInterceptor.

De forma alternativa, es posible sobrecargar el método de la clase, SpringBeanAutowiringInterceptor's getBeanFactory, por ejemplo, obteniendolo de un ApplicationContext a partir de una clase que lo recoja.

No hay comentarios :

Publicar un comentario