62
Introducción a EJB 3.1 (I) Publicado el 08 de Marzo de 2011 Con este artículo comienza un tutorial que describe el estandar EJB 3.1 de manera introductoria. El tutorial contiene tanto material teórico como práctico, de manera que según se vayan introduciendo nuevos conceptos se irán reflejando en código. Es muy recomendable que antes de seguir leyendo visites el siguiente anexo donde se explica como poner en marcha un entorno de desarrollo compatible con EJB 3.1, de manera que puedas seguir todos los ejemplos que se van a presentar en el tutorial, así como desarrollar los tuyos propios. La siguiente lista muestra el contenido de los primeros 6 artículos de los que constará el tutorial. Esta lista se actualizará si se publica contenido adicional: - 1. Introducción a EJB y primer ejemplo - 2. Stateless Session Beans y Stateful Session Beans - 3. Singletons y Message-Driven Beans - 4. Persistencia - 5. Servicios que ofrece el contenedor (1ª parte) - 6. Servicios que ofrece el contenedor (2ª parte) Con todo esto dicho, ¡comencemos! 1.1 ¿QUE SON LOS ENTERPRISE JAVABEANS? EJB (Enterprise JavaBeans) es un modelo de programación que nos permite construir aplicaciones Java mediante objetos ligeros (como POJO's). Cuando construimos una aplicación, son muchas las responsabilidades que se deben tener en cuenta, como la seguridad, transaccionalidad, concurrencia, etc. El estandar EJB nos permite centrarnos en el código de la lógica de negocio del problema que deseamos solucionar y deja el resto de responsabilidades al contenedor de aplicaciones donde se ejecutará la aplicación. 1.2 EL CONTENEDOR DE APLICACIONES Un contenedor de aplicaciones es un entorno (en si mismo no es más que una aplicación) que provee los servicios comunes a la aplicacion que deseamos ejecutar, gestionándolos por nosotros. Dichos servicios incluyen la creación/mantenimiento/destrucción de nuestros objetos de negocio, así como los servicios mencionados en el punto anterior, entre otros. Aunque el contenedor es responsable de la gestión y uso de dichos recursos/servicios, podemos interacturar con él para que nuestra aplicación haga uso de los servicios que se ofrecen (normalmente mediante metadados, como se verá a lo largo del tutorial). Una vez escrita nuestra aplicación EJB, podemos desplegarla en cualquier contenedor compatible con EJB, beneficiandonos de toda el trabajo tras bastidores que el contenedor gestiona por nosotros. De esta manera la lógica de negocio se mantiene independiente de

Manual EJB

Embed Size (px)

Citation preview

Page 1: Manual EJB

Introducción a EJB 3.1 (I)Publicado el 08 de Marzo de 2011

Con este artículo comienza un tutorial que describe el estandar EJB 3.1 de maneraintroductoria. El tutorial contiene tanto material teórico como práctico, de manera quesegún se vayan introduciendo nuevos conceptos se irán reflejando en código. Es muyrecomendable que antes de seguir leyendo visites el siguiente anexo donde se explica comoponer en marcha un entorno de desarrollo compatible con EJB 3.1, de manera que puedasseguir todos los ejemplos que se van a presentar en el tutorial, así como desarrollar lostuyos propios.

La siguiente lista muestra el contenido de los primeros 6 artículos de los que constará eltutorial. Esta lista se actualizará si se publica contenido adicional:

- 1. Introducción a EJB y primer ejemplo- 2. Stateless Session Beans y Stateful Session Beans- 3. Singletons y Message-Driven Beans- 4. Persistencia- 5. Servicios que ofrece el contenedor (1ª parte)- 6. Servicios que ofrece el contenedor (2ª parte)

Con todo esto dicho, ¡comencemos!

1.1 ¿QUE SON LOS ENTERPRISE JAVABEANS?EJB (Enterprise JavaBeans) es un modelo de programación que nos permite construiraplicaciones Java mediante objetos ligeros (como POJO's). Cuando construimos unaaplicación, son muchas las responsabilidades que se deben tener en cuenta, como laseguridad, transaccionalidad, concurrencia, etc. El estandar EJB nos permite centrarnos enel código de la lógica de negocio del problema que deseamos solucionar y deja el resto deresponsabilidades al contenedor de aplicaciones donde se ejecutará la aplicación.

1.2 EL CONTENEDOR DE APLICACIONESUn contenedor de aplicaciones es un entorno (en si mismo no es más que una aplicación)que provee los servicios comunes a la aplicacion que deseamos ejecutar, gestionándolos pornosotros. Dichos servicios incluyen la creación/mantenimiento/destrucción de nuestrosobjetos de negocio, así como los servicios mencionados en el punto anterior, entre otros.Aunque el contenedor es responsable de la gestión y uso de dichos recursos/servicios,podemos interacturar con él para que nuestra aplicación haga uso de los servicios que seofrecen (normalmente mediante metadados, como se verá a lo largo del tutorial).

Una vez escrita nuestra aplicación EJB, podemos desplegarla en cualquier contenedorcompatible con EJB, beneficiandonos de toda el trabajo tras bastidores que el contenedorgestiona por nosotros. De esta manera la lógica de negocio se mantiene independiente de

Page 2: Manual EJB

otro código que pueda ser necesario, resultando en código que es más fácil de escribir ymantener (además de ahorrarnos mucho trabajo).

1.3 LA ESPECIFICACIÓN EJB 3.1La especificación EJB 3.1 es parte de la plataforma JavaEE 6, desarrollada y mantenida porSun Microsystems (ahora parte de Oracle Corporation). JavaEE 6 provee diversas API'spara la construcción de aplicaciones empresariales, entre ellas EJB, JPA, JMS, y JAX-WS.Cada una de ellas se centra en un area específica, resolviendo así problemas concretos.Además, cada API/especificación está preparada para funcionar en compañia de las demásde forma nativa, y por tanto en su conjunto son una solución perfectamente válida paradesarrollar una aplicación end-to-end (de principio a fin).

Desde la versión 3.0, EJB no impone ninguna restricción ni obligación a nuestros objetosde negocio de implementar una API en concreto. Dicho de otro modo, podemos escribirdichos objetos de negocio usando POJO's, facilitando entre otras cosas la reutilización decomponentes y la tarea de testearlos.

Como se ha dicho, los POJO's son faciles de testear (siempre que estén bien diseñados). Alfinal de este primer artículo se verá un sencillo ejemplo de programación de un EJBmediante Test-Driven Development (Desarrollo Dirigido por Tests). TDD es unametodología de desarrollo en la cual cada bloque de código está respaldado por uno o mástests que han sido escritos con anterioridad. De manera muy resumida, TDD nos permiteenfocar de manera efectiva el problema que deseamos resolver de la siguiente manera:

- Escribimos un test que define que queremos hacer.- Ejecutamos el test y este falla (puesto que aún no hay lógica de negocio, o lo que es lo

mismo, como queremos hacerlo).- Escribimos la lógica de negocio que hace pasar el test (la solución más simple posible).- Mejoramos la lógica de negocio gradualmente, ejecutando el test después de cada

mejora para verificar que no hemos roto nada.

Escribir el test antes que la lógica de negocio y mantenerlo lo más simple posible nosobliga a escribir código independiente de otro código, con responsabilidades bien definidas(en resumen, buen código). Usado de forma correcta, TDD permite crear sistemas que sonescalables, y con niveles de bugs muy bajos. TDD es un tema tan amplio en si mismo queno tiene cabida en este tutorial (a excepción del citado ejemplo que veremos al final delartículo y que servirá solamente para demostrar que el modelo EJB es un buen modelo deprogramación), y en el que te animo que profundices si no lo conoces; las ventajas queofrece para escribir código de calidad son muchas, independientemente del uso de EJB ono.

Por otro lado, el uso de POJO's para encapsular nuestra lógica de negocio nos proporcionaun modelo simple que es altamente reutilizable (recuerda que la reutilización de clases esun concepto básico y esencial en programación orientada a objetos). Debes tener en cuentaque un POJO no actuará como un componente EJB hasta que haya sido empaquetado,

Page 3: Manual EJB

desplegado en un contenedor EJB y accedido por dicho contenedor (por ejemplo a peticiónde un usuario). Una vez que un POJO definido como EJB haya sido desplegado en elcontenedor, se convertirá en uno de los tres siguientes componentes (dependiendo del comolo hayamos definido):

- Session Bean- Message-Driven Bean- Entity Bean

Veamos una breve descripción de cada tipo de componente (en capítulos posteriores seexplicará cada tipo de componente con más detalle).

1.4 SESSION BEANSLos Session Beans (Beans de Sesión) son los componentes que contienen la lógica denegocio que requieren los clientes de nuestra aplicación. Son accedidos a través de unproxy (también llamado vista, término que utilizaré en adelante) tras realizar una solicitudal contenedor. Tras dicha solicitud, el cliente obtiene una vista del Session Bean, pero no elSession Bean real. Esto permite al contenedor realizar ciertas operaciones sobre el SessionBean real de forma transparente para el cliente (como gestionar su ciclo de vida, solicitaruna instancia a otro contenedor trabajando en paralelo, etc).

Los componentes Session Bean pueden ser de tres tipos:

- Stateless Session Beans- Stateful Session Beans- Singletons

Stateless Session Beans (SLSB - Beans de Sesión sin Estado) son componentes que norequieren mantener un estado entre diferentes invocaciones. Un cliente debe asumir quediferentes solicitudes al contenedor de un mismo SLSB pueden devolver vistas a objetosdiferentes. Dicho de otra manera, un SLSB puede ser compartido (y probablemente lo será)entre varios clientes. Por todo esto, los SLSB son creados y destruidos a discrección delcontenedor, y puesto que no mantienen estado son muy eficientes a nivel de uso dememoria y recursos en el servidor.

Stateful Session Beans (SFSB - Beans de Sesión con Estado), al contrario que SLSB, si quemantienen estado entre distintas invocaciones realizadas por el mismo cliente. Esto permitecrear un estado conversacional (como el carrito de la compra en una tienda online, quemantiene los objetos que hemos añadido mientras navegamos por las diferentes páginas),de manera que acciones llevadas a cabo en invocaciones anteriores son tenidas en cuenta enacciones posteriores. Un SFSB es creado justo antes de la primera invocación de un cliente,mantenido ligado a ese cliente, y destruido cuando el cliente invoque un método en el SFSBque esté marcado como finalizador (también puede ser destruido por timeout de sesión).Son menos eficientes a nivel de uso de memoria y recursos en el servidor que los SLSB.

Page 4: Manual EJB

Los Singleton son un nuevo tipo de Session Bean introducido en EJB 3.1. Un Singleton esun componente que puede ser compartido por muchos clientes, de manera que una y solouna instancia es creada. A nivel de eficiencia en uso de memoria y recursos sonindiscutíblemente los mejores, aunque su uso está restringido a resolver ciertos problemasmuy específicos.

1.5 MESSAGE-DRIVEN BEANSMessage-Driven Beans (MDB - Beans Dirigidos por Mensajes) son componentes de tipolistener que actuan de forma asíncrona. Su misión es la de consumir mensajes (por ejemploeventos que se producen en la aplicación), los cuales pueden gestionar directamente oenviar (derivar) a otro componente. Los MDB actuan sobre un proveedor de mensajería,por ejemplo Java Messaging System (JMS es además soportado de forma implícita por laespecificacion EJB).

Al igual que los Stateless Session Beans, los Message-Driven Beans no mantienen estadoentre invocaciones.

1.6 ENTITY BEANSLos Entity Beans (EB - Beans de Entidad) son representaciones de datos almacenados enuna base de datos, siempre en forma de POJO's. El encargado de gestionar los EB esEntityManager, un servicio que es suministrado por el contenedor y que está incluido en laespecificación Java Persistence API (JPA - API de Persistencia en Java). JPA es parte deEJB desde la versión 3.0 de esta última. Para saber más sobre JPA, puedes visitar untutorial anterior publicado en esta misma web en la siguiente dirección.

Al contrario que los Session Beans y los Message-Driven Beans, los Entity Beans no soncomponentes del lado del servidor. En otras palabras, no trabajamos con una vista delcomponente, si no con el componente real.

1.6 EJB Y TEST-DRIVEN DEVELOPMENTPara terminar este primer artículo, dejemos de lado la teoría y escribamos una sencillaaplicación EJB para ir abriendo el apetito. Para poder seguir este y futuros ejemplos, debestener en marcha un entorno compatible con EJB 3.1 (aunque la mayoría de los ejemplos,incluido este, funcionarán en un contenedor compatible con EJB 3.0). Por otro lado, todo elcódigo se ajusta al estandard EJB 3.1 (no se usarán extensiones exclusivas de uncontenedor concreto). Sin embargo, ten presente que las indicaciones relativas a la creacióndel proyecto, despliegue, y ejecución de los ejemplos estarán condicionadas por el entornoconcreto que se ha puesto en marcha mediante el anexo que acompaña este tutorial; si tuentorno es diferente, ciertas acciones (como los opciones de menús a ejecutar en tu IDE)serán otras.

Page 5: Manual EJB

Como se ha indicado en el punto 1.3, este ejemplo de desarrollará de manera puntualmediante Test-Driven Development. En los próximos artículos solo se mostrará códigoEJB, que es al fin y al cabo el tema a tratar en este turorial. Así mismo, los pasos para crearun proyecto o como desplegarlo en el contenedor EJB se omitirán en artículos posteriores.

Para comenzar inicia Eclipse (si aún no lo has hecho) y crea un nuevo proyecto EJB:

File > New > EJB Project

Dale un nombre al nuevo proyecto y asegúrate tanto de seleccionar en el desplegable'Target runtime' un contenedor compatible con EJB como de seleccionar la versión 3.1 en eldesplegable 'EJB module version'. Haz click en el botón 'Finish' para crear el proyecto.

Ahora es el momento de crear un test que defina y respalde nuestro primer EJB. Lo primeroes crear una carpeta dentro del proyecto donde almacenaremos todos los tests queescribamos. En la pestaña 'Project Explorer' haz click con el botón derecho sobre elproyecto EJB y selecciona:

New > Source Folder

Introduce el nombre del directorio en el campo 'Folder name' (utiliza un nombredescriptivo, como 'tests') y haz click en el botón 'Finish'. Si expandes el proyecto EJB (conla flecha negra que hay a la izquierda del nombre) verás que el nuevo directorio se hacreado correctamente. Ahora vamos a crear la clase donde escribiremos los tests paranuestro EJB. Haz click con el botón derecho sobre el directorio de tests y selecciona:

New > Other

En la ventana que te aparecerá selecciona:

Java > JUnit > JUnit Test Case

En la ventana de creación de un test de JUnit selecciona la opción 'New JUnit 4 test',introduce el nombre del paquete donde deseas alojar la clase en el campo 'Package' (muyrecomendado) y escribe un nombre para la clase de tests. En mi caso, el nombre del paqueteserá 'es.davidmarco.ejb.slsb' y el nombre de la clase de tests'PrimerEJBTest'. Haz click en el botón 'Finish'; si es el primer tests que escribes para elproyecto (como es nuestro caso) aparecerá una ventana donde Eclipse nos informa que lalibrería JUnit no está incluida en el classpath. Seleccionamos la opción 'Perform thefollowing action: Add JUnit4 library to the build path' (Realizar la siguiente acción: añadirla libreria JUnit 4 al path de construcción) y haz click en el botón 'OK'. Hecho esto, yapodemos escribir nuestro primer (y de momento único) test:

package es.davidmarco.ejb.slsb;

import org.junit.Test;import static org.junit.Assert.*;

Page 6: Manual EJB

public class PrimerEJBTest {@Testpublic void testSaluda() {

PrimerEJB ejb = new PrimerEJB();assertEquals("Hola usuario", ejb.saluda("usuario"));

}}

El test (un método que debe ser void, no aceptar parámetros, y estar anotado con laanotación de JUnit @Test) declara las intenciones (el contrato) del código que estamosdiseñando: crear un objeto de la clase PrimerEJB con un método saluda() que acepte unargumento de tipo String y devuelva un mensaje con el formato 'Hola argumento'. Aquíempezamos a ver las ventajas de EJB: podemos testear nuestro código de negociodirectamente, sin tener que desplegar el componente EJB en un contenedor y entonceshacer una llamada a este, con toda la parafernalia que esto requiere.

Y ahora si (por fin) vamos a escribir nuestro primer EJB. Nuestra clase de negocio ira en unpaquete con el mismo nombre que el utilizado para almacenar las clase de tests, pero en unacarpeta diferente. De esta manera mantenemos ambos tipos de clases separadas físicamenteen disco (por motivos de organización y por claridad), pero accesibles gracias a quevirtualmente pertenecen al mismo paquete, y por tanto entre ellos hay visibilidad de tipopackage-default (esto puede resultarnos útil si necesitamos, por ejemplo, acceder desde laclase de tests a métodos en las respectivas clases de negocio que han sido declarados como'protected'). En la pestaña 'Project Explorer' haz click con el botón derecho en la carpeta'ejbModule' (la carpeta por defecto que crea Eclipse en un proyecto EJB para almacenarnuestras clases) y selecciona:

New > Class

Introduce en el campo 'Package' el nombre del paquete donde vamos a almacenar la clasede negocio (en mi caso 'es.davidmarco.ejb.slsb') y en el campo 'Name' el nombre de la clasede negocio; para este último caso es conveniente usar el nombre de la clase de tests sin elsufijo 'Test' (en mi caso 'PrimerEJB') de manera que podamos asociar visualmente en elexplorador del IDE cada clase de negocio ('Xxx') con su clase de tests ('XxxTest'). Hazclick en el botón 'Finish' para crear la clase de negocio y añade la lógica de negocio(comienza con la solución mas simple posible):

package es.davidmarco.ejb.slsb;

public class PrimerEJB {public String saluda(String nombre) {

return null;}

}

Page 7: Manual EJB

Si en este punto ejecutamos el test que hemos escrito (haciendo click con el botón derechosobre el editor donde tenemos el código del test y seleccionando 'Run As > JUnit Test') estefallará (verás una barra de color rojo que indica que al menos un tests no ha pasadocorrectamente). Si observas la ventana 'Failure trace' (seguimiento de fallos) de la pestañade resultados de JUnit, verás un mensaje que, traducido a español, indica que se esperabacomo respuesta 'Hola Usuario' pero se recibió 'null'. Debajo de este mensaje puedes ver lapila de llamadas que ha generado el error, en nuestro caso ha sido la función estáticaAssertEquals de JUnit. Volvamos al editor donde tenemos la clase de negocio yarreglemos el código que está fallando:

package es.davidmarco.ejb.slsb;

public class PrimerEJB {public String saluda(String nombre) {

return "Hola usuario";}

}

Si ahora ejecutamos el test, veremos en la pestaña de JUnit que la barra es ahora de colorverde, lo cual indica que todos los tests se han ejecutado sin fallos (la ventana 'FailureTrace' esta ahora vacía, evidentemente). Ahora decidimos que, cuando un cliente pase unargumento de tipo null a nuestra función, esta deberá devolver un saludo por defecto.Renombremos el nombre del primer test que hemos escrito y escribamos un segundo testque pruebe esta nueva condición (desde ahora y hasta el final de esta sección omitiré enambas clases las sentencias package e import por claridad):

public class PrimerEJBTest {@Testpublic void testSaludaConNombre() {

PrimerEJB ejb = new PrimerEJB();assertEquals("Hola usuario", ejb.saluda("usuario"));

}

@Testpublic void testSaludaConNull() {

PrimerEJB ejb = new PrimerEJB();assertEquals("Hola desconocido", ejb.saluda(null));

}}

Como se dijo previamente, mediante TDD estamos dejando claras las intenciones denuestro código antes incluso de escribirlo, como se puede ver en el segundo tests. En él,esperamos recibir como respuesta la cadena de texto 'Hola desconocido' cuandoinvoquemos el método saluda() con un argumento de tipo null. Y mientras tanto, elprimer test (que hemos renombrado para darle más claridad y expresividad a nuestros tests)debe seguir pasando, por supuesto. Ejecutamos los tests ('Run As > JUnit Test') y el nuevo

Page 8: Manual EJB

test que hemos escrito falla (podemos ver en la ventana a la izquierda de la pestaña de JUnitel/los test/s que ha/n fallado marcados con una cruz blanca sobre fondo azul). Volvamos aleditor donde estamos escribiendo la lógica de negocio e implementemos la nuevafuncionalidad:

public class PrimerEJB {public String saluda(String nombre) {

if(nombre == null) {return "Hola desconocido";

}

return "Hola usuario";}

}

Ahora ambos tests pasan. Para terminar, ¿que ocurriría si en lugar de la cadena de texto'usuario' pasamos al método saluda() una cadena de texto distinta?. Añadamos un test quepruebe esta condición:

public class PrimerEJBTest {@Testpublic void testSaludaConNombre() {

PrimerEJB ejb = new PrimerEJB();assertEquals("Hola usuario", ejb.saluda("usuario"));

}

@Testpublic void testSaludaConOtroNombre() {

PrimerEJB ejb = new PrimerEJB();assertEquals("Hola Pedro", ejb.saluda("Pedro"));

}

@Testpublic void testSaludaConNull() {

PrimerEJB ejb = new PrimerEJB();assertEquals("Hola desconocido", ejb.saluda(null));

}}

Este último test demuestra, al ejecutarse y fallar, que nuestra lógica de negocio contiene unbug: siempre que invocamos el método saluda() con un parámetro de tipo String

(diferente de null) obtenemos la cadena de texto 'Hola usuario', ignorando así el parametroque le hemos pasado. He aquí otra ventaja más que surje del uso de TDD: descubrir bugs loantes posible. Cuanto más tiempo tardemos en descubrir un bug, más dificil nos resultaráencontrarlo y solucionarlo. Vamos a resolver este último error en nuestra lógica de negocio:

public class PrimerEJB {

Page 9: Manual EJB

public String saluda(String nombre) {if(nombre == null) {

return "Hola desconocido";}

return "Hola " + nombre;}

}

Ahora todos los tests pasan. Aunque aún nos quedaría la tarea de refactorizar los tresmétodos de tests (hay código redundante en todos ellos) y tal vez añadir algún test más (oeliminar...), vamos a dejar las cosas aquí. TDD es un tema demasiado amplio y complejoque está fuera del propósito de este tutorial. Aunque este ejemplo ha sido extremadamentesimple/tonto/llamalo-como-quieras, nos ha servido para demostrar lo facil que es diseñar uncomponente EJB paso a paso y libre de errores. Nadie quiere software que falle, y por tantodebes tomarte la tarea de testear el código que escribes muy en serio. Test-DrivenDevelopment es una manera muy sencilla y divertida de diseñar software de calidad.

1.7 DESPLEGANDO NUESTRO PRIMER EJBHasta ahora hemos tratado la clase PrimerEJB como si fuera un componente EJB. Pero locierto es que no es así (en otras palabras, te he mentido, aunque espero que puedasperdonarme...). Para que nuestro POJO sea reconocido por nuestro contenedor como uncomponente EJB verdadero tenemos que decirle que lo es:

package es.davidmarco.ejb.slsb;

import javax.ejb.Stateless;

@Statelesspublic class PrimerEJB {

// ...}

La anotación @Stateless define nuestro POJO como un Session Bean de tipo Stateless yuna vez desplegado en un contenedor EJB, este lo reconocerá como un componente EJBque podremos usar. ¡Así de simple!. Sin embargo, debemos añadir una segunda anotación anuestro (ahora si) componente EJB:

package es.davidmarco.ejb.slsb;

import javax.ejb.Remote;import javax.ejb.Stateless;

@Remote@Stateless

Page 10: Manual EJB

public class PrimerEJB {public String saluda(String nombre) {

if(nombre == null) {return "Hola desconocido";

}

return "Hola " + nombre;}

}

La anotación @Remote permite a nuestro EJB ser invocado remotamente (esto es, desdefuera del contenedor). Si omitimos esta anotación, el EJB sería considerado como 'Local'(concepto que veremos en el próximo artículo) y solo podría ser invocado por otroscomponentes ejecutandose dentro del mismo contenedor. Nosotros la hemos incluido puesen la próxima sección vamos a escribir un cliente Java externo al contenedor que solicitaráa este el componente que estamos diseñado. De manera adicional, todos los componentesque sean de tipo remoto (como este) deben extender una interface (o de lo contrario seproducirá un error durante el despliegue):

package es.davidmarco.ejb.slsb;

public interface MiInterfaceEJB {public String saluda(String nombre);

}

Por último modificamos nuestro EJB para que implemente la interface y el despliegue seacorrecto:

@Remote@Statelesspublic class PrimerEJB implements MiInterfaceEJB {

// ...}

La necesidad de una interface para componentes de tipo remoto, aunque que a priori puedaparecer una restricción (o una limitación), es necesaria para que el contenedor puedaconstruir la vista/proxy que será enviada a los clientes remotos (externos al contenedor) pormotivos que no vienen al caso. Además, se considera una buena práctica que nuestrasclases y métodos de negocio se construyan sobre interfaces: de esta manera los clientes queusan nuestro código trabajan con la interface, ignorando la implementación concreta. Deesta manera podemos cambiar dicha implementación en el futuro sin romper el código denuestros clientes.

Ahora ya podemos desplegar nuestra primera aplicación EJB en el contenedor. En lapestaña 'Project Explorer' haz click con el botón derecho sobre el nombre del proyecto, y

Page 11: Manual EJB

selecciona:

Run As > Run on Server

Durante el primer despliegue nos aparecerá una ventana donde podemos seleccionar elservidor donde deseamos realizar el despliegue (aparecerá por defecto el que definimos alcrear el proyecto). Seleccionamos la casilla 'Always use this server when running thisproject' (Usar siempre este servidor cuando se ejecute este proyecto) y hacemos click en elbotón 'Finish'. La pestaña 'Console' se volverá activa y en ella veremos multitud deinformación relativa al proceso de arranque del servidor (puesto que no estaba arrancado).Tras unos momentos (30-40 segundos en mi equipo) el contenedor se habra levantado, ycon él nuestra aplicación EJB. Entre los últimos mensajes de arranque del servidor puedesver los siguientes:

PrimerEJB/remote - EJB3.x Default Remote Business InterfacePrimerEJB/remote-es.davidmarco.ejb.slsb.InterfaceEJB - EJB3.x Remote Business

Interface

Esas dos lineas nos indican dos referencia JNDI válidas al componente EJB que hemosdesplegado, y las necesitaremos cuando escribamos el cliente para que el contenedor nosdevuelva el objeto correcto.

Antes de finalizar esta sección, veamos un último asunto relativo al despliegue. Una vezque ya tenemos desplegada nuestra aplicación en JBoss, si realizamos un cambio en nuestralógica de negocio y deseamos volver a desplegar la aplicación en el contenedor, debemoshacerlo desde la pestaña 'Servers' de Eclipse (y no desde la pestaña 'Project Explorer' comohicimos la primera vez que desplegamos la aplicación). Para ello, primero abrimos lapestaña 'Servers' si no es visible en el Workbench de Eclipse:

Window > Show Views > Servers

Si miramos a la recién abierta pestaña 'Servers' veremos la instancia de JBoss asociada anuestro proyecto, y junto a ella una flecha. Pinchamos en esta flecha para expandir elservidor y veremos nuestro proyecto EJB. Hacemos click con el botón derecho sobre elnombre del proyecto y seleccionamos 'Full Publish'. En unos segundos nuestro proyectoestará re-desplegado (puedes ver el proceso en la pestaña 'Console').

Por otro lado, cuando iniciemos el IDE y queramos acceder a una aplicación desplegadacon anterioridad (por ejemplo desde un cliente como el que vamos a construir en lapróxima sección) debemos iniciar primero el servidor, evidentemente. Para ello, haz clickcon el botón derecho sobre el nombre del servidor en la pestaña 'Servers' y selecciona'Start'.

1.8 EL CLIENTE JAVAAhora es el momento de escribir el cliente Java, el cual va a hacer una solicitud al

Page 12: Manual EJB

contenedor mediante JNDI para obtener el Stateless Session Bean que hemos creado en lasección 1.6 y desplegado en la sección 1.7. Con esto veremos como el contenedor gestionatodo el ciclo de vida de una aplicación EJB así como de sus componentes, mientras losclientes solo tienen que preocuparse de solicitar el componente que necesiten y usarlo. Loprimero es crear un nuevo proyecto Java:

File > New > Other

Seleccionamos 'Java Project', hacemos click en 'Next', le damos un nombre al proyecto yhacemos click en 'Finish'. En la pestaña 'Project Explorer' expandimos el proyectopinchando en la flecha que aparece a la izquierda de su nombre y en la carpeta 'src' creamosun nuevo paquete (muy recomendado) haciendo click con el botón derecho yseleccionando:

New > Package

Le damos un nombre al paquete (en mi caso 'es.davidmarco.ejb.cliente') y hacemos click en'Finish'. Volvemos a hacer click con el botón derecho sobre el paquete recién creado yseleccionamos:

New > Class

En la ventana que nos aparecerá le damos un nombre a la clase (en mi caso 'Cliente') ymarcamos la casilla 'public static void main(String[] args)' para que nos cree un métodomain() automáticamente, y hacemos click en el botón 'Finish'.

Antes de mostrar el código del cliente es preciso mencionar que para ejecutarlo se necesitanciertas librerias que, por motivos de simplicidad, vamos a obtener del primer proyecto (laaplicación EJB). Para ello, en la pestaña 'Project Explorer' haz click con el botón derechosobre el nombre del proyecto que hace de cliente y selecciona:

Build Path > Configure Build Path

En la ventana que se abrirá seleccionamos la pestaña 'Projects', hacemos click en el botón'Add', y marcamos la casilla correspondiente al proyecto donde está la aplicación EJB quehemos desplegado (y que, repito, contiene todas las librerias que necesita el cliente). Parafinalizar, hacemos click en el botón 'OK', y de nuevo hacemos click en el botón 'OK'. Elcódigo del cliente es el siguiente:

package es.davidmarco.ejb.cliente;

import java.util.Properties;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;import es.davidmarco.ejb.slsb.MiInterfaceEJB;

Page 13: Manual EJB

public class Cliente {

private static final String JNDI_PRIMER_EJB = "PrimerEJB/remote";

public static void main(String[] args) throws NamingException {Properties properties = new Properties();properties.put("java.naming.factory.initial",

"org.jnp.interfaces.NamingContextFactory");properties.put("java.naming.factory.url.pkgs",

"org.jboss.naming:org.jnp.interfaces");properties.put("java.naming.provider.url",

"jnp://localhost:1099");Context context = new InitialContext(properties);

MiInterfaceEJB bean = (MiInterfaceEJB)context.lookup(JNDI_PRIMER_EJB);

String respuesta = bean.saluda("Cliente Java");System.out.println(respuesta);

}}

El cliente contiene una constante llamada JNDI_PRIMER_EJB a la que le hemos dado elvalor de la referencia JNDI a nuestro componente (recuerda que este valor nos los dio elcontenedor cuando desplegó la aplicación, como vimos al final de la sección 1.7). Dentrodel método main() creamos un objeto de propiedades, introducimos los valores necesariospara acceder al contexto del contenedor EJB, y creamos dicho contexto mediante un objetoInitialContext y el objeto de propiedades que acabamos de crear y configurar.

A continuación viene lo realmente interesante: no obtenemos un objeto de tipoInterfaceEJB mediante el constructor new (como haríamos en una aplicación Javanormal), si no que se lo solicitamos al contenedor EJB a traves del método lookup() delcontexto que acabamos de crear. A este método le hemos pasado el nombre de la referenciaJNDI del componente que queremos obtener. Recuerda que cuando solicitamos alcontenedor un componente de tipo Session Bean (ya sea Stateless, Stateful, o Singleton) loque obtenemos no es una instancia del componente EJB (en nuestro caso PrimerEJB), si nouna vista (un objeto proxy) que sabe como alcanzar el objeto real dentro del contenedor.

Una vez que tenemos la vista podemos ejecutar cualquiera de los métodos que definimos ensu interface asociada (MiInterfaceEJB). Para ejecutar el cliente nada tan sencillo comohacer click con el botón derecho sobre el editor donde lo tenemos y seleccionar:

Run As > Java Application

En la pestaña 'Console' aparecera el resultado de la ejecución del cliente. A modo deexperimento puedes cambiar la invocación al método saluda(), pasarle un valor null enlugar de una cadena de texto, volver a ejecutar el cliente, y ver la respuesta del componenteEJB.

Page 14: Manual EJB

1.9 RESUMENEste primer artículo del tutorial de introducción a EJB 3.1 ha sido muy fructífero. En elhemos visto de manera superficial los conceptos básicos de la especificación EJB, unabreve introducción al contenedor EJB, los tipos de componentes que podemos producir, unsencillo ejemplo de Test-Driven Development + EJB, la forma de desplegar dicho ejemploen un contenedor compatible con EJB, y como acceder al componente (a través delcontenedor) desde un cliente Java.

En el próximo artículo veremos en profundidad dos de los tres tipos de componentes detipo Session Bean: Stateless Session Beans y Stateful Session Beans. Hasta entonces,¡felices despliegues!.

Introducción a EJB 3.1 (II)Publicado el 18 de Marzo de 2011

En el artículo anterior del tutorial de EJB 3.1 vimos de manera superficial que representa laespecificación EJB, que es un contenedor, los tipos de componentes dentro de unaaplicación EJB, y por último un sencillo ejemplo de desarrollo de una aplicación EJB 3.1mediante Test-Driven Development. En este segundo artículo vamos a empezar aprofundizar en algunos de esos temas, viendo dos de los tres tipos de Session Beans:Stateless y Stateful. Sin embargo, antes es necesario comprender algunos conceptosrelacionados 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 desesión dentro de una aplicación EJB. Comencemos.

2.1 METADATOSComo vimos en la primer artículo del tutorial, para declararar nuestros POJO's comoverdaderos componentes EJB necesitamos usar metadatos. Estos metadatos pueden ser dedos tipos:

- Anotaciones- XML

El uso de anotaciones es, a priori, el más sencillo y expresivo. Esta forma de aplicarinformación a nuestros componentes está disponible en la plataforma JavaEE desde suversión 5, y por tanto disponible también en JavaEE 6 (plataforma de la cual forma parte laespecificación EJB 3.1). El único punto en contra del uso de anotaciones es que, sideseamos cambiar el comportamiento de un componente, debemos recompilar nuestro

Page 15: Manual EJB

código (ya que la anotación acompaña al código Java). Por motivos de simplicidad, estaserá la forma de metadatos que se utilizará durante todo el tutorial. Puedes consultar lareferencia completa (en inglés) de las anotaciones soportadas en la especificación EJB 3.0en la siguiente dirección.

El uso de XML para añadir metadatos a nuestro código es la forma heredada de versionesanteriores 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 usode XML como fuente de metadatos reside en que no es necesario recompilar nuestro códigopara cambiar el comportamiento de nuestros componentes, con todas las ventajas que estopuede 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úntipo de información relativa a la especificación EJB, y por tanto es más portable. Porcontra, además de tener que mantener dos fuentes de información a la vez (el código Java yel archivo XML), el propio archivo XML puede ser dificil de mantener y entender cuandoalcanza cierta longitud y complejidad. En este tutorial no se verán ejemplos de metadadosen XML por motivos de simplicidad, aunque si deseas ampliar información puedes visitarel capítulo 19 de la especificación EJB 3.1, la cual puedes descargar desde la siguientedirección.

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

2.2 EL POOLUn pool es, expresado de forma básica, un almacén de objetos. El contenedor EJB mantieneuno o varios pools donde almacena componentes que están listos para ser servidos a uncliente 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ónEJB (mejorando así el rendimiento de esta última y de sus clientes).

Cuando un cliente obtiene una referencia, ya sea local (mediante @EJB, como veremos en lasección 2.5), o remota (mediante JNDI, como vimos en el ejemplo al final del artículoanterior), esta no apunta a ninguna instancia del componente en cuestión. Solamentecuando se invoca un método en dicha referencia, el contenedor extrae una instancia delpool 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 tutorialno necesitamos hacerlo en ningún momento. Los detalles concretos del funcionamiento delpool es algo que tampoco necesitamos saber de antemano para seguir el tutorial; cuando senecesite comprender un aspecto concreto de dicho funcionamiento, se explicará en lasección correspondiente.

Page 16: Manual EJB

2.3 LOCAL VS REMOTOComo se explicó muy brevemente en el primer artículo del tutorial, un Session Bean puedeser 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 VirtualMachine (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 referenciaque apunta al objeto en cuestión al cliente, pues dentro de la misma JVM está referencia esválida. Por contra, un Session Bean declarado como remoto está destinado a servirpeticiones de clientes externos al propio contenedor (como vimos en el ejemplo al final delartículo anterior). En este caso, lo que se pasa es una copia del objeto en cuestión, pues parauna JVM externa no tendrá ningun sentido una referencia a un objeto que no se encuentreen ella misma. Recuerda que esa copia no es una copia del componente EJB (una instanciadel POJO desplegado en el contenedor), si no una vista/proxy que sabe como acceder através de una red al objeto real.

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

@Local@Remote@Statelesspublic class MiBean implements MiInterfaceRemote {

// esta clase produce un error al desplegar}

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

@Localpublic interface MiBeanLocalInterface {

// declaración de métodos locales}

@Remotepublic interface MiBeanRemoteInterface {

// declaración de métodos remotos}

@Statelesspublic class MiBean implements MiBeanLocalInterface,MiBeanRemoteInterface {

// implementación de métodos locales y remotos}

Page 17: Manual EJB

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

public interface MiBeanLocalInterface {}

public interface MiBeanRemoteInterface {}

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

En el ejemplo anterior, hemos eliminado las anotaciones en las interfaces y las hemosaplicado 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, todoSession Bean declarado como remoto necesita implementar una interface de maneraobligatoria (dicha interface es necesaria para la creación del proxy/vista).

Por supuesto también podemos aprovechar las características de polimorfismo y herenciaen 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

Page 18: Manual EJB

{// implementación concreta para acceso remoto

}

En este tutorial los ejemplos se mantendrán siempre lo más simple posibles, pues a efectoslectivos toda la parafernalia del ejemplo anterior no es necesaria (de hecho escontraproducente). 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 CONTEXTA 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ónasociado a cada instancia de un componente EJB. A través de él podemos acceder, porejemplo, al servicio de seguridad: imaginemos que invocamos un método en un SessionBean dentro de una aplicación donde hay restricciones de seguridad, de manera que elSession Bean necesita comprobar si el cliente que lo está invocándo está autorizado o no ahacerlo. A través del contexto de ejecución de dicho Session Bean podemos acceder alservicio de seguridad y realizar dicha comprobación. Otros ejemplos del uso del contextode 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 unartí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 otroSession Bean como argumento de un método; esto es necesario, pues el contenedor nopermite pasar el Session Bean actual usando una referencia de tipo this (los detalles deporqué 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 actualmediante inyección de dependencia (asunto que se explicará en la sección 2.5):

@Local@Statelesspublic class MiBean {

@Resourceprivate SessionContext contexto;

// ...}

Mediante la anotación @Resource (Recurso) indicamos al contenedor que debe inyectar elcontexto de sesión asociado a dicha instancia en la variable contexto (cuando veamos elciclo de vida de los componentes EJB se verá cómo y cuándo se realiza esta operación).

Page 19: Manual EJB

Otra manera de acceder al contexto de sesión es usando la misma anotación, pero sobre unmétodo setter que siga el estandar JavaBean (no confundir con Enterprise JavaBean):

@Local@Statelesspublic class MiBean {

private SessionContext contexto;

@Resourcepublic void setContexto(SessionContext contexto) {

this.contexto = contexto;}

// ...}

Puesto que SessionContext extiende EJBContext, podemos acceder a ambas interfacesdesde la primera. Es muy importante que tengas presente que ciertos métodos enEJBContext están marcados como deprecated, y además lanzarán una excepción de tipoRuntimeException si son invocados. Te recomiendo que, por este motivo, consultes laAPI de EJBContext en la siguiente dirección.

2.5 INYECCIÓN DE DEPENDENCIASLa inyección de dependencias (Dependency Injection) es un proceso por el cual elcontenedor puede inyectar en un componente recursos que son necesarios. Un ejemplo deinyecció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 deinyección de dependencia surje cuando uno de nuestros componentes necesita de otrocomponente:

@Statelesspublic class UnComponente {

private OtroComponente dependencia;

public String metodo() {return dependencia.otroMetodo();

}}

En el ejemplo anterior, el Session Bean UnComponente requiere otro Session Bean de tipoOtroComponente. Puesto que, como ya se ha explicado, el contenedor EJB gestiona el ciclode vida de todos los componentes (toda la aplicación EJB funciona en un entornogestionado), una instanciación del tipo new OtroComponente() sería incorrecta (estainstancia no tendría, por ejemplo, un contexto de sesión asociado, pues ha sido inicializada

Page 20: Manual EJB

manualmente). La solución consiste en informar al contenedor de dicha dependenciamediante la anotación @EJB, dejando así en sus manos esta tarea:

@Local@Stateless(name="otroComponente")public class OtroComponente {

public String otroMetodo() {// ...

}}

@Statelesspublic 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, yfinalmente inyectar en la variable dependencia. En el ejemplo anterior, se registra unSession Bean con nombre otroComponente (gracias a@Stateless(name="otroComponente")), el cual puede ser accedido mediante inyecciónde dependencia gracias a @EJB(beanName="otroComponente").

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

2.6 COMPONENTES DEL LADO DEL SERVIDORLa primera pregunta que nos debemos hacer es: ¿Que es un componente del lado delservidor?. No es ni más ni menos que un componente que es gestionado de manera integrapor el contenedor. Esto es, el cliente del componente no interacciona con el componente ensi, si no con una representación (referencia/proxy/vista) del componente real. Loscomponentes 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 elcomponente Singleton y los Message-Driven Beans para el próximo artículo. Ahora otrabuena pregunta sería: ¿Que NO es un componente del lado del servidor? La respuesta es:

Page 21: Manual EJB

Entity Beans (EB - entidades). Los EB viajan entre el cliente y el servidor siendo siempre larepresentación del mismo objeto Java en ambos lados. Todo lo relacionado con EB setratará en el capítulo relacionado con persistencia (si estás interesado en este tema puedesvisitar un tutorial sobre Java Persistent API (JPA - API de Persistencia en Java) publicadoen esta misma web en la siguiente dirección).

2.7 STATELESS SESSION BEANS: CONCEPTOS BÁSICOSComo 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 diferentesinvocaciones de sus métodos. Podriamos considerar cada método dentro de un SLSB comoun 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 bajode 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 mantenerun estado entre invocaciones los hace aún más eficientes.

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

2.8 STATELESS SESSION BEANS: EL CICLO DE VIDAEl ciclo de vida de un componente describe los distintos estados (gestionados íntegramentepor el contenedor) por los que puede pasar cada instancia del componente desde que escreada 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 sidoinstanciada y construida por el contenedor, y se encuentra en el pool lista para recibirinvocaciones por parte de un cliente (cuando esta invocación sucede, la instancia esextraida el pool y asociada a una referencia que es pasada al cliente). A este estado se llegadurante el arranque del contenedor (el pool es poblado con cierto número de instancias), ycuando no existan suficientes instancias en el pool para servir llamadas de clientes (y portanto se deban crear más). En resumen, siempre que una nueva instancia del SLSB seacreada.

Page 22: Manual EJB

Durante la transición entre el primer estado y el segundo, el contenedor realizará tresoperaciones (en este orden):

- Instanciación del SLSB- Inyección de cualquier recurso necesario y de dependencias- 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 deClass.newInstance(). Por tanto, el SLSB debe tener un constructor por defecto (sinargumentos), 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 seutilizaron las anotaciones @Resource y @EJB para tales efectos. Estos y otros recursos soninyectados en el SLSB de forma automática por el contenedor durante esta operación. Demanera adicional, cada vez que un SLSB sea invocado por un nuevo cliente, todos losrecursos 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 (quepuede ser uno y solo uno) como un método callback, esto es, un método que reacciona antecierto evento (en este caso, a la construcción de la instancia, como su propio nombreindica). Dentro de este método tenemos la oportunidad de adquirir recursos adicionalesnecesarios para el SLSB, como una conexión de red o de base de datos. Este método debeser void, no aceptar parámetros, y no lanzar ninguna excepción de tipo checked. Alcontrario que la inyección de recursos, la ejecución del método @PostConstruct se ejecutauna 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 elnúmero de instancias en el pool, o porque se está produciendo un shutdown del servidor, serealiza 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 (Predestrucción), el cual es también un método callback, y el cual nos da la oportunidad deliberar cualquier recurso adquirido durante la construcción de la instancia (adquisición quehicimos en @PostConstruct). Al igual que ocurría con @PostConstruct, un métodoanotado con @PreDestroy es opcional, debe ser void, no aceptar parámetros, no lanzarninguna excepción de tipo checked, solo puede ser usado en un único método, y esejecutado una y solo una vez (durante la destrucción de la instancia).

2.9 STATELESS SESSION BEANS: UN SENCILLO EJEMPLOSupongamos que estamos desarrollando una aplicación de banca, y entre otras cosasnecesitamos escribir código que represente los conceptos de ingreso, retirada, ytransferencia de efectivo. Estos tres conceptos son operaciones que no requieren mantener

Page 23: Manual EJB

un estado dentro de la aplicación (esto es, una vez invocadas y terminadas no dependen deotras operaciones) y por tanto son excelentes candidatas para ser representadas mediante unSession Bean de tipo Stateless. Como ya hemos visto en multitud de ejemplos, paradeclarar un Session Bean de tipo Stateless utilizamos la anotación @Stateless (se omitenotras anotaciones como @Remote o @Local, así como todo código no estrictamentenecesario para explicar el tema tratado; desde este momento se dará por hecho esto en lamayoría de los ejemplos que veamos):

package es.davidmarco.ejb.slsb;

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

@Statelesspublic 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, CuentacuentaDestino, double cantidad) {

// transferir cantidad de cuenta origen a cuenta destino}

}

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

2.10 STATEFUL SESSION BEANS: CONCEPTOS BÁSICOSAl contrario que los Session Bean de tipo Stateless, los Stateful Session Bean (SFSB -Session Bean con Estado) mantienen estado entre distintas invocaciones de un mismocliente. Podríamos considerar un SFSB como una extensión del cliente en el contenedor, yaque cada SFSB está dedicado de manera exclusiva a un único cliente durante todo su ciclode vida. Otra diferencia entre SLSB y SFSB es que estos últimos no son almacenados en unpool, pues no son reusados, como veremos en la sección 2.11 cuando expliquemos su ciclode vida.

¿A que nos referimos cuando decimos que un SFSB mantiene un estado? La cuestión essimple: un SFSB almacena información a consecuencia de las operaciones que realiza en él

Page 24: Manual EJB

su cliente asociado, y dicha información (estado) debe estar disponible en invocacionesposteriores. De esta manera, un SFSB puede realizar una acción compleja mediantemultiples invocaciones. Un ejemplo muy común es el carrito de la compra de una tiendaonline; añadimos y eliminamos artículos del carrito en diferentes invocaciones, lo cual seríaimposible de realizar con un SLSB.

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

2.11 STATEFUL SESSION BEANS: EL CICLO DE VIDAEl ciclo de vida de un SFSB es ligeramente más complejo que el de su hermano pequeñoSLSB, 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 sidocreada aún. El segundo estado, preparado, representa una instancia del SFSB que ha sidoconstruida e inicializada, y está lista para servir llamadas de su cliente asociado (recuerdaque 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 temporalmentepara liberar recursos del servidor.

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

- Instanciación del SFSB- Inyección de cualquier recurso necesario y de dependencias, y asociación con el cliente- 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 deClass.newInstance(). Por tanto, el SFSB debe tener un constructor por defecto (sinargumentos), 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 seutilizaron las anotaciones @Resource y @EJB para tales efectos. Estos y otros recursos soninyectados en el SFSB de forma automática por el contenedor durante esta operación. Unavez finaliza la inyección de recursos y dependencias, el SFSB es asociado al cliente que hagenerado su creación.

Page 25: Manual EJB

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 (quepuede ser uno y solo uno) como un método callback, esto es, un método que reacciona antecierto evento (en este caso, a la construcción de la instancia, como su propio nombreindica). Al igual que con un SLSB, la función de este método es adquirir recursosadicionales que sean necesarios para el SFSB, como una conexión de red o de base dedatos. Y también al igual que su homónimo en un SLSB, este método debe ser void, noaceptar parámetros, no lanzar ninguna excepción de tipo checked, y no ser static nifinal. La ejecución del método @PostConstruct se ejecuta una y solo una vez, al final dela instanciación del componente. En este momento, el SFSB ya se encuentra en el estadopreparado, y procede a servir la llamada del cliente que ha generado la creación de lainstancia.

Por último, un SFSB en estado preparado puede realizar una transición a cualquiera de losotros dos estados de su ciclo de vida: no existe o pasivo. La transición al estado no existeocurre 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 contenedorque el cliente ha terminado de usar su SFSB asociado y, por tanto, dicha instancia ya no esnecesaria. En este momento la instancia es desasociada de su contexto de sesión yeliminada. 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 elsegundo caso (timeout), el SFSB ha sobrepasado un periodo de tiempo establecido y elcontenedor 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, sinpará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 liberarrecursos, persistiendo de manera temporal el estado del SFSB. La pasivación y posterioractivació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 lanzauna excepción de sistema (cualquiera de tipo unchecked cuya definición no esté anotadacon @ApplicationException), se producirá una transición al estado no existe sin llamar asu método @PreDestroy. Este comportamiento no está muy bien comprendido entre ciertaparte de la comunidad, pues a todas luces no parece muy correcto, pero es así y debestenerlo en cuenta.

2.12 STATEFUL SESSION BEANS: PASIVACIÓN Y ACTIVACIÓNDurante la vida de un SFSB, el contenedor puede decidir liberar recursos persistiendo demanera temporal el estado de dicho SFSB, liberando de esta manera memoria del sistema

Page 26: Manual EJB

(u otros recursos). A esto lo llamamos pasivación. Si durante la pasivación el cliente realizauna llamada al SFSB, el contenedor recreará en memoria la instancia persistida y procederacon dicha llamada; a esto lo llamamos activación. Todo el ciclo de activación-pasivaciónpuede 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 nosabe en ningún momento si se ha producido una pasivación o activación del SFSB asociadoa su sesión.

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

- Todos los tipos primitivos- Objetos que implementan la interface Serializable

- Referencias a factorias de recursos gestionados por el contenedor (comojavax.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 callbackpara controlar la pasivación-activación de un SFSB cuando existen datos que nodeben/pueden ser persistidos: @PrePassivate (Pre-pasivación) y @PostActivate (Post-activación). Ambas anotaciones son aplicadas en métodos dedicados a controlar cada unode los procesos, según corresponda (en la sección 2.13 veremos un ejemplo de un SFSBcon métodos de pasivación y activación).

@PrePassivate nos permite anotar un método (opcional) que deje el SFSB es un estadoadecuado para su pasivación. Puesto que la pasivación se lleva a cabo medianteserialización, todas las referencias a objetos no serializables deben ser puestas a null. Otraoperacion llevada a cabo mediante @PrePassivate es la liberación de recursos que nopertenezcan a la lista anterior. Una vez ejecutado el método @PrePassivate, el SFSB esserializado y persistido temporalmente (tal vez en disco, en una cache, etc; esto es decisiónde cada implementación de EJB). Es importante tener en cuenta que si un SFSB en estadopasivo 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 contenedorrealizará la activación del componente, deserializando la instancia desde el lugar donde seencuentra persistida, restaurándola al estado anterior a producirse la pasivación (inyeccióndel contexto de sesión, inyección de referencias a otros componentes EJB, etc), yejecutando un método anotado con @PostActivate (también opcional). En este métodopodemos inicializar todos los objetos no serializables a unos valores adecuados (no debesconfiar en los valores por defecto que el contenedor da a objetos no serializables, pues entre

Page 27: Manual EJB

implementaciones pueden ser diferentes), así como restaurar cualquier recurso necesariopara el correcto funcionamiento del SFSB. Una vez que la activación ha concluido, el SFSBpuede 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 cualquiermétodo callback.

2.13 STATEFUL SESSION BEANS: UN EJEMPLO SENCILLOSupongamos que estamos diseñando una aplicación de tienda online, con un carrito de lacompra donde los clientes pueden añadir artículos, eliminarlos, vaciar el carrito, o realizarun pedido. Puesto que cada una de estas operaciones se lleva a cabo de maneraindependiente 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 componenteSession Bean de tipo Stateful. Para declarar un SFSB usamos la anotación @Stateful yhacemos que la clase en cuestión implemente la interface Serializable (puesto que eseste 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 ejemploes ú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;

@Statefulpublic class Carrito implements Serializable {

private Map articulosEnCarrito = new HashMap();private BaseDeDatos bbdd;

public void añadirArticulo(Articulo articulo, int cantidad) {

Page 28: Manual EJB

// 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

}

@Removepublic void finalizarCompra() {

// procesar el pedido}

@PostConstruct@PostActivateprivate void inicializar() {

// obtener conexión con la base de datos}

@PrePassivate@PreDestroyprivate void detener() {

// liberar conexión con la base de datos}

}

Como puedes ver, la clase Carrito está anotada con @Stateful y marcada comoSerializable. Dentro de ella tenemos dos campos: artículosEnCarrito, donde sealmacena 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 depasivación), y BaseDeDatos, que es una clase ficticia que supuestamente nos permiterealizar operaciones sobre una base de datos, pero no puede ser pasivada (supongamos quemantiene una conexión con una base de datos real que no puede permanecer abierta durantela pasivación).

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

Los dos últimos métodos (inicializar y detener) se encargar de obtener y liberarrecursos 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 soncomunes a los procesos de construcción del SFSB y una posible activación posterior. De

Page 29: Manual EJB

igual manera, @PrePassivate y @PreDestroy acompañan al mismo método, por el mismomotivo. Ambos métodos, de tipo callback, son gestionados por el contenedor en base aeventos, y por tanto el cliente del SFSB no necesita saber de su existencia (nosotros loshemos declarado private, aunque cualquier nivel de visibilidad está permitido).

2.14 RESUMENEn este segundo artículo del tutorial de EJB hemos visto conceptos relacionados con latecnología EJB, como la definición del pool, local vs remoto, metadatos, inyección dedependencias, y contexto de sesión. Ademas, hemos visto en cierta profundidad los dostipos 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, ¡felizinyección de dependencias!

Introducción a EJB 3.1 (III)Publicado el 28 de Marzo de 2011

En el artículo anterior vimos algunos conceptos comunes a los componentes de unaaplicación EJB, como el contexto de sesión, o la diferencia entre remoto y local. Además,vimos en cierta profundidad los dos tipos de Session Bean más comunes en una aplicaciónEJB: Stateless y Stateful. En este artículo vamos a ver el último tipo de Session Bean(Singleton Session Bean), dos conceptos aplicables a todos los Session Bean y que sonnuevos en la especificación EJB 3.1 (vista sin interface y llamadas asíncronas), yfinalmente un nuevo tipo de componente: Message-Driven Bean. Comencemos.

3.1 SINGLETON SESSION BEANS: CONCEPTOS BÁSICOSEl Singleton Session Bean (no existe una traducción literal de la palabra Singleton, peroviene a expresar el concepto de instancia única; desde ahora nos referiremos a estecomponente como Singleton Session Bean, o Singleton a secas) es un nuevo tipo de SessionBean introducido en la especificación EJB 3.1. Este componente se basa en el patrón dediseño del mismo nombre definido por Erich Gamma, Richard Helm, Ralph Johnson y JohnVlissides en su libro Design Patterns: Elements of Reusable Object Oriented Software(Patrones de Diseño: Elementos Reusables de Software Orientado a Objetos; una lecturaimprescindible). Este patrón de diseño garantiza que de una clase dada solamente puedacrearse una instancia, con un punto de acceso global para acceder a dicha instancia. Lanaturaleza única de un componente Singleton conlleva un alto rendimiento dentro del

Page 30: Manual EJB

contenedor para este tipo de Session Bean.

Es importante tener en cuenta la sutil diferencia entre un componente de tipo Singleton ycualquiera de los otros dos tipos de Session Bean (SLSB y SFSB): cuando un cliente haceuna llamada a un método de un SLSB o SFSB, puesto que la instancia del Session Beanestá asociada a ese cliente en concreto (durante la invocación del metodo para un SLSB, ydurante toda la duración de la sesión para un SFSB), un único thread (hilo de ejecución) escapaz de acceder a dicho método, y por tanto el componente es seguro en terminos demulti-threading (ejecución de múltiples hilos). Sin embargo, cuando trabajamos con unSingleton, multiples llamadas en paralelo pueden estar produciendose en un momento dadoa su única instancia, y por tanto el componente debe garantizar que un hilo de ejecución noestá interfiriendo con otro hilo de ejecución, produciendo resultados incorrectos. Dicho deotra manera, un componente Singleton debe ser concurrente;

3.2 SINGLETON SESSION BEANS: CONCURRENCIA BÁSICADebido a la naturaleza de los Session Bean de tipo Singleton, tenemos que tener muy clarosciertos aspectos al diseñar este tipo de componente. Veamos un ejemplo de una clase dondeno tenemos en cuenta la cuestión de concurrencia:

class ClaseNoConcurrente {private int numeroDeInvocacionesDeEstaInstancia = 0;

public int metodoUno() {// lógica de negocioreturn numeroDeEjecuciones++;

}

public int metodoDos() {// lógica de negocioreturn numeroDeEjecuciones++;

}}

En la clase del ejemplo anterior, no se ha tenido en cuenta el aspecto de concurrencia (locual, dependiendo de las especificaciones del problema de negocio que queremos resolver,puede ser perfectamente legal). Una consecuencia de esto es que, cuando intervienenmúltiples hilos de ejecución en paralelo, una llamada al cualquiera de los métodosmetodoXxx puede devolver un resultado incorrecto. Veamos una posibilidad (entre muchaspermitidas por la JVM) de la ejecución de esta clase por varios clientes:

1. Un cliente C-1 llama a metodoUno()

2. El cliente C-1 obtiene el valor 1 para el número de ejecuciones3. Un cliente C-2 llama a metodoUno()

4. Un cliente C-3 llama a metodoDos()

6. El cliente C-3 obtiene el valor 2 para el número de ejecuciones

Page 31: Manual EJB

7. El cliente C-2 obtiene el valor 2 para el número de ejecuciones

¿Que ha ocurrido en el ejemplo anterior? El cliente C-2 cree que los métodos metodoXxxhan sido invocados dos veces (punto 7), cuando en realidad se han invocado tres veces(puntos 1, 3, y 4). Esto es debido a que la operación numeroDeEjecuciones++ no esatómica: cuando el código Java es convertido en código de bytes (.class), dicha operaciónse compone de muchas otras, de manera que la JVM puede para el hilo actual en mitad delincremento y dar tiempo de ejecución a otro hilo. Incluso aunque la operación fueraatómica, la JVM podría seguir deteniendo un hilo durante la lógica de negocio (antes derealizar el incremento), dando tiempo de ejecución a otro hilo y produciendo de nuevoresultados incorrectos. Puesto que un Singleton es compartido por muchos clientes(accedido por muchos hilos de ejecución) este comportamiento no es aceptable: elSingleton debe ser concurrente.

La especificación EJB 3.1 nos permite controlar la concurrencia de un Singleton de dosmaneras:

- Concurrencia Gestionada por el Contenedor (CMC - Contained-Managed Concurrency)- Concurrencia Gestionada por el Bean (BMC - Bean-Managed Concurrency)

Veamos cada una de ellas con cierto detalle.

3.3 SINGLETON SESSION BEANS: CONTAINED-MANAGED CONCURRENCYMediante CMC, el contenedor es responsable de gestionar toda la concurrencia en elSingleton mediante metadatos, liberando así al programador de la tarea de escribir códigoconcurrente. Por defecto, todos los componentes Singleton son gestionados por elcontenedor, aunque podemos especificarlo de manera explicita mediante la anotación@ConcurrencyManagement:

import javax.ejb.ConcurrencyManagement;import javax.ejb.ConcurrencyManagementType;import javax.ejb.Singleton;

@Singleton@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)class MiSingleton {

// ...}

En el ejemplo anterior hemos definido un componente Singleton mediante la anotación@Singleton, y hemos indicado al contenedor de forma explícita que gestione por nosotrosla concurrencia del componente mediante CMC (aunque, repito, este comportamiento es elofrecido por defecto para todos los Singleton). Cuando usamos CMC, todos los métodos dela clase tienen por defecto un bloqueo de tipo write (escritura). Este bloqueo es de utilidad

Page 32: Manual EJB

cuando uno o varios métodos deben ser accedidos de manera secuencial (hasta que uno notermine, otro no puede comenzar) para evitar que se obtengan resultados incorrectos. Esteacceso secuencial es necesario en casos como el del ejemplo de la sección 3.2, en el quevarios métodos escribían sobre el valor de una variable que después era devuelta al cliente.Como se puede deducir del comportamiento del bloqueo write, este afecta a todo el objeto,y no a métodos concretos (todos los métodos write del mismo Singleton deben compartirun único bloqueo).

Por otro lado, cuando un método realiza solo operaciones de tipo read (lectura), de maneraque no se altera el estado del componente, podemos indicarle al contenedor que no bloqueeninguna invocación a dicho método. De esta manera, el método pueda ser accedido pormúltiples hilos de ejecución simultaneamente (puesto que no modificamos el estado delcomponente, el acceso en paralelo es seguro). Tenemos que indicar que un método es detipo read mediante la anotación @Lock:

import javax.ejb.Lock;import javax.ejb.LockType;

// ...

@Lock(LockType.READ)public String metodo() {

// ...}

Aunque, como ya se ha dicho, el tipo de bloqueo write se aplica por defecto, podemosexpresarlo de forma explícita mediante @Lock(LockType.WRITE). Otra opción que nosbrinda CMC es la liberación de un bloqueo automáticamente si este no ocurre tras untiempo preestablecido (timeout):

import java.util.concurrent.TimeUnit;import javax.ejb.AccessTimeout;

// ...

@AccessTimeout(value=5, unit=TimeUnit.SECONDS)public String metodo() {

// ...}

3.4 SINGLETON SESSION BEANS: BEAN-MANAGED CONCURRENCYEn ocasiones, la concurrencia gestionada por el contenedor no es suficiente (tal vez no nospermite definir con suficiente detalle la manera en la que necesitamos que funcione laconcurrencia de nuestra aplicación, por ejemplo). Bean-Managed Concurrency (BMC -

Page 33: Manual EJB

Concurrencia Gestionada por el Bean) deja en manos del programador toda la gestión de laconcurrencia. Esto puede realizarse utilizando bloques synchronized, variables atómicascomo java.util.concurrent.atomic.AtomicInteger, etc. Podemos indicar que laconcurrencia de un componente Singleton será gestionada íntegramente por el programadormediante la anotación @ConcurrencyManagement (la cual usamos en la sección anteriorpara declarar explícitamente el comportamiento contrario, CMC):

import javax.ejb.ConcurrencyManagement;import javax.ejb.ConcurrencyManagementType;import javax.ejb.Singleton;

@Singleton@ConcurrencyManagement(ConcurrencyManagementType.BEAN)class OtraClaseConcurrente {

// Gestión explícita de la concurrencia}

La concurrencia en Java es un tema largo y complejo que no puede ser tratado en estetutorial. Un buen lugar para empezar (en inglés) es The Java Tutorials. Estoy seguro queexisten otras buenas fuentes de información en español, y si estás interesado tu buscadorfavorito te llevará hasta ellas.

3.5 SINGLETON SESSION BEANS: EL CICLO DE VIDAEl ciclo de vida de un Singleton muy parecido al de un SLSB, pues como en este último secompone de los dos mismos estados:

- No Existe (Does not exists)- Preparado (Method-ready)

La diferencia estriba en cuando se crea la única instancia del componente Singleton (tras eldespliegue de la aplicación o tras la primera invocación al componente), y en su duración (alo largo de toda la vida de la aplicación, esto es, hasta que la aplicación sea replegada o elservidor sea parado). Evidentemente, solo el primer comportamiento (el momento decreación) puede ser customizado, y para ello la especificación EJB nos ofrece, comosiempre, metadatos. Aunque el contenedor puede decidir inicializar la única instancia de unSingleton en el momento que considere más oportuno, podemos forzar que dicho procesoocurra durante el despliegue mediante la anotación @Startup:

import javax.ejb.Singleton;import javax.ejb.Startup;

@Singleton@Startuppublic class MiSingleton {

// ...

Page 34: Manual EJB

}

El código anterior fuerza al contenedor a crear la única instancia de MiSingleton en elmomento del despliegue de la aplicación EJB de la que forma parte. Esto comportamientoes llamado eager initialization (inicialización temprana). Ahora supongamos que unSingleton debe ser inicializado antes que otro Singleton (tal vez este último necesite ponerla aplicación en cierto estado necesario para el correcto funcionamiento del primero):podemos indicar esta dependencia en el orden de inicialización mediante la anotación@DependsOn:

@Singletonpublic class MiSingletonA {

// ...}

@Singleton@DependsOn("MiSingletonA")public class MiSingletonB {

// ...}

En el ejemplo anterior, indicamos al contenedor que en el momento de inicializar la únicainstancia de MiSingletonB, MiSingletonA debe haber sido inicializado. De esta manera, elcontenedor forzará la inicialización del Singleton dependiente si aún no se ha producido.Este comportamiento es totalmente ajeno al uso u omisión de @Startup: si un Singletondepende de otro, este último se creará antes que el primero.

Para terminar con el ciclo de vida de los componentes Singleton, indicar que, al igual quecon los componentes SLSB y SFSB, disponemos de las anotaciones @PostConstruct y@PreDestroy para responder a los eventos de creación y destrucción (respectivamente) delcomponente:

@Singletonpublic class MiSingletonA {

@PostConstructpublic void inicializar() {

// Cualquier operación/es necesaria/s para la creación delcomponente,

// como obtención de recursos}

@PreDestroypublic void detener() {

// Cualquier operación/es necesaria/s antes de la destrucción delcomponente,

// como liberación de recursos}

Page 35: Manual EJB

// lógica de negocio del Singleton}

3.6 SINGLETON SESSION BEANS: UN EJEMPLO SENCILLOLa utilidad del componente Singleton está limitada a ciertos problemas de negocio quepodemos (o debemos) resolver mediante una única instancia de un objeto. Ejemplos válidosde componentes Singleton serían gestores de ventanas, sistemas de ficheros, colas deimpresión, caches, etc. Para nuestro ejemplo de Singleton vamos a desarrollar un sistema delogging, el cual debe ser accedido por toda la aplicación a través de una única instancia:

package es.davidmarco.ejb.singleton;

import java.io.FileWriter;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import javax.annotation.PostConstruct;import javax.annotation.PreDestroy;import javax.ejb.Lock;import javax.ejb.LockType;import javax.ejb.Singleton;

@Singletonpublic class SistemaDeLog {

private FileWriter writer;

private enum Nivel {DEBUG, INFO, ERROR

}

@PostConstructprotected void inicializar() throws IOException {

writer = new FileWriter("aplicacion.log", true);}

@PreDestroyprotected void detener() throws IOException {

writer.flush();writer.close();

}

@Lock(LockType.WRITE)public void debug(String mensaje) {

escribirMensajeEnArchivo(Nivel.DEBUG, mensaje);}

@Lock(LockType.WRITE)public void info(String mensaje) {

escribirMensajeEnArchivo(Nivel.INFO, mensaje);}

Page 36: Manual EJB

@Lock(LockType.WRITE)public void error(String mensaje) {

escribirMensajeEnArchivo(Nivel.ERROR, mensaje);}

private void escribirMensajeEnArchivo(Nivel nivel, String mensaje) {String cabecera = generarCabecera(nivel);try {

writer.write(cabecera + mensaje + "\n");} catch (IOException ioe) {

throw new RuntimeException(ioe);}

}

private String generarCabecera(Nivel nivel) {String fechaMasHoraActual = new SimpleDateFormat("dd/MM/yyyy

HH:mm:ss").format(new Date());StringBuilder cabecera = new StringBuilder();cabecera.append("[");cabecera.append(nivel.name());cabecera.append("] ");cabecera.append(fechaMasHoraActual);cabecera.append(" - ");

return cabecera.toString();}

}

El ejemplo anterior, aunque más desarrollado que los vistos hasta ahora (es completamentefuncional), sigue siendo un ejemplo de juguete; sin embargo, es bastante interesante paraexplicar el componente Singleton. Existen verdaderos frameworks de logging que puedes(¡y debes!) usar en tus aplicaciones.

Las primero que nos interesa del ejemplo son los métodos callback @PostConstruct y@PreDestroy. En ellos se obtiene y libera (respectivamente) un recurso dado, en nuestrocaso el acceso a un fichero en disco. Esto nos muestra la utilidad del componente Singleton:únicamente una instancia debe crear, editar, y finalmente cerrar el mismo fichero en disco.Y esta única instancia es la que obtendrán todos los clientes del Singleton. Ambos métodosse han marcado con visibilidad protected para facilitar la tarea de testing (y por un motivoadicional que se explicará en la próxima sección).

Lo segundo que nos interesa del ejemplo son los tres métodos que se exponen al cliente:debug, info, y error. A traves de ellos el cliente puede realizar las operaciones delogging. Los tres han sido marcados como métodos write mediante@Lock(LockType.WRITE), y aunque este comportamiento es definido por defecto paratodos los métodos de un Singleton (y por tanto la anotación es redundante), se han incluidopara diferenciarlos del resto (siempre es aconsejable escribir código expresivo).

Por último, los métodos de utilidad escribirMensajeEnArchivo y generarCabecera sonmétodos de utilidad usados por la lógica de negocio de nuestro componente.

Page 37: Manual EJB

3.7 SESSION BEANS EN GENERAL: NO-INTERFACE VIEWComo se indicó en la sección anterior, el ejemplo del sistema de logging es completamentefuncional. Sin embargo, el componente no ha sido declarado local ni remoto (ejemplosanteriores omitían este aspecto intencionadamente para mantener el código simple). En estemomento es preciso introducir el concepto de vista sin interface.

Una vista sin interface (no-interface view) es una variación del concepto de componentelocal, e introducida en la especificación EJB 3.1. Como su nombre indica, se trata de uncomponente que no está anotado con @Local ni @Remote, ni implementa ninguna interfacede negocio donde se hayan aplicado cualquiera de las anotaciones anteriores. Si noimplementamos ninguna interface de negocio, ¿como sabe el contenedor que métodos delcomponente debe exponer a sus clientes? La respuesta es sencilla: todos aquellosdeclarados public, incluyendo los de sus superclases y los de tipo callback (en el ejemplode la sección anterior hemos declarado los métodos callback con un nivel de visibilidadmenor a public precisamente para evitar que sean expuestos). Aunque a efectos prácticosun componente de este tipo es tratado como uno local, puedes encontrar los detallesconcretos sobre la vista sin interface en la especificación EJB 3.1.

3.8 SESSION BEANS EN GENERAL: LLAMADAS ASÍNCRONASOtra de las novedades de la especificación EJB 3.1 es la posibilidad de llamar a losmétodos de nuestros Session Bean de forma asíncrona. Hasta ahora, todas las llamadas quehemos realizado han sido sincronas, de manera que los clientes deben esperar hasta que elmétodo invocado termine de ejecutarse para seguir ejecutando el resto de sus sentencias.Este es el comportamiento normal cuando invocamos un método en Java. Sin embargo,cuando realizamos una llamada asíncrona, el flujo de ejecución vuelve automáticamente ala aplicación que realiza la llamada, sin esperar el resultado de la llamada. Más tarde,podemos comprobar dicho resultado si existe y lo necesitamos.

Las llamadas asíncronas son de gran utilidad en situaciones en las que un método realizauna operación que necesita un tiempo considerable para completarse, y no deseamosbloquear al cliente mientras dicha operación se realiza. Veamos primero el ejemplo mássencillo posible de llamada asíncrona:

package es.davidmarco.ejb.slsb;

import javax.ejb.Asynchronous;import javax.ejb.Stateless;

@Statelesspublic class ClaseAsincrona {

@Asynchronouspublic void metodoLento() {

Page 38: Manual EJB

try {Thread.sleep(15000);

} catch(InterruptedException ie) {throw new RuntimeException(ie);

}}

}

En el ejemplo anterior, hemos declarado el método metodoLento() como un métodoasíncrono mediante la anotación @Asynchronous. Dicha anotación puede ser aplicadatambién a nivel de clase, en cuya caso todos los métodos de negocio (los declarados en unainterface local, en una interface remota, o los de visibilidad public en una vista sininterface) serán considerados como asíncronos. Dentro del método hemos simulado unproceso relativamente largo deteniendo durante quince segundos el hilo de ejecución queestá procesando la instancia del SLSB. Cualquier cliente que llame a este método no tendráque esperar esos quince segundos, ya que nada más realizar la llamada le será devuelto encontrol de ejecución, sin esperar a que el método asíncrono termine. Este comportamientose conoce como fire-and-forget (disparar y olvidar).

Otra posibilidad es que necesitemos el resultado de una invocación asíncrona. Supongamosque necesitamos un método que realiza un cálculo intensivo y, cuando finalmente obtiene elresultado, lo devuelve en una variable de tipo Double. La manera de declarar dicho métodosería similar a esta:

import java.util.concurrent.Future;import javax.ejb.AsyncResult;import javax.ejb.Asynchronous;

// ...

@Asynchronouspublic Future metodoLentoConResultado() {

try {Thread.sleep(15000);

return new AsyncResult(Math.PI);} catch(InterruptedException ie) {

throw new RuntimeException(ie);}

}

Como puedes ver, el método metodoLentoConResultado() devuelve un objeto de tipoFuture (más concretamente de su implementación AsyncResult) parametizado a Double.Es sobre este objeto Future sobre el que el cliente deberá comprobar si el métodoasíncrono ha terminado su ejecución, para obtener a continuación su resultado:

import java.util.concurrent.ExecutionException;

Page 39: Manual EJB

import java.util.concurrent.Future;import javax.naming.NamingException;

// ...

Future resultado = bean.metodoLentoConResultado();System.out.println("Método asíncrono invocado");System.out.println("Realizando otras tareas mientras se ejecuta el métodoasíncrono...");// ...

while(true) {if(!resultado.isDone()) {

Thread.sleep(2000);} else {

System.out.println("Método asincrono devuelve el resultado " +resultado.get());

break;}

}

El ejemplo anterior es parte de un cliente del SLSB asíncrono. En el, en un momento dado,se llama al método asíncrono, asignando el objeto Future devuelto por dicho método enuna variable. En este momento el resultado no está listo (recuerda que tardará quincesegundos en producirse), pero el control de la ejecución del cliente continua sin esperar.Más adelante el cliente comprueba si el resultado está finalmente disponible mediante elmétodo isDone() de la clase Future. Cuando esto ocurra, podemos obtener nuestroansiado resultado mediante el método get() (también de la clase Future), el cual devuelveun objeto del mismo tipo al que usamos para parametizar la respuesta del método asíncrono(en nuestro caso, un objeto Double). Es importante tener presente que el método get()

bloquea el hilo de ejecución del cliente hasta que el resultado esté listo, de manera quepodemos omitir por completo el bucle while:

Future resultado = bean.metodoLentoConResultado();System.out.println("Método asíncrono invocado");System.out.println("Realizando otras tareas mientras se ejecuta el métodoasíncrono...");// ...System.out.println("Método asíncrono devuelve resultado " +resultado.get());

La interface Future incluye otros métodos con los que podemos cancelar la ejecución delmétodo asíncrono, o comprobar si dicha ejecución ha sido cancelada por el contenedor (porejemplo en caso de excepción). Es recomendable que visites la API de Future si vas atrabajar con ella.

Antes de terminar con los métodos asíncronos, quiero hacer constar que en laimplementación del contenedor EJB que estamos usando en este tutorial (JBoss 6.0.0 Final)

Page 40: Manual EJB

las llamadas asíncronas aún no están completamente implementadas (el hilo de ejecucióndel cliente se bloquea al llamar al método asíncrono hasta que este ha terminado deejecutarse; en otras palabras, las llamadas asíncronas se comportan como llamadassíncronas). En otros contenedores compatibles con EJB 3.1, como Glassfish v3, elcomportamiento de las llamadas asíncronas si es el correcto (no lo he probadopersonalmente, pero así aparece indicado por usuarios de ambos servidores).

3.9 SESSION BEANS EN GENERAL: RESUMENCon esta sección terminamos de ver los componentes de tipo Session Bean, que sonaquellos que contienen la lógica de negocio de una aplicación EJB que puede ser invocadapor los clientes de dicha aplicación (ya sea en cualquiera de sus tres variaciones: Stateless,Stateful, o Singleton). Ahora es el momento de pasar a un nuevo tipo de componente conuna misión totalmente diferente: Message-Driven Beans.

3.10 MESSAGE-DRIVEN BEANS: CONCEPTOS BÁSICOSLos componentes de tipo Message-Driven Bean (MDB - Bean Dirigido por Mensajes) soncomponentes asíncronos de tipo listener (oyente). Un MDB no es más que un componenteque espera a que se le envie un mensaje, y realiza cierta acción cuando finalmente recibedicho mensaje (el MDB escucha por si alguien le llama, de ahí su nombre). Algunaspropiedades de los componentes MDB son:

- No mantienen estado- Son gestionados por el contenedor (transacciones, seguridad, concurrencia, etc)- Son clases puras que no implementan interfaces de negocio (puesto que son invocados

por un cliente)

Los componentes MDB forman parte de un sistema de mensajería, el cual se compone delos siguientes subsistemas:

Cliente (Productor) ---> Broker ---> Cliente (Consumidor) ---> Message-Driven Bean

En lo que refiere a este tutorial, el cliente consumidor será el propio contenedor EJB, elcual distribuirá los mensajes que consuma a los MDB correspondientes.

Los tres primeros subsistemas (clientes y broker) forman parte del servicio de mensajería,que en la especificación EJB es gestionado por defecto mediante JMS. En este momento espreciso desviarnos del camino para explicar con un mínimo de detalle que es y cómofunciona JMS.

3.11 JAVA MESSAGE SERVICE: CONCEPTOS BÁSICOSJava Message Service (JMS - Servicio de Mensajería en Java) es una API neutral que puede

Page 41: Manual EJB

ser usada para acceder a sistemas de mensajería. Todos los contenedores EJB 3.x debenproporcionar un proveedor de JMS (el cual define su propia implementación de la API), demanera que podamos trabajar con mensajes sin necesidad de añadir librerías externas.Puesto que esta API es neutral, podemos cambiar en cualquier momento el proveedor JMSpor uno que se adecue más a nuestras necesidades (o incluso usar un sistema de mensajeríadiferente a JMS).

Una aplicación JMS se compone, generalmente, de múltiples clientes JMS y un únicoproveedor JMS. Un cliente JMS puede ser de dos tipos:

- Productor: su misión es enviar mensajes- Consumidor: su misión es recibir mensajes

Por otro lado, la misión del proveedor JMS es dirigir y enviar los mensajes que le llegan atraves de un broker (esto es algo bastante más complejo, pero por simplicidad vamos apensar que tenemos un subsistema llamado broker donde se almacenan los mensajesenviados hasta que son servidos a todos sus consumidores). JMS proporciona un tipo demensajería asíncrona: los clientes JMS envían mensajes a traves del broker sin esperar unarespuesta. Es responsabilidad del broker hacer llegar el mesaje a los clientes JMS quedeban consumir el mensaje. De esta manera JMS proporciona un sistema de comunicaciónmuy poco acoplado, pues el productor y el consumidor no saben el uno del otro en ningúnmomento.

3.12 JAVA MESSAGE SERVICE: MODELOS DE MENSAJERÍAJMS proporciona dos modelos de mensajería, los cuales nos permiten definir elcomportamiento de nuestro sistema de mensajería:

- Publicar y Suscribir (pub/sub - Publish and Subscribe)- Punto a Punto (p2p - Point to Point)

En el modelo pub/sub, un cliente JMS de tipo productor publicasus mensajes en un canalvirtual llamado topic (tema). A su vez, uno o varios clientes JMS de tipo consumidor sesuscriben a dicho topic si desean recibir los mensajes que en él se publiquen. Si unsuscriptor decide desconectarse del topic y más tarde reconectarse, recibirá todos losmensajes publicados durante su ausencia (aunque este comportamiento es configurable).Por todo esto, el modelo pub/sub es de tipo uno-a-muchos (un productor, muchosconsumidores).

En el modelo p2p, un cliente JMS de tipo productor publica sus mensajes en un canalvirtual llamado queue (cola). De manera similar al modelo pub/sub, pueden existirmultiples consumidores conectados al queue. Sin embargo, el queue no enviaráautomáticamente los mensajes que le lleguen a todos los consumidores, si no que son estosúltimos los que deben solicitarlos al queue. De manera adicional, solo un consumidorconsumirá cada mensaje publicado: el primero en solicitarlo. Cuando esto ocurra, elmensaje se borrará del queue, y el resto de consumidores no será siquiera consciente de la

Page 42: Manual EJB

anterior existencia del mensaje. Por todo esto, el modelo p2p es de tipo uno-a-uno (unproductor, un consumidor).

En versiones anteriores a JMS 1.1 cada uno de estos modelos usaba su propio conjunto deinterfaces y clases para para el envio y recepción de mensajes. Desde la citada versión deJMS, sin embargo, está disponible una API unificada que es válida para ambos modelos demensajería.

Ahora es el momento de volver al camino que dejamos dos secciones atrás, y seguir connuestros amados componentes MDB.

3.13 MESSAGE-DRIVEN BEANS: EL CICLO DE VIDATal como vimos en la sección 3.10, los componentes MDB no mantienen estado entreinvocaciones (son stateless, pero no confundir con los componentes SLSB). Por tanto, suciclo de vida es similar a los SLSB, constando de dos estados:

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

Un MDB en el primer estado es aquel que no ha sido creada aún, y por tanto no existe enmemoria. Un MDB en el segundo estado representa una instancia que ha sido instanciada einicializada por el contenedor. Se llega a este estado cuando se inicia el servidor (que puededecidir crear cierto número de instancias del MDB para procesar mensajes), o cuando elnúmero de instancias en el pool sea insuficiente para atender todos los mensajes que seestén recibiendo. La especificación EJB no fuerza a que exista un pool de MDB's, demanera que una implementación concreta de contenedor EJB ppdría decidir crear unainstancia cada vez que se reciba un mensaje (y eliminar esta instancia al terminar deprocesar el mensaje) en lugar de mantener un pool de instancias ya preparadas. Este últimoaspecto, sea como sea, no nos afecta como programadores (al diseñar un MDB) ni comoclientes; la forma en que sea gestionada nuestra aplicación es, a priori, responsabilidadexclusiva del contenedor.

Durante la transición entre el primer estado y el segundo, el contenedor realizará tresoperaciones (en este orden):

- Instanciación del MDB- Inyección de cualquier recurso necesario y de dependencias- Ejecución de un método dentro del MDB marcado con la anotación @PostConstruct, si

existe

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

Durante la inyección de dependencias, el contenedor inyectará automáticamente cualquier

Page 43: Manual EJB

recurso necesario para el MDB en base a los metadatos que hayamos proporcionado (comouna anotación @MessageDrivenContext, o una entrada en el descriptor XML de EJB). Demanera adicional, cada vez que un MDB procese un nuevo mensaje, todas las dependenciasse inyectarán de nuevo.

Por último, el contenedor ejecutará, si existe, un método dentro del MDB anotado con@PostConstruct (Post construcción). En este método podemos obtener recursosadicionales necesarios para el MDB (como conexiones de red, etc), recursos quepermanecerán abiertos hasta la destrucción del MDB. Las reglas de declaración de losmétodos callback como @PostConstruct se vieron una y otra vez en el artículo anterior.

Cuando el contenedor no necesita una instancia del MDB (ya sea porque decide reducir elnúmero de instancias en el pool, o porque se esta produciendo un shutdown del servidor),se realiza una transición en sentido inverso: del estado preparado en pool al estado noexiste. Durante esta transición se ejecutará, si existe, un método anotado con @PreDestroy

(Pre destrucción), donde podemos liberar los recursos adquiridos en @PostConstruct. Esimportante tener presente que dentro del método @PreDestroy todavía tenemos acceso alcontexto del MDB (lo mismo es válido para todos los Session Bean).

3.14 MESSAGE-DRIVEN BEANS: DEFINICIÓNComo ya hemos visto, los componentes de tipo MDB tienen como misión procesarmensajes enviados de forma asíncrona. Aunque todo contenedor compatible EJB 3.x debeincluir una implementación de JMS (de manera que tengamos un servicio de mensajería onthe box), MDB puede trabajar con otros servicios de mensajería diferentes. En este tutorialsolo se usará JMS como servicio de mensajería (afectando este hecho, por ejemplo, a lainterface que debe implementar el MDB, como veremos en el próximo párrafo).

Todo MDB debe implementar la interface javax.jms.MessageListener, la cual define elmétodo onMessage(). Es dentro de este método donde se desarrolla toda la acción cuandoel MDB procesa un mensaje, como veremos en el ejemplo de la próxima sección. Demanera adicional, debemos indicar al contenedor que nuestra clase es un componenteMDB. Para ello, utilizamos la anotación @MessageDriven:

package es.davidmarco.ejb.mdb;

import javax.ejb.MessageDriven;import javax.jms.Message;import javax.jms.MessageListener;

@MessageDriven()public class PrimerMDB implements MessageListener {

public void onMessage(Message message) {// procesar el mensaje

}}

Page 44: Manual EJB

El ejemplo anterior aún no es funcional (producirá un error de despliegue), ya que el MDBnecesita saber el tipo de canal virtual (topic/queue) al que debe conectarse, así como elnombre de dicho canal virtual. Esta información forma parte de la configuración del MDB,configuración que proporcionamos al componente a través del atributo activationConfig

(configuración de activación) de la anteriormente vista anotación @MessageDriven:

@MessageDriven(activationConfig={@ActivationConfigProperty(propertyName="destinationType",

propertyValue="javax.jms.Topic"),@ActivationConfigProperty(propertyName="destination",

propertyValue="topic/MiTopic")})public class PrimerMDB implements MessageListener {

// ...}

En el ejemplo anterior, hemos proporcionado al MDB la configuración necesaria medianteun array de anotaciones @ActivationConfigProperty. Cada una de estas anotacionescontiene dos atributos: propertyName y propertyValue, en los cuales indicamos parejasnombre-de-la-propiedad/valor-de-la-propiedad, respectivamente. Volviendo a nuestroúltimo ejemplo, hemos configurado el MDB con las siguientes propiedades:

- El tipo de canal virtual al que el MDB se conectará (javax.jms.Topic)- El nombre JNDI del canal virtual al que el MDB se conectará (topic/MiTopic).

Otra opción de configuración bastante interesante (aunque opcional) es la duración de lasubscripción:

@ActivationConfigProperty(propertyName="subscriptionDurability",propertyValue="Durable")

Añadiendo la anotación anterior al array de propiedades de configuración del MDB,nuestro componente recibirá todos los mensajes que se envien a su canal virtual mientras elcontenedor EJB (y por tanto el propio componente MDB) esté offline. Esta situación puedeocurrir durante un shutdown del servidor, un problema de conectividad por red, etc. Enotras palabras, el mensaje estará disponible hasta que todos sus suscriptores de tipo durablelo hayan recibido y procesado. La opción contraria se aplica cambiando el valor del atributopropertyValue a NonDurable (no durable). La durabilidad de los mensajes solo afecta alos componentes MDB que trabajan con topics (modelo pub/sub), pues en un canal de tipoqueue no tiene ningún sentido almacenar un mensaje para componentes MDB offline (encuanto un MDB este online y lo consuma, el mensaje se eliminará).

Al igual que los componentes Session Bean, los componentes MDB funcionan dentro de uncontexto de ejecución. Y por tanto, al igual que los componentes Session Bean, nuestrosMDB pueden acceder a su contexto de ejecución si necesitan comunicarse con el

Page 45: Manual EJB

contenedor:

import javax.annotation.Resource;import javax.ejb.MessageDriven;import javax.ejb.MessageDrivenContext;

@MessageDriven(/*...*/)public class PrimerMDB implements MessageListener {

@Resourceprivate MessageDrivenContext context;

// ...}

En el ejemplo anterior indicamos al contenedor que inyecte una instancia deMessageDrivenContext mediante la anotación @Resource. MessageDrivenContextextiende la interface EJBContext, sin añadir ningún método. Solamente los métodostransaccionales son útiles al MDB, como se verá cuando trabajemos con transacciones enartículos posteriores. El resto de métodos de la interface lanzará una excepción si soninvocados, ya que no tiene sentido que un MDB pueda, por ejemplo, acceder al servicio deseguridad.

Por otro lado, un MDB también puede referenciar un Session Bean para realizar elprocesamiento del mensaje (mediante la anotación @EJB, sección 2.5 del artículo anterior),así como enviar él mismo sus propios mensajes mediante JMS (al final de la próximasección veremos un ejemplo de un MDB procesando mensajes, y a su vez enviando nuevosmensajes que serán procesador por otro MDB).

3.15 MESSAGE-DRIVEN BEANS: UN SENCILLO EJEMPLOYa hemos visto como los servicios de mensajería asíncronos desacoplan de forma completaal productor de un mensaje de su/s receptor/es: ninguno de ellos es consciente de la partecontraria. Esto debido a que, entre ellos, existe un broker donde se realiza todo el procesode almacenaje (ya sea en canales virtuales de tipo topic o queue) y reparto de los mensajesque se reciben.

El ejemplo que vamos a ver consta de tres partes:

- Creación de un canal virtual- Creación de un componente MDB para consumir mensajes- Creación de un cliente para enviar mensajes mediante JMS

El primer paso lógico es crear un canal virtual donde poder enviar mensajes (un topic paramodelos pub/sub o un queue para modelos p2p). En JBoss 6.0.0 Final (el servidor deaplicaciones que instalamos en el anexo que acompaña este tutorial), el proveedor JMSincorporado es HornetQ 2.1.2 Final. Para declarar un canal virtual de tipo topic, edita el

Page 46: Manual EJB

archivo llamado hornetq-jms.xml del directorio server/default/deploy/hornetq/ de tuinstalación de JBoss y añade lo siguiente (default es la configuración por defecto al crear elservidor en Eclipse; si seleccionaste otra configuración, modifica la ruta anterior a la quecorresponda):

<configuration xmlns="urn:hornetq"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd">

<!-- ... -->

<topic name="PrimerTopic"><entry name="/topic/PrimerTopic" />

</topic>

</configuration>

En el archivo XML anterior hemos definido un canal virtual de tipo topic (mediante elelemento <topic>) con nombre PrimerTopic, y lo hemos asociado a la dirección JNDI/topic/PrimerTopic (es necesario reiniciar JBoss si se encuentra levantado). La forma deconfigurar un canal virtual puede ser diferente entre distintos contenedores (incluso entredistintas implementaciones de un mismo contenedor), por lo que si estás usando unservidor de aplicaciones diferente a JBoss 6.0.0 Final o un proveedor JMS diferente aHornetQ 2.1.2 Final, quizá necesites revisar la documentación correspondiente paradeclarar el canal virtual.

Aunque este ejemplo se basará en un canal virtual de tipo topic, puedes declarar un queue(para montar un modelo de mensajería p2p) de la siguiente manera:

<configuration xmlns="urn:hornetq"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd">

<!-- ... -->

<queue name="PrimerQueue"><entry name="/queue/PrimerQueue" />

</queue>

</configuration>

El siguiente paso lógico sería escribir un componente MDB que procese los mensajes deltopic. Para ello, necesitamos crear un proyecto EJB en Eclipse y declarar una clase como lasiguiente:

package es.davidmarco.ejb.mdb;

Page 47: Manual EJB

import javax.ejb.ActivationConfigProperty;import javax.ejb.MessageDriven;import javax.jms.JMSException;import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.TextMessage;

@MessageDriven(activationConfig={@ActivationConfigProperty(propertyName="destinationType",

propertyValue="javax.jms.Topic"),@ActivationConfigProperty(propertyName="destination",

propertyValue="topic/PrimerTopic")})public class PrimerMDB implements MessageListener {

@Overridepublic void onMessage(Message message) {

if(message instanceof TextMessage) {try {

String contenidoDelMensaje =((TextMessage)message).getText();

System.out.println("PrimerMDB ha procesado el mensaje: "+ contenidoDelMensaje);

} catch (JMSException jmse) {throw new RuntimeException("Error al procesar un

mensaje");}

}}

}

En el ejemplo anterior declaramos un componente MDB (con la anotación@MessageDriven), lo configuramos para actuar como consumidor de los mensajes del topicregistrado con dirección JNDI topic/PrimerTopic (con las anotaciones@ActivationConfigProperty), y finalmente añadimos dentro del método onMessage() lalógica que procesará los mensajes. Recuerda que este método es el único declarado en lainterface MessageListener, la cual deben implementar todos los MDB basados en JMS.

Es interesante explicar con un mínimo detalle las operaciones que se realizan dentro denuestro método onMessage. Este método requiere un parámetro de tipojavax.jms.Message, que es una interface base de la cual extienden otras interfaces másespecíficas. Una de esas subinterfaces es TextMessage, la cual provee de métodos paratrabajar con mensajes cuyo contenido es texto. Por tanto, este MDB está diseñado pararecibir mensajes desde el topic asociado, y procesarlos si son de tipo TextMessage (en casoafirmativo, se imprime en el log del servidor el contenido del mensaje). Si durante dichoprocesamiento se produce un error, el MDB lanzará una excepción. Lo ideal es que el topicasociado solo reciba este tipo de mensajes, y que mensajes con otro tipo de contenido seanalmacenados en otros canales virtuales (de esta manera no se crearán instancias quefinalmente no procesarán el mensaje).

Tras desplegar el proyecto EJB con nuestro primer componente MDB, el último paso

Page 48: Manual EJB

lógico sería crear un cliente JMS que produjera mensajes y los enviara al topic. Este clientepodría ser, por ejemplo, un proyecto Java normal y corriente. Puesto que necesitamos laslibrerias de JMS (las cuales son parte de EJB 3.x), debemos incluirlas al crear nuestroproyecto. La forma más sencilla sería pulsando Next en la pantalla de creación del proyectoJava en Eclipse, seleccionando la pestaña Libraries y añadiendo las librerias del servidorJBoss:

Add Library > Server Runtime > Botón Next > JBoss 6.0 Runtime > Botón Finish >Botón Finish

Ahora ya estamos listos para escribir el cliente productor JMS:

package es.davidmarco.ejb.cliente;

import java.util.Properties;import javax.jms.Connection;import javax.jms.ConnectionFactory;import javax.jms.JMSException;import javax.jms.MessageProducer;import javax.jms.Session;import javax.jms.TextMessage;import javax.jms.Topic;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;

public class ClienteJMS {public static void main(String[] args) throws NamingException,

JMSException {Properties propiedades = new Properties();propiedades.put("java.naming.factory.initial",

"org.jnp.interfaces.NamingContextFactory");propiedades.put("java.naming.factory.url.pkgs",

"org.jboss.naming:org.jnp.interfaces");propiedades.put("java.naming.provider.url",

"jnp://localhost:1099");

Context contexto = new InitialContext(propiedades);ConnectionFactory factoria =

(ConnectionFactory)contexto.lookup("ConnectionFactory");

Topic topic = (Topic)contexto.lookup("topic/PrimerTopic");Connection conexion = factoria.createConnection();Session sesion = conexion.createSession(false,

Session.AUTO_ACKNOWLEDGE);MessageProducer productor = sesion.createProducer(topic);conexion.start();

TextMessage mensajeDeTexto = sesion.createTextMessage("Mensajeenviado desde Java");

productor.send(mensajeDeTexto);

conexion.close();

Page 49: Manual EJB

}}

En nuestro cliente JMS externo al contenedor lo primero que hacemos es crear un objeto depropiedades con los valores necesarios para conectar con el contenedor EJB (como hicimosen el cliente del primer artículo). A continuación creamos un contexto de ejecución desde elque poder acceder al contenedor, y mediante JNDI obtenemos un objeto factoríaConnectionFactory, a través del cual podremos realizar conexiones para el envío demensajes.

Ahora viene la parte interesante: creamos un objeto Topic que está asociado al canal virtualque hemos declarado en el primer paso lógico de esta sección (mediante su direcciónJNDI), creamos una conexión con el broker mediante el objeto factoría, iniciamos unanueva sesión dentro de la conexión recién creada, creamos un objeto MessageProducer

asociado al objeto Topic, iniciamos la conexión para poder enviar un mensaje, creamos unmensaje de tipo texto, lo enviamos, y finalmente cerramos la conexión.

Al ejecutar el cliente, el mensaje se enviará al topic, y puesto que existen clientesconsumidores asociados a ese topic, el contenedor EJB consumirá el mensaje, extraerá delpool MDB una instancia del componente MDB (o creará la instancia en el aire; a nosotrosnos es indiferente), y le pasará el mensaje al método onMessage() de dicha instancia paraque procese el mensaje. Si, tras ejecutar el cliente JMS, miras la pestaña Console deEclipse, verás el mensaje imprimido en el log de JBoss (tal como se definió al escribir elmétodo onMessage).

¿Recuerdas el sencillo esquema de la sección 3.10?:

Cliente (Productor) ---> Broker ---> Cliente (Consumidor) ---> Message-Driven Bean

- El cliente Java es el cliente productor- JMS (más concretamente su implementación HornetQ) es el broker- El contenedor EJB es el cliente consumidor- Una instancia MDB es quien procesa el mensaje

Lo interesante de un servicio de mensajería es que el cliente productor no sabe quienconsumirá su mensaje, ni como lo hará. Podría ser un MDB, o podría ser otro componenteen nada relacionado con la especificación EJB (tal vez ejecutándose en una máquina remotacon un sistema operativo distinto).

Por último, veamos como hacer que un MDB sea también productor:

package es.davidmarco.ejb.mdb;

import javax.annotation.Resource;import javax.ejb.ActivationConfigProperty;import javax.ejb.MessageDriven;

Page 50: Manual EJB

import javax.ejb.MessageDrivenContext;import javax.jms.Connection;import javax.jms.ConnectionFactory;import javax.jms.JMSException;import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.MessageProducer;import javax.jms.Session;import javax.jms.TextMessage;import javax.jms.Topic;

@MessageDriven(activationConfig={@ActivationConfigProperty(propertyName="destinationType",

propertyValue="javax.jms.Topic"),@ActivationConfigProperty(propertyName="destination",

propertyValue="topic/PrimerTopic")})public class PrimerMDB implements MessageListener {

@Resourceprivate MessageDrivenContext contexto;

@Overridepublic void onMessage(Message message) {

if(message instanceof TextMessage) {try {

String contenidoDelMensaje =((TextMessage)message).getText();

System.out.println("PrimerMDB ha procesado el mensaje: "+ contenidoDelMensaje);

enviarMensaje("Mensaje enviado desde MDB");} catch (JMSException jmse) {

throw new RuntimeException("Error al procesar unmensaje");

}}

}

private void enviarMensaje(String mensaje) throws JMSException {ConnectionFactory factoria =

(ConnectionFactory)contexto.lookup("ConnectionFactory");Topic topic = (Topic)contexto.lookup("topic/SegundoTopic");Connection conexion = factoria.createConnection();Session sesion = conexion.createSession(false,

Session.AUTO_ACKNOWLEDGE);MessageProducer productor = sesion.createProducer(topic);conexion.start();

TextMessage mensajeDeTexto = sesion.createTextMessage(mensaje);productor.send(mensajeDeTexto);

conexion.close();}

}

Page 51: Manual EJB

En el ejemplo anterior, dentro del método onMessage() se llama a un método de utilidadque envia un nuevo mensaje a un segundo topic (que debemos haber declarado conanterioridad, por supuesto). La única diferencia entre el código del método de utilidad y elmétodo main() del cliente Java es que, puesto que el primero se está ejecutando dentro delcontenedor EJB, podemos obtener el contexto de ejecución mediante inyección dedependencia, evitando así la necesidad de crear el objeto de propiedades y conectar por redal contenedor (lo cual sería bastante absurdo).

Ahora podemos declarar un segundo MDB que procese los mensajes del segundo topic:

package es.davidmarco.ejb.mdb;

import javax.ejb.ActivationConfigProperty;import javax.ejb.MessageDriven;import javax.jms.JMSException;import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.TextMessage;

@MessageDriven(activationConfig={@ActivationConfigProperty(propertyName="destinationType",

propertyValue="javax.jms.Topic"),@ActivationConfigProperty(propertyName="destination",

propertyValue="topic/SegundoTopic")})public class SegundoMDB implements MessageListener {

@Overridepublic void onMessage(Message message) {

if(message instanceof TextMessage) {try {

String contenidoDelMensaje =((TextMessage)message).getText();

System.out.println("SegundoMDB ha procesado el mensaje: "+ contenidoDelMensaje);

} catch (JMSException jmse) {throw new RuntimeException("Error al procesar un

mensaje");}

}}

}

Cuando PrimerMDB reciba un mensaje, la consola de JBoss (pestaña Console de Eclipse)mostrará como ambos MDB han procesado los mensajes de sus topics correspondientes:

19:00:47,453 INFO [STDOUT] PrimerMDB ha procesado el mensaje: Mensaje enviadodesde Java

19:00:47,475 INFO [STDOUT] SegundoMDB ha procesado el mensaje: Mensajeenviado desde MDB

Page 52: Manual EJB

3.16 RESUMENEn este tercer artículo del tutorial de EJB hemos terminado de ver los componentes de ladodel servidor, además de algunas características interesantes que son nuevas en laespecificación EJB 3.1: llamadas asíncronas y vista sin interface (ambas son aplicables acomponentes Session Bean, pero no a Message-Driven Beans).

En el próximo artículo veremos el último tipo de componente EJB que nos queda por ver(Entity Beans), así como la forma de realizar persistencia con JPA en una aplicación EJB3.1. En ningún caso se explicará a fondo JPA (ni que es, ni como funciona), así que si noconoces este framework te aconsejo que visites el tutorial de cuatro artículos sobre JPA quese encuentra publicado en esta misma web.

Introducción a EJB 3.1 (IV)Publicado el 07 de Abril de 2011

En los artículos anteriores del tutorial de introducción a EJB 3.1, hemos visto comodeclarar y trabajar con componentes de lado del servidor (Session Beans y Message-DrivenBeans). El último componente que nos queda por ver es Entity Beans (EB - Beans deEntidad; a partir de ahora nos referiremos a ellos como entidades).

4.1 ENTIDADES: CONCEPTOS BÁSICOSLas entidades, a diferencia del resto de componentes EJB, son objetos Java reales que sonmanejados entre componentes (o entre un cliente y un componente) en su forma original,nunca a través de proxys/vistas. Podemos crearlos con sentencias new, pasarlos comoparámetros a un Session Bean, etc. Pero el verdadero valor de las entidades reside en que suestado puede ser almacenado en una base de datos, y más tarde recuperado en un nuevoobjeto del tipo correspondiente. De manera adicional, los cambios que realicemos en elestado de una entidad serán sincronizados con la información que tenemos almacenada enla base de datos.

Aunque las entidades se consideran componentes EJB, hasta la version JavaEE 1.4pertenecían a una especificación independiente llamada Java Persistence API (JPA - API dePersistencia en Java). Fue a partir de la versión 5 de JavaEE que la especificación EJBabsorvió a la especificación JPA. Sin embargo, podemos trabajar con entidades en unaaplicación no-EJB, aunque, tras bambalinas, se seguirán ejecutando dentro de uncontenedor EJB. Desde ahora usaremos el término aplicación JPA para referirnos aaplicaciones que realizan persistencia, y el término aplicación EJB cuando existaintegración de ambas tecnologías.

Page 53: Manual EJB

En este artículo no vamos a tratar en profundidad la especificación JPA (ni la declaración niel uso de entidades), si no como integrarla con una aplicación EJB 3.1. Si no conoces JPA,es prácticamente obligatorio que visites el tutorial de JPA publicado en este mismo blog, demanera que puedas comprender todo el material que sigue a continuación.

4.2 ENTIDADES: EL CICLO DE VIDAComo ya se ha mencionado, las entidades no son objetos del lado del servidor. Sinembargo, cuando son usadas dentro del contexto de un contenedor EJB, se convierten enobjetos gestionados (gracias al servicio de persistencia, el cual es controlado mediante lainterface EntityManager). Esto nos lleva a los dos únicos estados de una entidad cuando seencuentra en el contexto de un contenedor EJB:

- Gestionada (Attached)- No gestionada (Detached)

En el primer estado, la entidad se encuentra gestionada por el servicio de persistencia:cualquier cambio que realicemos en su estado se verá reflejado en la base de datossubyacente. En el segundo estado, la entidad es un objeto Java regular, y cualquier cambioque realicemos en su estado no será sincronizado con la base de datos subyacente. En esteúltimo estado, la entidad puede ser, por ejemplo, enviada a traves de una red (medianteserialización).

4.3 ENTIDADES: UNIDAD DE PERSISTENCIAUna unidad de persistencia (persistence unit) representa un conjunto de entidades quepueden ser mapeadas a una base de datos, así como la información necesaria para que laaplicación JPA pueda acceder a dicha base de datos. Se define mediante un archivo llamadopersistence.xml, el cual debe acompañar a la aplicación donde se realizan las tareas depersistencia (recuerda que puede ser una aplicación EJB o una aplicación Java normal):

<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://java.sun.com/xml/ns/persistence"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/persistence

http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"version="2.0">

<persistence-unit name="introduccionEJB">

<!-- configuración de acceso a la base de datos --><!-- lista de entidades que pueden ser mapeadas -->

</persistence-unit></persistence>

Page 54: Manual EJB

Podemos definir más de una unidad de persistencia por aplicación, declarando cada una deellas mediante el elemento XML <persistence-unit>. Cada unidad de persistencia debeseguir estas dos reglas:

- Debe proporcionar un nombre (identidad) a través del cual pueda ser llamado- Debe conectar a una sola fuente de datos (data source)

Dependiendo del tipo de paquete que estemos construyendo (EAR, JAR, etc), el archivopersistence.xml deberá encontrarse en una localización u otra. En la sección 4.6 veremos unejemplo completo de este archivo, así como la información necesaria para su correctodespliegue dentro de una aplicación EJB.

4.4 ENTIDADES: CONTEXTO DE PERSISTENCIAOtro concepto que tenemos que tener claro es el de contexto de persistencia. Un contextode persistencia representa un conjunto de instancias de entidades que se encuentrangestionadas en un momento dado. Existen dos tipos de contextos de persistencia:

- Limitados a una transacción (Transaction-scoped)- Extendidos (Extended)

Cuando trabajamos dentro de un contexto de persistencia limitado a una transancción,todas las entidades gestionadas pasarán a estar no gestionadas cuando dicha transacciónfinalice. Dicho con otras palabras, los cambios realizados tras finalizar la transacción noserán sincronizados con la base de datos.

Cuando trabajamos dentro de un contexto de persistencia extendido, las cosas funcionan demanera diferente: el contexto de persistencia sobrevivirá a la transacción donde se ejecuta,de manera que los cambios que realicemos en el estado de las entidades gestionadas por elcontexto de persistencia se sincronizarán con la base de datos en el momento en queentremos en una nueva transacción. Este comportamiento es útil cuando trabajamos conSFSB, pues permite mantener un estado conversacional y mantener nuestras entidadessincronizadas.

4.5 ENTIDADES: ENTITY MANAGERMediante la interface EntityManager (Gestor de entidades) tenemos acceso al servicio depersistencia de nuestro contenedor. Podemos obtener una instancia de EntityManager ennuestros componentes EJB mediante inyección de dependencias:

import javax.ejb.Stateless;import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;

Page 55: Manual EJB

@Statelesspublic class MiSlsb {

@PersistenceContext(unitName="introduccionEJB")private EntityManager em;

// operaciones del SLSB}

El ejemplo anterior inyecta una instancia de EntityManager en el SLSB mediante laanotación @PersistenceContext. A esta anotación hay que proporcionarle como atributoel nombre de la unidad de persistencia que EntityManager usará para realizar lapersistencia (y que hemos definido en el archivo persistence.xml). Con los metadatosproporcionados obtendremos por defecto un contexto de persistencia limitado a unatransacción (ver sección anterior); si deseamos obtener un contexto de persistenciaextendido (solo válido en SFSB por su naturaleza conversacional), debemos añadir elatributo type con el valor correspondiente para este comportamiento:

import javax.persistence.PersistenceContextType;

// ...

@PersistenceContext(unitName="introduccionEJB",type=PersistenceContextType.EXTENDED)private EntityManager em;

En lo que se refiere a este tutorial, la integración de EJB con JPA termina aquí. Por tanto,podemos pasar a ver un ejemplo donde conectaremos todas las piezas para realizarpersistencia mediante EJB 3.1.

4.6 ENTIDADES: UN SENCILLO EJEMPLOVeamos un sencillo ejemplo de un componente EJB realizando persistencia sobre una basede datos. Al igual que algunos ejemplos anteriores, este consta de varias partes:

- Una fuente de datos (data source) donde realizar la persistencia- Una aplicación EJB donde se realiza las acciones de persistencia- Un cliente EJB desde el que intercambiar entidades con la aplicación EJB

Nuestro primer paso va a ser definir una fuente de datos que conectará nuestro contenedorEJB con nuestra base de datos. Siguiendo el entorno de desarrollo configurado en el anexoque acompaña este tutorial, escribimos un archivo llamado derby-ds.xml y lo guardamos enel directorio server\default\deploy de nuestra instalación de JBoss:

Page 56: Manual EJB

<?xml version="1.0" encoding="UTF-8"?><datasources><local-tx-datasource>

<jndi-name>DerbyDS</jndi-name><connection-

url>jdbc:derby://localhost:1527/introduccionEJB;create=true</connection-url>

<driver-class>org.apache.derby.jdbc.ClientDataSource40</driver-class>

<user-name></user-name><password></password>

</local-tx-datasource></datasources>

En el archivo XML anterior definimos una fuente de datos (data source) limitado atransacciones locales, esto es, dentro del propio contenedor (existe otro ambito de ejecuciónde una transacción llamado extendido, capaz de realizar su trabajo a través de múltiplescontenedores). A esta fuente de datos le hemos dado un nombre JNDI desde la que poderinvocarla, así como los parámetros de conexión con nuestra base de datos subyacente (url,usuario, y password). Si JBoss 6.0.0 Final está arrancado, nada más guardar el archivoanterior la fuente de datos será activada y se producirá la conexión con Derby, así quedeberás tener la base de datos iniciada o se producirá un error; nosotros vamos a considerarque en este preciso momento tanto JBoss como Derby están parados.

El siguiente paso es crear un proyecto EJB 3.1 en Eclipse, y continuación levantar la basede datos Derby desde el propio IDE. Para ello, haz click con el botón derecho sobre elnombre del proyecto EJB en la pestaña Proyect Explorer y selecciona:

Apache Derby > Add Apache Derby nature

La operación anterior añade a nuestro proyecto las librerias del servidor embebido Derby,de manera que podamos conectar con él. A continuación levantamos la base de datoshaciendo click con el botón derecho sobre el nombre del proyecto EJB en la pestañaProyect Explorer y seleccionando:

Apache Derby > Start Derby Network Server

Nos aparecerá una ventana donde se nos informa que la base de datos está siendo levantada,haz click en el botón OK para finalizar este proceso. En un entorno en producción, todasestas operaciones serían innecesarias, pues teóricamente tendríamos una base de datosexterna funcionando de manera continua.

Ahora vamos a crear un componente SLSB remoto, de manera que podamos llamarlo desdeun cliente Java normal. Como recordarás, un componente remoto requiere de maneraobligatoria implementar una interface:

package es.davidmarco.ejb.slsb;

Page 57: Manual EJB

import es.davidmarco.ejb.entidad.Cuenta;

public interface OperacionesConCuentas {public void crearCuenta(Cuenta cuenta);public Cuenta obtenerCuenta(Long id);public void borrarCuenta(Cuenta cuenta);

}

Ahora ya podemos implementar el componente remoto:

package es.davidmarco.ejb.slsb;

import javax.ejb.Remote;import javax.ejb.Stateless;import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;import es.davidmarco.ejb.entidad.Cuenta;

@Remote@Statelesspublic class OperacionesConCuentasRemote implements OperacionesConCuentas{

@PersistenceContext(unitName="introduccionEJB")private EntityManager em;

@Overridepublic void crearCuenta(Cuenta nuevaCuenta) {

em.persist(nuevaCuenta);}

@Overridepublic Cuenta obtenerCuenta(Long id) {

return em.find(Cuenta.class, id);}

@Overridepublic void borrarCuenta(Cuenta cuenta) {

em.remove(cuenta);}

}

El componente anterior es extremadamente sencillo: en él se inyecta una instancia deEntityManager, la cual es usada en los métodos del Session Bean para realizar las tareasde persistencia. Estos métodos usan como parámetros o tipos de retorno instancias de laclase Cuenta (la cual define cuentas bancarias) y que será nuestra entidad:

package es.davidmarco.ejb.entidad;

Page 58: Manual EJB

import java.io.Serializable;

import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;

@Entitypublic class Cuenta implements Serializable {

private static final long serialVersionUID = 1L;

@Id@GeneratedValueprivate Long id;private String numeroDeCuenta;private String nombreDelTitular;private Double saldo;

public Long getId() {return id;

}

public void setId(Long id) {this.id = id;

}

public String getNumeroDeCuenta() {return numeroDeCuenta;

}

public void setNumeroDeCuenta(String numeroDeCuenta) {this.numeroDeCuenta = numeroDeCuenta;

}

public String getNombreDelTitular() {return nombreDelTitular;

}

public void setNombreDelTitular(String nombreDelTitular) {this.nombreDelTitular = nombreDelTitular;

}

public Double getSaldo() {return saldo;

}

public void setSaldo(Double saldo) {this.saldo = saldo;

}

public void aumentarSaldo(Double cantidad) {saldo += cantidad;

}

public void reducirSaldo(Double cantidad) {saldo -= cantidad;

Page 59: Manual EJB

}}

Algunos detalles de la entidad definida en el ejemplo anterior merecen una pequeñaexplicación: para empezar, nuestra entidad implementa la interface Serializable,necesaría cuando nuestra entidad va a viajar a través de una red (en nuestro caso entre elcliente Java y el contenedor EJB). Dentro de la entidad definimos 4 propiedades: una parala identidad de la entidad, y tres para representar su estado (numeroDeCuenta,nombreDelTitular, y saldo), más sus correspondientes métodos getter/setter. De maneraadicional, hemos añadido algunas operaciones de lógica de negocio (aumentarSaldo() yreducirSaldo) dentro de la entidad; es una buena práctica que las operacionesrelacionadas con cuentas estén dentro de la clase que representa dichas cuentas.

Ahora que tenemos un componente EJB y una entidad, vamos a crear la unidad depersistencía asociada a la fuente de datos y nuestra entidad. Crea un archivo llamadopersistence.xml en el directorio META-INF del proyecto EJB:

<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://java.sun.com/xml/ns/persistence"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/persistence

http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"version="2.0">

<persistence-unit name="introduccionEJB" transaction-type="JTA"><provider>org.hibernate.ejb.HibernatePersistence</provider>

<jta-data-source>java:/DerbyDS</jta-data-source><class>es.davidmarco.ejb.entidad.Cuenta</class>

<properties><property name="hibernate.dialect"

value="org.hibernate.dialect.DerbyDialect" /><property name="hibernate.hbm2ddl.auto" value="update"/>

</properties></persistence-unit>

</persistence>

En el archivo XML anterior hemos declarado una unidad de persistencia con nombreintroduccionEJB (que fue el que usamos como parámetro de la anotación@PersistenceContext en el componente SLSB que definimos previamente), así comotransacciones de tipo JTA (gestionadas por el contenedor; los tipos de transacción seexplicaron en las secciones 3.2 y 3.3 del tercer artículo del tutorial de JPA).

Ya dentro de la declaración de la unidad de persistencia, hemos declarado el proveedor depersistencia que usaremos (HibernatePersistence), la dirección JNDI de la fuente dedatos que declaramos en el archivo derby-ds.xml, y las entidades que gestionaremos en estaunidad de persistencia (en nuestro caso solamente Cuenta). Por último, hemos configurado

Page 60: Manual EJB

algunos detalles relativos a la fuente de datos dentro del elemento <properties>: eldialecto que usará el contenedor para construir sentencias SQL adecuadas a nuestra base dedatos, y la creación automática de los esquemas necesarios en base a los metadatos denuestras entidades (de esta manera nos evitamos crear manualmente tanto las tablas comosus columnas, incluyendo la definición del tipo de dato de cada columna, restricciones decada columna, etc). Ahora ya podemos desplegar la aplicación EJB en el contenedor.

El último paso necesario para probar nuestro ejemplo es crear un cliente Java que pasaráuna instancia ya inicializada de la entidad Cliente al componente EJB. Para ello, y paramantener las cosas sencillas, creamos un proyecto Java en Eclipse y le añadimos laslibrerias del proyecto EJB haciendo click con el botón derecho del ratón en el nombre delproyecto Java y seleccionando:

Build Path > Configure Build Path

En la ventana que nos aparece, vamos a la pestaña Proyects, hacemos click en el botónAdd, seleccionamos el proyecto EJB donde esta nuestro componente SLSB y nuestraentidad, hacemos click en el botón OK, y de nuevo hacemos click en el botón OK. Ahora yapodemos escribir el cliente:

package es.davidmarco.ejb.cliente;

import java.util.Properties;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;import es.davidmarco.ejb.entidad.Cuenta;import es.davidmarco.ejb.slsb.OperacionesConCuentas;

public class Cliente {private static final String JNDI_BEAN =

"OperacionesConCuentasRemote/remote";

public static void main(String[] args) throws NamingException {Properties properties = new Properties();properties.put("java.naming.factory.initial",

"org.jnp.interfaces.NamingContextFactory");properties.put("java.naming.factory.url.pkgs",

"org.jboss.naming:org.jnp.interfaces");properties.put("java.naming.provider.url",

"jnp://localhost:1099");Context context = new InitialContext(properties);

Cuenta cuenta = new Cuenta();cuenta.setNumeroDeCuenta("0000-0001");cuenta.setNombreDelTitular("Nuevo cliente");cuenta.setSaldo(2500D); */

OperacionesConCuentas occ =(OperacionesConCuentas)context.lookup(JNDI_BEAN);

occ.crearCuenta(cuenta);

Page 61: Manual EJB

}}

En el ejemplo anterior, creamos e inicializamos una cuenta, obtenemos un proxy/vista alcomponente EJB, e invocamos su método crearCuenta() pasándole como parámetro lacuenta. Esta entidad será serializada, viajará a traves de la red hasta el contenedor, serádeserializada, y dentro del componente EJB será persistida en la base de datos. Podemoscomprobarlo ejecutando una sentencia SQL contra la base de datos: para ello, haz click conel botón derecho sobre el nombre del proyecto EJB en la pestaña Proyect Explorer yselecciona:

Apache Derby > ij (Interactive SQL)

La consola de ij se abrirá en la pestaña Console de Eclipse, y desde el prompt de ij nosconectamos a la base de datos mediante el comando:

connect 'jdbc:derby://localhost:1527/introduccionEJB;'

Cuando la conexión se realice, volveremos a ver el prompt de ij. Ahora ya podemos realizarla consulta SQL mediante el comando:

select * from cuenta;

La consola de ij nos mostrará un registro en la tabla Cuenta:

1 |Nuevo cliente |0000-0001 |2500.0

Esto demuestra que nuestra entidad (un POJO Java) ha sido almacenado en una base dedatos relacional (tablas y columnas) de manera transparente para nosotros. Misióncumplida. Si deseáramos realizar el proceso inverso (obtener un objeto Java desde lainformación almacenada en la base de datos):

// ...

public class Cliente {private static final String JNDI_BEAN =

"OperacionesConCuentasRemote/remote";

public static void main(String[] args) throws NamingException {Properties properties = new Properties();properties.put("java.naming.factory.initial",

"org.jnp.interfaces.NamingContextFactory");properties.put("java.naming.factory.url.pkgs",

"org.jboss.naming:org.jnp.interfaces");properties.put("java.naming.provider.url",

"jnp://localhost:1099");Context context = new InitialContext(properties);

Page 62: Manual EJB

// Cuenta cuenta = new Cuenta();// cuenta.setNumeroDeCuenta("0000-0001");// cuenta.setNombreDelTitular("Nuevo cliente");// cuenta.setSaldo(2500D); */

OperacionesConCuentas occ =(OperacionesConCuentas)context.lookup(JNDI_BEAN);

// occ.crearCuenta(cuenta);Cuenta cuenta = occ.obtenerCuenta(1L);System.out.println("Titular de la cuenta "

+ cuenta.getNumeroDeCuenta()+ " con saldo "+ cuenta.getSaldo()+ ": "+ cuenta.getNombreDelTitular());

}}

En el ejemplo anterior, hemos comentado las lineas que crean y persisten un cliente (de otramanera se insertará un nuevo registro con los misma información en la base de datos perocon ID con valor 2), y en su lugar hemos llamado al método obtenerCuenta() del SLSB,para así obtener una cuenta desde la base de datos en base a su ID. Si una cuenta con ese IDno existe, obtendremos como respuesta un valor null. Te invito a que, a modo de práctica,elimines de la base de datos la cuenta que hemos creado llamando al métodoborrarCuenta() del SLSB; para verificarlo, una vez hayas ejecutado esta operaciónvuelve a realizar la consulta SQL contra la base de datos a través de la consola de ij:

select * from cuenta;

Si has realizado correctamente el ejercicio, la tabla Cuenta no deberá mostrar ningunaentidad (salvo que hayas insertado entidades adicionales).

4.7 RESUMENComo hemos visto en este artículo, podemos realizar persistencia de maneraextremadamente sencilla en nuestras aplicaciones EJB. Las entidades son simples POJO's,la configuración con la base de datos se configura mediante archivos XML, y en nuestroscomponentes EJB solo tenemos que inyectar una unidad de persistencia y ejecutar susmétodos.

En este punto ya hemos visto todos los componentes EJB (Session Beans, Message-DrivenBeans, y Entities). Los dos próximos artículos estarán dedicados a los diversos serviciosque ofrece el contenedor, servicios que nuestros componentes pueden usar para realizartaréas más complejas (y que son necesarias en aplicaciones reales).