Introducción a EJB 3.1 (II)

En el artículo anterior del tutorial de EJB 3.1 vimos de manera superficial que representa la especificación EJB, que es un contenedor, los tipos de componentes dentro de una aplicación EJB, y por último un sencillo ejemplo de desarrollo de una aplicación EJB 3.1 mediante Test-Driven Development. En este segundo artículo vamos a empezar a profundizar en algunos de esos temas, viendo dos de los tres tipos de Session Beans: Stateless y Stateful. Sin embargo, antes es necesario comprender algunos conceptos relacionados con la tecnología EJB, como el funcionamiento del pool, el uso de metadatos, componentes locales vs componentes remotos, inyección de dependencias, y contexto de sesión dentro de una aplicación EJB. Comencemos.

2.1 Metadatos

Como vimos en la primer artículo del tutorial, para declararar nuestros POJO's como verdaderos componentes EJB necesitamos usar metadatos. Estos metadatos pueden ser de dos tipos:

  • Anotaciones
  • XML

El uso de anotaciones es, a priori, el más sencillo y expresivo. Esta forma de aplicar información a nuestros componentes está disponible en la plataforma JavaEE desde su versión 5, y por tanto disponible también en JavaEE 6 (plataforma de la cual forma parte la especificación EJB 3.1). El único punto en contra del uso de anotaciones es que, si deseamos cambiar el comportamiento de un componente, debemos recompilar nuestro código (ya que la anotación acompaña al código Java). Por motivos de simplicidad, esta será la forma de metadatos que se utilizará durante todo el tutorial. Puedes consultar la referencia completa (en inglés) de las anotaciones soportadas en la especificación EJB 3.0 en la siguiente dirección.

El uso de XML para añadir metadatos a nuestro código es la forma heredada de versiones anteriores de la especificación JavaEE. Para ello, debemos incluir un archivo llamado ejb-jar.xml dentro del directorio META-INF del archivo jar/ear a desplegar. La ventaja del uso de XML como fuente de metadatos reside en que no es necesario recompilar nuestro código para cambiar el comportamiento de nuestros componentes, con todas las ventajas que esto puede suponer una vez que un proyecto se encuentra en producción. De manera adicional, al encontrarse los metadatos en un archivo externo, nuestro código Java no contiene ningún tipo de información relativa a la especificación EJB, y por tanto es más portable. Por contra, además de tener que mantener dos fuentes de información a la vez (el código Java y el archivo XML), el propio archivo XML puede ser dificil de mantener y entender cuando alcanza cierta longitud y complejidad. En este tutorial no se verán ejemplos de metadados en XML por motivos de simplicidad, aunque si deseas ampliar información puedes visitar el capítulo 19 de la especificación EJB 3.1, la cual puedes descargar desde la siguiente dirección.

Por último, debes tener muy presente que los metadatos en formato XML sobreescriben cualquier comportamiento ya expresado mediante anotaciones, siempre que ambos hagan referencia a un mismo componente. Por ello, una combinación de ambos tipos de metadatos sería perfectamente legal, aunque salvo honrosas excepciones, poco recomendable (puede inducir a la confusión, y por tanto a cometer errores).

2.2 El pool

Un pool es, expresado de forma básica, un almacén de objetos. El contenedor EJB mantiene uno o varios pools donde almacena componentes que están listos para ser servidos a un cliente que los solicite. De esta manera, el contenedor gestiona la creación, mantenimiento, y destrucción de componentes en segundo plano y de manera transparente a la aplicación EJB (mejorando así tanto el rendimiento de esta última como el de sus clientes).

Cuando un cliente obtiene una referencia, ya sea local (mediante @EJB, como veremos en la sección 2.5), o remota (mediante JNDI, como vimos en el ejemplo al final del artículo anterior), esta no apunta a ninguna instancia del componente en cuestión. Solamente cuando se invoca un método en dicha referencia, el contenedor extrae una instancia del pool y la asigna a dicha referencia, de manera que la invocación pueda tener efecto.

El comportamiento de multitud de aspectos del pool es configurable, aunque en este tutorial no necesitamos hacerlo en ningún momento. Los detalles concretos del funcionamiento del pool es algo que tampoco necesitamos saber de antemano para seguir el tutorial; cuando se necesite comprender un aspecto concreto de dicho funcionamiento, se explicará en la sección correspondiente.

2.3 Local VS Remoto

Como se explicó muy brevemente en el primer artículo del tutorial, un Session Bean puede ser declarado de tipo local o remoto. Un Session Bean declarado como local estará destinado a servir solicitudes de otros componentes dentro de la misma Java Virtual Machine (JVM - Máquina Virtual Java) donde está desplegado (dicho con otras palabras, dentro del contenedor donde se ejecuta la aplicación). El contenedor pasará la referencia que apunta al objeto en cuestión al cliente, pues dentro de la misma JVM está referencia es válida. Por contra, un Session Bean declarado como remoto está destinado a servir peticiones de clientes externos al propio contenedor (como vimos en el ejemplo al final del artículo anterior). En este caso, lo que se pasa es una copia del objeto en cuestión, pues para una JVM externa no tendrá ningun sentido una referencia a un objeto que no se encuentre en ella misma. Recuerda que esa copia no es una copia del componente EJB (una instancia del POJO desplegado en el contenedor), si no una vista/proxy que sabe como acceder a través de una red al objeto real.

La especificación EJB no permite que la interface (en el sentido de contrato) de un Session Bean sea declarada local y remota al mismo tiempo. Por tanto, lo siguiente no es válido y producirá un error al ser desplegado:

@Local 
@Remote 
@Stateless 
public class MiBean implements MiInterfaceRemote { 
    // esta clase produce un error al desplegar 
}
	

n el ejemplo anterior, la clase MiBean actua como interface local y remota al mismo tiempo, lo cual no está permitido. Sin embargo, lo siguiente si que es válido:

@Local 
public interface MiBeanLocalInterface { 
    // declaración de métodos locales 
} 

@Remote 
public interface MiBeanRemoteInterface { 
    // declaración de métodos remotos 
} 

@Stateless 
public class MiBean implements MiBeanLocalInterface, MiBeanRemoteInterface { 
    // implementación de métodos locales y remotos 
}
	

En el ejemplo anterior, declaramos una interface local y otra remota, y las implementamos en un Session Bean de tipo Stateless. De esta manera, el Session Bean podrá servir solicitudes de ambos tipos (cada una a través de la interface correspondiente). Otra forma de expresar lo mismo sería de la siguiente manera:

public interface MiBeanLocalInterface {} 

public interface MiBeanRemoteInterface {} 

@Local(PrimerBeanLocalInterface.class) 
@Remote(PrimerBeanRemoteInterface.class) 
@Stateless 
public class MiBean implements MiBeanRemoteInterface {} 
	

En el ejemplo anterior, hemos eliminado las anotaciones en las interfaces y las hemos aplicado a la clase MiBean. Añadiendo los argumentos correspondientes en @Local y @Remote le indicamos al contenedor que interface actuará como local y cual como remota. Seguimos teniendo que implementar la interface remota en la declaración de la clase (implements MiBeanRemoteInterface), pues como se vio en el artículo anterior, todo Session Bean declarado como remoto necesita implementar una interface de manera obligatoria (dicha interface es necesaria para la creación del proxy/vista).

Por supuesto también podemos aprovechar las características de polimorfismo y herencia en Java para crear nuestros componentes:

public interface MiInterfaceBase { 
    // contrato general para todas las interfaces que hereden de esta 
} 

public interface MiInterfaceLocal extends MiInterfaceBase { 
    // contrato concreto para acceso local 
} 

public interface InterfaceRemote extends MiInterfaceBase { 
    // contrato concreto para acceso remoto 
} 

public abstract class MiBeanBase implements MiInterfaceBase { 
    // implementación general para todas las clases que hereden de esta 
} 

@Stateless 
@Local(MiInterfaceLocal.class) 
public class MiBeanLocal extends MiBeanBase implements MiInterfaceLocal { 
    // implementación concreta para acceso local 
} 

@Stateless 
@Remote(MiInterfaceRemote.class) 
public class MiBeanRemote extends MiBeanBase implements MiInterfaceRemote { 
    // implementación concreta para acceso remoto 
}
	

En este tutorial los ejemplos se mantendrán siempre lo más simple posibles, pues a efectos lectivos toda la parafernalia del ejemplo anterior no es necesaria (de hecho es contraproducente). El aspecto clave a recordar estriba en no declarar una misma interface (repito, en el sentido de contrato) de tipo local y remota al mismo tiempo.

2.4 EJB context y Session context

A veces, necesitamos acceder al contexto de ejecución del componente que se está usando. EJBContext (Contexto de EJB) es una interface que provee acceso al contexto de ejecución asociado a cada instancia de un componente EJB. A través de él podemos acceder, por ejemplo, al servicio de seguridad: imaginemos que invocamos un método en un Session Bean dentro de una aplicación donde hay restricciones de seguridad, de manera que el Session Bean necesita comprobar si el cliente que lo está invocándo está autorizado o no a hacerlo. A través del contexto de ejecución de dicho Session Bean podemos acceder al servicio de seguridad y realizar dicha comprobación. Otros ejemplos del uso del contexto de ejecución son el acceso a transacciones, u obtener el Timer Service (Servicio de Reloj, necesario para programar eventos mediante unidades de tiempo, como veremos en un artículo posterior).

SessionContext (Contexto de Sesión) es una interface que implementa EJBContext, añadiendo métodos que permiten el uso de servicios adicionales. A través de él podemos, por ejemplo, obtener una referencia al Session Bean actual para poder pasarla a otro Session Bean como argumento de un método; esto es necesario, pues el contenedor no permite pasar el Session Bean actual usando una referencia de tipo this (los detalles de porqué esto es así no son necesarios para entender el material, y por tanto los omito). Podemos acceder al contexto de sesión asociado a la instancia del componente actual mediante inyección de dependencia (asunto que se explicará en la sección 2.5):

@Local 
@Stateless 
public class MiBean { 
    @Resource 
    private SessionContext contexto; 

    // ... 
} 
	

Mediante la anotación @Resource (Recurso) indicamos al contenedor que debe inyectar el contexto de sesión asociado a dicha instancia en la variable contexto (cuando veamos el ciclo de vida de los componentes EJB se verá cómo y cuándo se realiza esta operación). Otra manera de acceder al contexto de sesión es usando la misma anotación, pero sobre un método setter que siga el estandar JavaBean (no confundir con Enterprise JavaBean):

@Local 
@Stateless 
public class MiBean { 
    private SessionContext contexto; 
     
    @Resource 
    public void setContexto(SessionContext contexto) { 
        this.contexto = contexto; 
    } 

    // ... 
} 
	

Puesto que SessionContext extiende EJBContext, podemos acceder a ambas interfaces desde la primera. Es muy importante que tengas presente que ciertos métodos en EJBContext están marcados como deprecated, y además lanzarán una excepción de tipo RuntimeException si son invocados. Lo mejor es que, siempre que tengas dudas, consultes la API de EJBContext.

2.5 Inyección de dependencias

La inyección de dependencias (Dependency Injection) es un proceso por el cual el contenedor puede inyectar en un componente recursos que son necesarios. Un ejemplo de inyección de dependencia lo vimos en la sección anterior, donde usamos la anotación @Resource para inyectar una instancia de SessionContext en un SLSB. Otro ejemplo de inyección de dependencia surge cuando uno de nuestros componentes necesita de otro componente:

@Stateless 
public class UnComponente { 
    private OtroComponente dependencia; 
     
    public String metodo() { 
        return dependencia.otroMetodo(); 
    } 
} 
	

En el ejemplo anterior, el Session Bean UnComponente requiere otro Session Bean de tipo OtroComponente. Puesto que, como ya se ha explicado, el contenedor EJB gestiona el ciclo de vida de todos los componentes (toda la aplicación EJB funciona en un entorno gestionado), una instanciación del tipo new OtroComponente() sería incorrecta (esta instancia no tendría, por ejemplo, un contexto de sesión asociado, pues ha sido inicializada manualmente). La solución consiste en informar al contenedor de dicha dependencia mediante la anotación @EJB, dejando así en sus manos esta tarea:

@Local 
@Stateless(name="otroComponente") 
public class OtroComponente { 
    public String otroMetodo() { 
        // ... 
    } 
} 

@Stateless 
public class UnComponente { 
    @EJB(beanName="otroComponente") 
    private OtroComponente dependencia; 
     
    public String metodo() { 
        return dependencia.otroMetodo(); 
    } 
} 
	

Gracias a la anotación @EJB el contenedor sabe que componente instanciar, inicializar y por último inyectar en la variable dependencia. En el ejemplo anterior, se registra un Session Bean con nombre otroComponente (gracias a @Stateless(name="otroComponente")), el cual puede ser accedido mediante inyección de dependencia gracias a @EJB(beanName="otroComponente").

La inyección de dependencias es una poderosa carecterística de EJB que libera al programador de muchas responsabilidades, ademas de resultar en código poco acoplado y apto para unit testing (entre otras buenas cualidades). Puedes encontrar más información sobre la anotación @EJB aquí y sobre la anotación @Resource aquí (ambas páginas en inglés).

2.6 Componentes del lado del servidor

Los componentes del lado del servidor son todos aquellos que son gestionados de manera íntegra por el contenedor. Esto es, el cliente del componente no interacciona con el componente en si, si no con una representación (referencia/proxy/vista) del componente real. Los componentes del lado del servidor son dos:

  • Todos los Session Bean (Stateless, Stateful, y Singleton)
  • Message-Driven Beans

En este artículo veremos los dos primeros tipos de Session Beans, y dejaremos el componente Singleton y los Message-Driven Beans para el próximo artículo. Ahora otra buena pregunta sería: ¿Que NO es un componente del lado del servidor? La respuesta es: Entity Beans (EB - entidades). Los EB viajan entre el cliente y el servidor siendo siempre la representación del mismo objeto Java en ambos lados. Todo lo relacionado con EB se tratará en el capítulo relacionado con persistencia (si estás interesado en este tema puedes visitar un tutorial sobre Java Persistent API (JPA - API de Persistencia en Java) publicado en esta misma web).

2.7 Stateless Session Beans: conceptos básicos

Como vimos brevemente en el primer artículo del tutorial, los Stateless Session Bean (SLSB - Session Bean Sin Estado) son aquellos que no mantienen estado entre diferentes invocaciones de sus métodos. Podriamos considerar cada método dentro de un SLSB como un servicio en si mismo, que realiza una tarea y devuelve un resultado (puede no hacerlo) sin depender de invocaciones anteriores o posteriores sobre él mismo o sobre otro método. Debido a este comportamiento, el contenedor puede utilizar un número relativamente bajo de SLSB's para servir llamadas de muchos clientes (ya que ninguno de estos clientes estará asociado a un SLSB en concreto), y por ello son muy eficientes. El hecho de no mantener un estado entre invocaciones los hace aún más eficientes.

Es importante destacar que un SLSB si puede mantener un estado interno, aunque este no debe escapar nunca hacia el cliente. Un ejemplo de este estado interno sería una variable que almacenara el número de veces que una instancia en concreto ha sido invocada: un cliente que dependa del valor de esta variable podría obtener un resultado distinto en cada llamada al SLSB, ya que el contenedor no garantiza devolver la misma instancia al mismo cliente. La regla es: si un SLSB mantiene estado, este debe ser interno (invisible para el cliente).

2.8 Stateless Session Beans: el ciclo de vida

El ciclo de vida de un componente describe los distintos estados (gestionados íntegramente por el contenedor) por los que puede pasar cada instancia del componente desde que es creada hasta que es destruida. Para los SLSB, este ciclo de vida es muy simple y consta únicamente de dos estados (incluyo el término original en inglés):

  • No existe (Does not exists)
  • Preparado en pool (Method-ready pool)

El primer estado, no existe, es autoexplicativo: la instancia del SLSB no ha sido creada aún. El segundo estado, Preparado en pool, representa una instancia del SLSB que ha sido instanciada y construida por el contenedor, y se encuentra en el pool lista para recibir invocaciones por parte de un cliente (cuando esta invocación sucede, la instancia es extraida el pool y asociada a una referencia que es pasada al cliente). A este estado se llega durante el arranque del contenedor (el pool es poblado con cierto número de instancias), y cuando no existan suficientes instancias en el pool para servir llamadas de clientes (y por tanto se deban crear más). En resumen, siempre que una nueva instancia del SLSB sea creada.

Durante la transición entre el primer estado y el segundo, el contenedor realizará tres operaciones en el siguiente orden:

  1. Instanciación del SLSB
  2. Inyección de cualquier recurso necesario y de dependencias
  3. Ejecución de un método dentro del SLSB marcado con la anotación @PostConstruct, si existe

La instanciación del SLSB se lleva a cabo mediante reflexión, a traves de Class.newInstance(). Por tanto, el SLSB debe tener un constructor por defecto (sin argumentos), ya sea de forma implícita o explícita.

La inyección de recursos y de dependencias se vio en las secciones 2.4 y 2.5, donde se utilizaron las anotaciones @Resource y @EJB para tales efectos. Estos y otros recursos son inyectados en el SLSB de forma automática por el contenedor durante esta operación. De manera adicional, cada vez que un SLSB sea invocado por un nuevo cliente, todos los recursos son inyectados de nuevo.

Por último, el contenedor ejecutará un método dentro del SLSB que esté anotado con @PostConstruct (Post construcción), si existe. Esta anotación declara dicho método (que puede ser uno y solo uno) como un método callback, esto es, un método que reacciona ante cierto evento (en este caso, a la construcción de la instancia, como su propio nombre indica). Dentro de este método tenemos la oportunidad de adquirir recursos adicionales necesarios para el SLSB, como una conexión de red o de base de datos. Este método debe ser void, no aceptar parámetros, y no lanzar ninguna excepción de tipo checked. Al contrario que la inyección de recursos, la ejecución del método @PostConstruct se ejecuta una y solo una vez (al final de la instanciación del componente).

Cuando el contenedor no necesita una instancia de SLSB, ya sea porque decide reducir el número de instancias en el pool, o porque se está produciendo un shutdown del servidor, se realiza una transición en sentido inverso: del estado preparado en pool al estado no existe. Durante esta transición se ejecutará, si existe, un método anotado con @PreDestroy (Pre destrucción), el cual es también un método callback, y el cual nos da la oportunidad de liberar cualquier recurso adquirido durante la construcción de la instancia (adquisición que hicimos en @PostConstruct). Al igual que ocurría con @PostConstruct, un método anotado con @PreDestroy es opcional, debe ser void, no aceptar parámetros, no lanzar ninguna excepción de tipo checked, solo puede ser usado en un único método, y es ejecutado una y solo una vez (durante la destrucción de la instancia).

2.9 Stateless Session Beans: un ejemplo sencillo

Supongamos que estamos desarrollando una aplicación de banca, y entre otras cosas necesitamos escribir código que represente los conceptos de ingreso, retirada, y transferencia de efectivo. Estos tres conceptos son operaciones que no requieren mantener un estado dentro de la aplicación (esto es, una vez invocadas y terminadas no dependen de otras operaciones) y por tanto son excelentes candidatas para ser representadas mediante un Session Bean de tipo Stateless. Como ya hemos visto en multitud de ejemplos, para declarar un Session Bean de tipo Stateless utilizamos la anotación @Stateless (se omiten otras anotaciones como @Remote o @Local, así como todo código no estrictamente necesario para explicar el tema tratado; desde este momento se dará por hecho esto en la mayoría de los ejemplos que veamos):

package es.davidmarco.ejb.slsb; 

import javax.ejb.Stateless; 
import es.davidmarco.ejb.modelo.Cuenta; 

@Stateless 
public class OperacionesConEfectivo { 
    public void ingresarEfectivo(Cuenta cuenta, double cantidad) { 
        // ingresar cantidad en cuenta 
    } 

    public void retirarEfectivo(Cuenta cuenta, double cantidad) { 
        //retirar cantidad de cuenta 
    } 

    public void transferirEfectivo(Cuenta cuentaOrigen, Cuenta cuentaDestino, double cantidad) { 
        // transferir cantidad de cuenta origen a cuenta destino 
    } 
}
	

La clase Cuenta representa una cuenta bancaria, y teóricamente lo representaríamos mediante un componente de tipo Entity Bean (Bean de Entidad), los cuales veremos en el cuarto artículo de este tutorial. Lo realmente importante del ejemplo anterior es el concepto de operaciones que no requieren estado, y por tanto pueden ser representadas mediante Session Beans de tipo Stateless.

2.10 Statefull Session Beans: conceptos básicos

Al contrario que los Session Bean de tipo Stateless, los Stateful Session Bean (SFSB - Session Bean con Estado) mantienen estado entre distintas invocaciones de un mismo cliente. Podríamos considerar un SFSB como una extensión del cliente en el contenedor, ya que cada SFSB está dedicado de manera exclusiva a un único cliente durante todo su ciclo de vida. Otra diferencia entre SLSB y SFSB es que estos últimos no son almacenados en un pool, pues no son reusados, como veremos en la sección 2.11 cuando expliquemos su ciclo de vida.

¿A que nos referimos cuando decimos que un SFSB mantiene un estado? La cuestión es simple: un SFSB almacena información a consecuencia de las operaciones que realiza en él su cliente asociado, y dicha información (estado) debe estar disponible en invocaciones posteriores. De esta manera, un SFSB puede realizar una acción compleja mediante multiples invocaciones. Un ejemplo muy común es el carrito de la compra de una tienda online; añadimos y eliminamos artículos del carrito en diferentes invocaciones, lo cual sería imposible de realizar con un SLSB.

A pesar de mantener estado, un SFSB no es persistente, de manera que dicho estado se pierde cuando la sesión del cliente asociado termina. Su misión es servir como lógica de negocio (misión de todos los Session Bean) y nada más. Para persistir dicha información deberemos usar otro tipo de componente EJB (Entity Bean) el cual veremos en el artículo sobre persistencia.

2.11 Statefull Session Beans: el ciclo de vida

El ciclo de vida de un SFSB es ligeramente más complejo que el de su hermano pequeño SLSB, y consta de tres estados:

  • No existe (Does not exists)
  • Preparado (Method-ready)
  • Pasivo (Passive)

El primer estado, no existe, es similar al de un SLSB: la instancia del SFSB no ha sido creada aún. El segundo estado, preparado, representa una instancia del SFSB que ha sido construida e inicializada, y está lista para servir llamadas de su cliente asociado (recuerda que esta asociación perdura durante toda la vida del SFSB). El tercer estado, pasivo, representa un SFSB que, después de un periodo de inactividad, es persistido temporalmente para liberar recursos del servidor.

Durante la transición entre el primer estado y el segundo, el contenedor realizará tres operaciones en el siguiente orden:

  1. Instanciación del SFSB
  2. Inyección de cualquier recurso necesario y de dependencias, y asociación con el cliente
  3. Ejecución de un método dentro del SFSB marcado con la anotación @PostConstruct, si existe

La instanciación del SFSB se lleva a cabo mediante reflexión, a traves de Class.newInstance(). Por tanto, el SFSB debe tener un constructor por defecto (sin argumentos), ya sea de forma implícita o explícita.

La inyección de recursos y de dependencias se vio en las secciones 2.4 y 2.5, donde se utilizaron las anotaciones @Resource y @EJB para tales efectos. Estos y otros recursos son inyectados en el SFSB de forma automática por el contenedor durante esta operación. Una vez finaliza la inyección de recursos y dependencias, el SFSB es asociado al cliente que ha generado su creación.

Por último, el contenedor ejecutará un método dentro del SFSB que esté anotado con @PostConstruct (Post construcción), si existe. Esta anotación declara dicho método (que puede ser uno y solo uno) como un método callback, esto es, un método que reacciona ante cierto evento (en este caso, a la construcción de la instancia, como su propio nombre indica). Al igual que con un SLSB, la función de este método es adquirir recursos adicionales que sean necesarios para el SFSB, como una conexión de red o de base de datos. Y también al igual que su homónimo en un SLSB, este método debe ser void, no aceptar parámetros, no lanzar ninguna excepción de tipo checked, y no ser static ni final. La ejecución del método @PostConstruct se ejecuta una y solo una vez, al final de la instanciación del componente. En este momento, el SFSB ya se encuentra en el estado preparado, y procede a servir la llamada del cliente que ha generado la creación de la instancia.

Un SFSB en estado preparado puede realizar una transición a cualquiera de los otros dos estados de su ciclo de vida: no existe o pasivo. La transición al estado no existe ocurre cuando:

  • El cliente ejecuta un método dentro del SFSB anotado con @Remove
  • El SFSB sobrepasa un periodo de inactividad establecido (timeout)

En el primer caso, la ejecución de un método anotado con @Remove informa al contenedor que el cliente ha terminado de usar su SFSB asociado y, por tanto, dicha instancia ya no es necesaria. En este momento la instancia es desasociada de su contexto de sesión y eliminada. Este método no es de tipo callback (como @PostConstruct o @PreDestroy), pues no reacciona a un evento, si no que es ejecutado explicitamente por el cliente. En el segundo caso (timeout), el SFSB ha sobrepasado un periodo de tiempo establecido y el contenedor decide eliminarlo, previa ejecución de un método (uno y solo uno) anotado con @PreDestroy, y con iguales reglas que en su homónimo en SLSB: opcional, void, sin parámetros, sin excepciones de tipo checked, no static, y no final.

Por otro lado, la transición al estado pasivo se produce cuando el contenedor decide liberar recursos, persistiendo de manera temporal el estado del SFSB. La pasivación y posterior activación de un SFSB requiere una sección exclusiva que veremos enseguida.

Por finalizar con el ciclo de vida de un componente SFSB, ten presente que si este lanza una excepción de sistema (cualquiera de tipo unchecked cuya definición no esté anotada con @ApplicationException), se producirá una transición al estado no existe sin llamar a su método @PreDestroy. Este comportamiento no está muy bien comprendido entre cierta parte de la comunidad, pues a todas luces no parece muy correcto, pero es así y debes tenerlo en cuenta.

2.12 Statefull Session Beans: pasivación y activación

Durante la vida de un SFSB, el contenedor puede decidir liberar recursos persistiendo de manera temporal el estado de dicho SFSB, liberando de esta manera memoria del sistema (u otros recursos). A esto lo llamamos pasivación. Si durante la pasivación el cliente realiza una llamada al SFSB, el contenedor recreará en memoria la instancia persistida y procedera con dicha llamada; a esto lo llamamos activación. Todo el ciclo de activación-pasivación puede ocurrir múltiples veces en la vida de un SFSB, o ninguna en absoluto. Sea como sea, es un proceso gestionado por el contenedor y totalmente transparence al cliente, el cual no sabe en ningún momento si se ha producido una pasivación o activación del SFSB asociado a su sesión.

No toda la información (estado) almacenada en un SFSB puede ser persistida. Los siguientes tipos son pasivados por defecto:

  • Todos los tipos primitivos
  • Objetos que implementan la interface Serializable
  • Referencias a factorias de recursos gestionados por el contenedor (como javax.sql.DataSource)
  • Referencias a otros componentes EJB
  • javax.ejb.SessionContext
  • javax.jta.UserTransaction
  • javax.naming.Context
  • javax.persistence.EntityManager
  • javax.persistence.EntityManagerFactory

Para todos los demás tipos, la especificación EJB nos proporciona dos métodos callback para controlar la pasivación-activación de un SFSB cuando existen datos que no deben/pueden ser persistidos: @PrePassivate (Pre-pasivación) y @PostActivate (Post-activación). Ambas anotaciones son aplicadas en métodos dedicados a controlar cada uno de los procesos, según corresponda (en la sección 2.13 veremos un ejemplo de un SFSB con métodos de pasivación y activación).

@PrePassivate nos permite anotar un método (opcional) que deje el SFSB es un estado adecuado para su pasivación. Puesto que la pasivación se lleva a cabo mediante serialización, todas las referencias a objetos no serializables deben ser puestas a null. Otra operacion llevada a cabo mediante @PrePassivate es la liberación de recursos que no pertenezcan a la lista anterior. Una vez ejecutado el método @PrePassivate, el SFSB es serializado y persistido temporalmente (tal vez en disco, en una cache, etc; esto es decisión de cada implementación de EJB). Es importante tener en cuenta que si un SFSB en estado pasivo sobrepasa un periodo de inactividad establecido (timeout), el contenedor eliminará la instancia persistida sin ejecutar su método @PreDestroy.

Si durante la pasivación el cliente asociado realiza una llamada al SFSB, el contenedor realizará la activación del componente, deserializando la instancia desde el lugar donde se encuentra persistida, restaurándola al estado anterior a producirse la pasivación (inyección del contexto de sesión, inyección de referencias a otros componentes EJB, etc), y ejecutando un método anotado con @PostActivate (también opcional). En este método podemos inicializar todos los objetos no serializables a unos valores adecuados (no debes confiar en los valores por defecto que el contenedor da a objetos no serializables, pues entre implementaciones pueden ser diferentes), así como restaurar cualquier recurso necesario para el correcto funcionamiento del SFSB. Una vez que la activación ha concluido, el SFSB puede gestionar la llamada del cliente que generó dicha activación.

Tanto @PrePassivate como @PostActivate deben seguir las siguientes reglas:

  • El tipo de retorno debe ser void
  • No puede aceptar ningún parámetro
  • No puede lanzar ninguna excepción de tipo checked
  • No puede ser static ni final

Estas reglas son las que se han aplicado a todos los métodos callback vistos hasta ahora, por lo que una vez aprendidas podrás aplicarlas fácilmente cuando escribas cualquier método callback.

2.13 Statefull Session Beans: un ejemplo sencillo

Supongamos que estamos diseñando una aplicación de tienda online, con un carrito de la compra donde los clientes pueden añadir artículos, eliminarlos, vaciar el carrito, o realizar un pedido. Puesto que cada una de estas operaciones se lleva a cabo de manera independiente a las demas, pero los cambios de cada una de ellas pueden afectar al resto, necesitamos mantener un estado entre distintas invocaciones, y por tanto un componente Session Bean de tipo Stateful. Para declarar un SFSB usamos la anotación @Stateful y hacemos que la clase en cuestión implemente la interface Serializable (puesto que es este el mecanismo que utilizará el contenedor para la pasivación-activación). El ejemplo, como siempre, es una estructura básica sin apenas implementación: el objetivo del ejemplo es únicamente mostrar donde encaja cada pieza:

package es.davidmarco.ejb.sfsb; 

import java.io.Serializable; 
import java.util.HashMap; 
import java.util.Map; 
import javax.annotation.PostConstruct; 
import javax.annotation.PreDestroy; 
import javax.ejb.PostActivate; 
import javax.ejb.PrePassivate; 
import javax.ejb.Remove; 
import javax.ejb.Stateful; 
import es.davidmarco.ejb.modelo.Articulo; 
import es.davidmarco.ejb.util.BaseDeDatos; 

@Stateful 
public class Carrito implements Serializable { 
    private Map articulosEnCarrito = new HashMap(); 
    private BaseDeDatos bbdd; 
     
    public void añadirArticulo(Articulo articulo, int cantidad) { 
        // añadir la cantidad de cierto artículo al carrito 
    } 
     
    public void eliminarArticulo(Articulo articulo, int cantidad) { 
        // eliminar la cantidad de cierto artículo del carrito 
    } 
     
    public void vaciarCarrito() { 
        // vaciar el carrito 
    } 
     
    @Remove 
    public void finalizarCompra() { 
        // procesar el pedido 
    } 
     
    @PostConstruct 
    @PostActivate 
    private void inicializar() { 
        // obtener conexión con la base de datos 
    } 
     
    @PrePassivate 
    @PreDestroy 
    private void detener() { 
        // liberar conexión con la base de datos 
    } 
}
	

Como puedes ver, la clase Carrito está anotada con @Stateful y marcada como Serializable. Dentro de ella tenemos dos campos: artículosEnCarrito, donde se almacena el estado requerido para el correcto funcionamiento del proceso de compra (HashMap es de tipo Serializable y por tanto es persistida de forma automática en caso de pasivación), y BaseDeDatos, que es una clase ficticia que supuestamente nos permite realizar operaciones sobre una base de datos, pero no puede ser pasivada (supongamos que mantiene una conexión con una base de datos real que no puede permanecer abierta durante la pasivación).

Dentro de la clase, los cuatro primeros métodos nos permiten realizar operaciones de lógica de negocio, como añadir cierta cantidad de cierto artículo al carrito, vaciar el carrito completamente, o procesar el pedido una vez añadidos los artículos deseados. El cuarto método (finalizarCompra), de manera adicional, indica al contenedor que hemos terminado de trabajar con el componente SFSB y que por tanto puede eliminarlo (gracias a la anotación @Remove). Estos cuatro métodos son la interface que mostramos al cliente, y por tanto todos ellos son declarados public.

Los dos últimos métodos (inicializar y detener) se encargar de obtener y liberar recursos en momentos clave del ciclo de vida del SFSB. Fíjate como @PostConstruct y @PostActivate anotan el mismo método, pues todas las operaciones que hay en él son comunes a los procesos de construcción del SFSB y una posible activación posterior. De igual manera, @PrePassivate y @PreDestroy acompañan al mismo método, por el mismo motivo. Ambos métodos, de tipo callback, son gestionados por el contenedor en base a eventos, y por tanto el cliente del SFSB no necesita saber de su existencia (nosotros los hemos declarado private, aunque cualquier nivel de visibilidad está permitido).

Resumen

En este segundo artículo del tutorial de EJB hemos visto conceptos relacionados con la tecnología EJB, como la definición del pool, local vs remoto, metadatos, inyección de dependencias, y contexto de sesión. Ademas, hemos visto en cierta profundidad los dos tipos de Session Bean más comunes: Stateless y Stateful.

En el próximo artículo veremos el tercer y último tipo de Session Bean (Singleton) así como un nuevo tipo de componente: Message-Driven Beans.

Hasta entonces, ¡feliz inyección de dependencias!