Sunteți pe pagina 1din 62

Introduccin a EJB 3.

1 (I)
Publicado el 08 de Marzo de 2011

Con este artculo comienza un tutorial que describe el estandar EJB 3.1 de manera introductoria. El tutorial contiene tanto material terico como prctico, de manera que segn se vayan introduciendo nuevos conceptos se irn reflejando en cdigo. 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 artculos de los que constar el tutorial. Esta lista se actualizar si se publica contenido adicional: - 1. Introduccin 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 programacin que nos permite construir aplicaciones Java mediante objetos ligeros (como POJO's). Cuando construimos una aplicacin, son muchas las responsabilidades que se deben tener en cuenta, como la seguridad, transaccionalidad, concurrencia, etc. El estandar EJB nos permite centrarnos en el cdigo de la lgica de negocio del problema que deseamos solucionar y deja el resto de responsabilidades al contenedor de aplicaciones donde se ejecutar la aplicacin.

1.2 EL CONTENEDOR DE APLICACIONES Un contenedor de aplicaciones es un entorno (en si mismo no es ms que una aplicacin) que provee los servicios comunes a la aplicacion que deseamos ejecutar, gestionndolos por nosotros. Dichos servicios incluyen la creacin/mantenimiento/destruccin de nuestros objetos de negocio, as como los servicios mencionados en el punto anterior, entre otros. Aunque el contenedor es responsable de la gestin y uso de dichos recursos/servicios, podemos interacturar con l para que nuestra aplicacin haga uso de los servicios que se ofrecen (normalmente mediante metadados, como se ver a lo largo del tutorial). Una vez escrita nuestra aplicacin 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 lgica de negocio se mantiene independiente de

otro cdigo que pueda ser necesario, resultando en cdigo que es ms fcil de escribir y mantener (adems de ahorrarnos mucho trabajo).

1.3 LA ESPECIFICACIN EJB 3.1 La especificacin EJB 3.1 es parte de la plataforma JavaEE 6, desarrollada y mantenida por Sun Microsystems (ahora parte de Oracle Corporation). JavaEE 6 provee diversas API's para la construccin de aplicaciones empresariales, entre ellas EJB, JPA, JMS, y JAX-WS. Cada una de ellas se centra en un area especfica, resolviendo as problemas concretos. Adems, cada API/especificacin est preparada para funcionar en compaia de las dems de forma nativa, y por tanto en su conjunto son una solucin perfectamente vlida para desarrollar una aplicacin end-to-end (de principio a fin). Desde la versin 3.0, EJB no impone ninguna restriccin ni obligacin a nuestros objetos de negocio de implementar una API en concreto. Dicho de otro modo, podemos escribir dichos objetos de negocio usando POJO's, facilitando entre otras cosas la reutilizacin de componentes y la tarea de testearlos. Como se ha dicho, los POJO's son faciles de testear (siempre que estn bien diseados). Al final de este primer artculo se ver un sencillo ejemplo de programacin de un EJB mediante Test-Driven Development (Desarrollo Dirigido por Tests). TDD es una metodologa de desarrollo en la cual cada bloque de cdigo est respaldado por uno o ms tests que han sido escritos con anterioridad. De manera muy resumida, TDD nos permite enfocar 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 an no hay lgica de negocio, o lo que es lo mismo, como queremos hacerlo). - Escribimos la lgica de negocio que hace pasar el test (la solucin ms simple posible). - Mejoramos la lgica de negocio gradualmente, ejecutando el test despus de cada mejora para verificar que no hemos roto nada. Escribir el test antes que la lgica de negocio y mantenerlo lo ms simple posible nos obliga a escribir cdigo independiente de otro cdigo, con responsabilidades bien definidas (en resumen, buen cdigo). Usado de forma correcta, TDD permite crear sistemas que son escalables, y con niveles de bugs muy bajos. TDD es un tema tan amplio en si mismo que no tiene cabida en este tutorial (a excepcin del citado ejemplo que veremos al final del artculo y que servir solamente para demostrar que el modelo EJB es un buen modelo de programacin), y en el que te animo que profundices si no lo conoces; las ventajas que ofrece para escribir cdigo de calidad son muchas, independientemente del uso de EJB o no. Por otro lado, el uso de POJO's para encapsular nuestra lgica de negocio nos proporciona un modelo simple que es altamente reutilizable (recuerda que la reutilizacin de clases es un concepto bsico y esencial en programacin orientada a objetos). Debes tener en cuenta que un POJO no actuar como un componente EJB hasta que haya sido empaquetado,

desplegado en un contenedor EJB y accedido por dicho contenedor (por ejemplo a peticin de un usuario). Una vez que un POJO definido como EJB haya sido desplegado en el contenedor, se convertir en uno de los tres siguientes componentes (dependiendo del como lo hayamos definido): - Session Bean - Message-Driven Bean - Entity Bean Veamos una breve descripcin de cada tipo de componente (en captulos posteriores se explicar cada tipo de componente con ms detalle).

1.4 SESSION BEANS Los Session Beans (Beans de Sesin) son los componentes que contienen la lgica de negocio que requieren los clientes de nuestra aplicacin. Son accedidos a travs de un proxy (tambin llamado vista, trmino que utilizar en adelante) tras realizar una solicitud al contenedor. Tras dicha solicitud, el cliente obtiene una vista del Session Bean, pero no el Session Bean real. Esto permite al contenedor realizar ciertas operaciones sobre el Session Bean real de forma transparente para el cliente (como gestionar su ciclo de vida, solicitar una 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 Sesin sin Estado) son componentes que no requieren mantener un estado entre diferentes invocaciones. Un cliente debe asumir que diferentes solicitudes al contenedor de un mismo SLSB pueden devolver vistas a objetos diferentes. 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 discreccin del contenedor, y puesto que no mantienen estado son muy eficientes a nivel de uso de memoria y recursos en el servidor. Stateful Session Beans (SFSB - Beans de Sesin con Estado), al contrario que SLSB, si que mantienen estado entre distintas invocaciones realizadas por el mismo cliente. Esto permite crear un estado conversacional (como el carrito de la compra en una tienda online, que mantiene los objetos que hemos aadido mientras navegamos por las diferentes pginas), de manera que acciones llevadas a cabo en invocaciones anteriores son tenidas en cuenta en acciones posteriores. Un SFSB es creado justo antes de la primera invocacin de un cliente, mantenido ligado a ese cliente, y destruido cuando el cliente invoque un mtodo en el SFSB que est marcado como finalizador (tambin puede ser destruido por timeout de sesin). Son menos eficientes a nivel de uso de memoria y recursos en el servidor que los SLSB.

Los Singleton son un nuevo tipo de Session Bean introducido en EJB 3.1. Un Singleton es un componente que puede ser compartido por muchos clientes, de manera que una y solo una instancia es creada. A nivel de eficiencia en uso de memoria y recursos son indiscutblemente los mejores, aunque su uso est restringido a resolver ciertos problemas muy especficos.

1.5 MESSAGE-DRIVEN BEANS Message-Driven Beans (MDB - Beans Dirigidos por Mensajes) son componentes de tipo listener que actuan de forma asncrona. Su misin es la de consumir mensajes (por ejemplo eventos que se producen en la aplicacin), los cuales pueden gestionar directamente o enviar (derivar) a otro componente. Los MDB actuan sobre un proveedor de mensajera, por ejemplo Java Messaging System (JMS es adems soportado de forma implcita por la especificacion EJB). Al igual que los Stateless Session Beans, los Message-Driven Beans no mantienen estado entre invocaciones.

1.6 ENTITY BEANS Los Entity Beans (EB - Beans de Entidad) son representaciones de datos almacenados en una base de datos, siempre en forma de POJO's. El encargado de gestionar los EB es EntityManager, un servicio que es suministrado por el contenedor y que est incluido en la especificacin Java Persistence API (JPA - API de Persistencia en Java). JPA es parte de EJB desde la versin 3.0 de esta ltima. Para saber ms sobre JPA, puedes visitar un tutorial anterior publicado en esta misma web en la siguiente direccin. Al contrario que los Session Beans y los Message-Driven Beans, los Entity Beans no son componentes del lado del servidor. En otras palabras, no trabajamos con una vista del componente, si no con el componente real.

1.6 EJB Y TEST-DRIVEN DEVELOPMENT Para terminar este primer artculo, dejemos de lado la teora y escribamos una sencilla aplicacin EJB para ir abriendo el apetito. Para poder seguir este y futuros ejemplos, debes tener en marcha un entorno compatible con EJB 3.1 (aunque la mayora de los ejemplos, incluido este, funcionarn en un contenedor compatible con EJB 3.0). Por otro lado, todo el cdigo se ajusta al estandard EJB 3.1 (no se usarn extensiones exclusivas de un contenedor concreto). Sin embargo, ten presente que las indicaciones relativas a la creacin del proyecto, despliegue, y ejecucin de los ejemplos estarn condicionadas por el entorno concreto que se ha puesto en marcha mediante el anexo que acompaa este tutorial; si tu entorno es diferente, ciertas acciones (como los opciones de mens a ejecutar en tu IDE) sern otras.

Como se ha indicado en el punto 1.3, este ejemplo de desarrollar de manera puntual mediante Test-Driven Development. En los prximos artculos solo se mostrar cdigo EJB, que es al fin y al cabo el tema a tratar en este turorial. As mismo, los pasos para crear un proyecto o como desplegarlo en el contenedor EJB se omitirn en artculos posteriores. Para comenzar inicia Eclipse (si an no lo has hecho) y crea un nuevo proyecto EJB: File > New > EJB Project Dale un nombre al nuevo proyecto y asegrate tanto de seleccionar en el desplegable 'Target runtime' un contenedor compatible con EJB como de seleccionar la versin 3.1 en el desplegable 'EJB module version'. Haz click en el botn 'Finish' para crear el proyecto. Ahora es el momento de crear un test que defina y respalde nuestro primer EJB. Lo primero es crear una carpeta dentro del proyecto donde almacenaremos todos los tests que escribamos. En la pestaa 'Project Explorer' haz click con el botn derecho sobre el proyecto EJB y selecciona: New > Source Folder Introduce el nombre del directorio en el campo 'Folder name' (utiliza un nombre descriptivo, como 'tests') y haz click en el botn 'Finish'. Si expandes el proyecto EJB (con la flecha negra que hay a la izquierda del nombre) vers que el nuevo directorio se ha creado correctamente. Ahora vamos a crear la clase donde escribiremos los tests para nuestro EJB. Haz click con el botn 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 creacin de un test de JUnit selecciona la opcin 'New JUnit 4 test', introduce el nombre del paquete donde deseas alojar la clase en el campo 'Package' (muy recomendado) y escribe un nombre para la clase de tests. En mi caso, el nombre del paquete ser 'es.davidmarco.ejb.slsb' y el nombre de la clase de tests 'PrimerEJBTest'. Haz click en el botn 'Finish'; si es el primer tests que escribes para el proyecto (como es nuestro caso) aparecer una ventana donde Eclipse nos informa que la librera JUnit no est incluida en el classpath. Seleccionamos la opcin 'Perform the following action: Add JUnit4 library to the build path' (Realizar la siguiente accin: aadir la libreria JUnit 4 al path de construccin) y haz click en el botn 'OK'. Hecho esto, ya podemos escribir nuestro primer (y de momento nico) test:
package es.davidmarco.ejb.slsb; import org.junit.Test; import static org.junit.Assert.*;

public class PrimerEJBTest { @Test public void testSaluda() { PrimerEJB ejb = new PrimerEJB(); assertEquals("Hola usuario", ejb.saluda("usuario")); } }

El test (un mtodo que debe ser void, no aceptar parmetros, y estar anotado con la anotacin de JUnit @Test) declara las intenciones (el contrato) del cdigo que estamos diseando: crear un objeto de la clase PrimerEJB con un mtodo saluda() que acepte un argumento de tipo String y devuelva un mensaje con el formato 'Hola argumento'. Aqu empezamos a ver las ventajas de EJB: podemos testear nuestro cdigo de negocio directamente, sin tener que desplegar el componente EJB en un contenedor y entonces hacer 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 un paquete con el mismo nombre que el utilizado para almacenar las clase de tests, pero en una carpeta diferente. De esta manera mantenemos ambos tipos de clases separadas fsicamente en disco (por motivos de organizacin y por claridad), pero accesibles gracias a que virtualmente pertenecen al mismo paquete, y por tanto entre ellos hay visibilidad de tipo package-default (esto puede resultarnos til si necesitamos, por ejemplo, acceder desde la clase de tests a mtodos en las respectivas clases de negocio que han sido declarados como 'protected'). En la pestaa 'Project Explorer' haz click con el botn derecho en la carpeta 'ejbModule' (la carpeta por defecto que crea Eclipse en un proyecto EJB para almacenar nuestras clases) y selecciona: New > Class Introduce en el campo 'Package' el nombre del paquete donde vamos a almacenar la clase de negocio (en mi caso 'es.davidmarco.ejb.slsb') y en el campo 'Name' el nombre de la clase de negocio; para este ltimo caso es conveniente usar el nombre de la clase de tests sin el sufijo 'Test' (en mi caso 'PrimerEJB') de manera que podamos asociar visualmente en el explorador del IDE cada clase de negocio ('Xxx') con su clase de tests ('XxxTest'). Haz click en el botn 'Finish' para crear la clase de negocio y aade la lgica de negocio (comienza con la solucin mas simple posible):
package es.davidmarco.ejb.slsb; public class PrimerEJB { public String saluda(String nombre) { return null; } }

Si en este punto ejecutamos el test que hemos escrito (haciendo click con el botn derecho sobre el editor donde tenemos el cdigo del test y seleccionando 'Run As > JUnit Test') este fallar (vers una barra de color rojo que indica que al menos un tests no ha pasado correctamente). Si observas la ventana 'Failure trace' (seguimiento de fallos) de la pestaa de resultados de JUnit, vers un mensaje que, traducido a espaol, indica que se esperaba como respuesta 'Hola Usuario' pero se recibi 'null'. Debajo de este mensaje puedes ver la pila de llamadas que ha generado el error, en nuestro caso ha sido la funcin esttica AssertEquals de JUnit. Volvamos al editor donde tenemos la clase de negocio y arreglemos el cdigo 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 pestaa de JUnit que la barra es ahora de color verde, lo cual indica que todos los tests se han ejecutado sin fallos (la ventana 'Failure Trace' esta ahora vaca, evidentemente). Ahora decidimos que, cuando un cliente pase un argumento de tipo null a nuestra funcin, esta deber devolver un saludo por defecto. Renombremos el nombre del primer test que hemos escrito y escribamos un segundo test que pruebe esta nueva condicin (desde ahora y hasta el final de esta seccin omitir en ambas clases las sentencias package e import por claridad):
public class PrimerEJBTest { @Test public void testSaludaConNombre() { PrimerEJB ejb = new PrimerEJB(); assertEquals("Hola usuario", ejb.saluda("usuario")); } @Test public void testSaludaConNull() { PrimerEJB ejb = new PrimerEJB(); assertEquals("Hola desconocido", ejb.saluda(null)); } }

Como se dijo previamente, mediante TDD estamos dejando claras las intenciones de nuestro cdigo antes incluso de escribirlo, como se puede ver en el segundo tests. En l, esperamos recibir como respuesta la cadena de texto 'Hola desconocido' cuando invoquemos el mtodo saluda() con un argumento de tipo null. Y mientras tanto, el primer test (que hemos renombrado para darle ms claridad y expresividad a nuestros tests) debe seguir pasando, por supuesto. Ejecutamos los tests ('Run As > JUnit Test') y el nuevo

test que hemos escrito falla (podemos ver en la ventana a la izquierda de la pestaa de JUnit el/los test/s que ha/n fallado marcados con una cruz blanca sobre fondo azul). Volvamos al editor donde estamos escribiendo la lgica de negocio e implementemos la nueva funcionalidad:
public class PrimerEJB { public String saluda(String nombre) { if(nombre == null) { return "Hola desconocido"; } return "Hola usuario"; } }

Ahora ambos tests pasan. Para terminar, que ocurrira si en lugar de la cadena de texto 'usuario' pasamos al mtodo saluda() una cadena de texto distinta?. Aadamos un test que pruebe esta condicin:
public class PrimerEJBTest { @Test public void testSaludaConNombre() { PrimerEJB ejb = new PrimerEJB(); assertEquals("Hola usuario", ejb.saluda("usuario")); } @Test public void testSaludaConOtroNombre() { PrimerEJB ejb = new PrimerEJB(); assertEquals("Hola Pedro", ejb.saluda("Pedro")); } @Test public void testSaludaConNull() { PrimerEJB ejb = new PrimerEJB(); assertEquals("Hola desconocido", ejb.saluda(null)); } }

Este ltimo test demuestra, al ejecutarse y fallar, que nuestra lgica de negocio contiene un bug: siempre que invocamos el mtodo saluda() con un parmetro de tipo String (diferente de null) obtenemos la cadena de texto 'Hola usuario', ignorando as el parametro que le hemos pasado. He aqu otra ventaja ms que surje del uso de TDD: descubrir bugs lo antes posible. Cuanto ms tiempo tardemos en descubrir un bug, ms dificil nos resultar encontrarlo y solucionarlo. Vamos a resolver este ltimo error en nuestra lgica de negocio:
public class PrimerEJB {

public String saluda(String nombre) { if(nombre == null) { return "Hola desconocido"; } return "Hola " + nombre; } }

Ahora todos los tests pasan. Aunque an nos quedara la tarea de refactorizar los tres mtodos de tests (hay cdigo redundante en todos ellos) y tal vez aadir algn test ms (o eliminar...), vamos a dejar las cosas aqu. TDD es un tema demasiado amplio y complejo que est fuera del propsito de este tutorial. Aunque este ejemplo ha sido extremadamente simple/tonto/llamalo-como-quieras, nos ha servido para demostrar lo facil que es disear un componente EJB paso a paso y libre de errores. Nadie quiere software que falle, y por tanto debes tomarte la tarea de testear el cdigo que escribes muy en serio. Test-Driven Development es una manera muy sencilla y divertida de disear software de calidad.

1.7 DESPLEGANDO NUESTRO PRIMER EJB Hasta ahora hemos tratado la clase PrimerEJB como si fuera un componente EJB. Pero lo cierto es que no es as (en otras palabras, te he mentido, aunque espero que puedas perdonarme...). Para que nuestro POJO sea reconocido por nuestro contenedor como un componente EJB verdadero tenemos que decirle que lo es:
package es.davidmarco.ejb.slsb; import javax.ejb.Stateless; @Stateless public class PrimerEJB { // ... }

La anotacin @Stateless define nuestro POJO como un Session Bean de tipo Stateless y una vez desplegado en un contenedor EJB, este lo reconocer como un componente EJB que podremos usar. As de simple!. Sin embargo, debemos aadir una segunda anotacin a nuestro (ahora si) componente EJB:
package es.davidmarco.ejb.slsb; import javax.ejb.Remote; import javax.ejb.Stateless; @Remote @Stateless

public class PrimerEJB { public String saluda(String nombre) { if(nombre == null) { return "Hola desconocido"; } return "Hola " + nombre; } }

La anotacin @Remote permite a nuestro EJB ser invocado remotamente (esto es, desde fuera del contenedor). Si omitimos esta anotacin, el EJB sera considerado como 'Local' (concepto que veremos en el prximo artculo) y solo podra ser invocado por otros componentes ejecutandose dentro del mismo contenedor. Nosotros la hemos incluido pues en la prxima seccin vamos a escribir un cliente Java externo al contenedor que solicitar a este el componente que estamos diseado. De manera adicional, todos los componentes que sean de tipo remoto (como este) deben extender una interface (o de lo contrario se producir 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 sea correcto:
@Remote @Stateless public class PrimerEJB implements MiInterfaceEJB { // ... }

La necesidad de una interface para componentes de tipo remoto, aunque que a priori pueda parecer una restriccin (o una limitacin), es necesaria para que el contenedor pueda construir la vista/proxy que ser enviada a los clientes remotos (externos al contenedor) por motivos que no vienen al caso. Adems, se considera una buena prctica que nuestras clases y mtodos de negocio se construyan sobre interfaces: de esta manera los clientes que usan nuestro cdigo trabajan con la interface, ignorando la implementacin concreta. De esta manera podemos cambiar dicha implementacin en el futuro sin romper el cdigo de nuestros clientes. Ahora ya podemos desplegar nuestra primera aplicacin EJB en el contenedor. En la pestaa 'Project Explorer' haz click con el botn derecho sobre el nombre del proyecto, y

selecciona: Run As > Run on Server Durante el primer despliegue nos aparecer una ventana donde podemos seleccionar el servidor donde deseamos realizar el despliegue (aparecer por defecto el que definimos al crear el proyecto). Seleccionamos la casilla 'Always use this server when running this project' (Usar siempre este servidor cuando se ejecute este proyecto) y hacemos click en el botn 'Finish'. La pestaa 'Console' se volver activa y en ella veremos multitud de informacin 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, y con l nuestra aplicacin EJB. Entre los ltimos mensajes de arranque del servidor puedes ver los siguientes: PrimerEJB/remote - EJB3.x Default Remote Business Interface PrimerEJB/remote-es.davidmarco.ejb.slsb.InterfaceEJB - EJB3.x Remote Business Interface Esas dos lineas nos indican dos referencia JNDI vlidas al componente EJB que hemos desplegado, y las necesitaremos cuando escribamos el cliente para que el contenedor nos devuelva el objeto correcto. Antes de finalizar esta seccin, veamos un ltimo asunto relativo al despliegue. Una vez que ya tenemos desplegada nuestra aplicacin en JBoss, si realizamos un cambio en nuestra lgica de negocio y deseamos volver a desplegar la aplicacin en el contenedor, debemos hacerlo desde la pestaa 'Servers' de Eclipse (y no desde la pestaa 'Project Explorer' como hicimos la primera vez que desplegamos la aplicacin). Para ello, primero abrimos la pestaa 'Servers' si no es visible en el Workbench de Eclipse: Window > Show Views > Servers Si miramos a la recin abierta pestaa 'Servers' veremos la instancia de JBoss asociada a nuestro proyecto, y junto a ella una flecha. Pinchamos en esta flecha para expandir el servidor y veremos nuestro proyecto EJB. Hacemos click con el botn derecho sobre el nombre del proyecto y seleccionamos 'Full Publish'. En unos segundos nuestro proyecto estar re-desplegado (puedes ver el proceso en la pestaa 'Console'). Por otro lado, cuando iniciemos el IDE y queramos acceder a una aplicacin desplegada con anterioridad (por ejemplo desde un cliente como el que vamos a construir en la prxima seccin) debemos iniciar primero el servidor, evidentemente. Para ello, haz click con el botn derecho sobre el nombre del servidor en la pestaa 'Servers' y selecciona 'Start'.

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

contenedor mediante JNDI para obtener el Stateless Session Bean que hemos creado en la seccin 1.6 y desplegado en la seccin 1.7. Con esto veremos como el contenedor gestiona todo el ciclo de vida de una aplicacin EJB as como de sus componentes, mientras los clientes solo tienen que preocuparse de solicitar el componente que necesiten y usarlo. Lo primero es crear un nuevo proyecto Java: File > New > Other Seleccionamos 'Java Project', hacemos click en 'Next', le damos un nombre al proyecto y hacemos click en 'Finish'. En la pestaa 'Project Explorer' expandimos el proyecto pinchando en la flecha que aparece a la izquierda de su nombre y en la carpeta 'src' creamos un nuevo paquete (muy recomendado) haciendo click con el botn derecho y seleccionando: 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 botn derecho sobre el paquete recin creado y seleccionamos: New > Class En la ventana que nos aparecer le damos un nombre a la clase (en mi caso 'Cliente') y marcamos la casilla 'public static void main(String[] args)' para que nos cree un mtodo main() automticamente, y hacemos click en el botn 'Finish'. Antes de mostrar el cdigo del cliente es preciso mencionar que para ejecutarlo se necesitan ciertas librerias que, por motivos de simplicidad, vamos a obtener del primer proyecto (la aplicacin EJB). Para ello, en la pestaa 'Project Explorer' haz click con el botn derecho sobre el nombre del proyecto que hace de cliente y selecciona: Build Path > Configure Build Path En la ventana que se abrir seleccionamos la pestaa 'Projects', hacemos click en el botn 'Add', y marcamos la casilla correspondiente al proyecto donde est la aplicacin EJB que hemos desplegado (y que, repito, contiene todas las librerias que necesita el cliente). Para finalizar, hacemos click en el botn 'OK', y de nuevo hacemos click en el botn 'OK'. El cdigo del cliente es el siguiente:
package es.davidmarco.ejb.cliente; import import import import import java.util.Properties; javax.naming.Context; javax.naming.InitialContext; javax.naming.NamingException; es.davidmarco.ejb.slsb.MiInterfaceEJB;

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 el valor de la referencia JNDI a nuestro componente (recuerda que este valor nos los dio el contenedor cuando despleg la aplicacin, como vimos al final de la seccin 1.7). Dentro del mtodo main() creamos un objeto de propiedades, introducimos los valores necesarios para acceder al contexto del contenedor EJB, y creamos dicho contexto mediante un objeto InitialContext y el objeto de propiedades que acabamos de crear y configurar. A continuacin viene lo realmente interesante: no obtenemos un objeto de tipo InterfaceEJB mediante el constructor new (como haramos en una aplicacin Java normal), si no que se lo solicitamos al contenedor EJB a traves del mtodo lookup() del contexto que acabamos de crear. A este mtodo le hemos pasado el nombre de la referencia JNDI del componente que queremos obtener. Recuerda que cuando solicitamos al contenedor un componente de tipo Session Bean (ya sea Stateless, Stateful, o Singleton) lo que obtenemos no es una instancia del componente EJB (en nuestro caso PrimerEJB), si no una 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 mtodos que definimos en su interface asociada (MiInterfaceEJB). Para ejecutar el cliente nada tan sencillo como hacer click con el botn derecho sobre el editor donde lo tenemos y seleccionar: Run As > Java Application En la pestaa 'Console' aparecera el resultado de la ejecucin del cliente. A modo de experimento puedes cambiar la invocacin al mtodo saluda(), pasarle un valor null en lugar de una cadena de texto, volver a ejecutar el cliente, y ver la respuesta del componente EJB.

1.9 RESUMEN Este primer artculo del tutorial de introduccin a EJB 3.1 ha sido muy fructfero. En el hemos visto de manera superficial los conceptos bsicos de la especificacin EJB, una breve introduccin al contenedor EJB, los tipos de componentes que podemos producir, un sencillo ejemplo de Test-Driven Development + EJB, la forma de desplegar dicho ejemplo en un contenedor compatible con EJB, y como acceder al componente (a travs del contenedor) desde un cliente Java. En el prximo artculo veremos en profundidad dos de los tres tipos de componentes de tipo Session Bean: Stateless Session Beans y Stateful Session Beans. Hasta entonces, felices despliegues!.

Introduccin a EJB 3.1 (II)


Publicado el 18 de Marzo de 2011

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

2.1 METADATOS Como vimos en la primer artculo del tutorial, para declararar nuestros POJO's como verdaderos componentes EJB necesitamos usar metadatos. Estos metadatos pueden ser de dos tipos: - Anotaciones - XML El uso de anotaciones es, a priori, el ms sencillo y expresivo. Esta forma de aplicar informacin a nuestros componentes est disponible en la plataforma JavaEE desde su versin 5, y por tanto disponible tambin en JavaEE 6 (plataforma de la cual forma parte la especificacin EJB 3.1). El nico punto en contra del uso de anotaciones es que, si deseamos cambiar el comportamiento de un componente, debemos recompilar nuestro

cdigo (ya que la anotacin acompaa al cdigo Java). Por motivos de simplicidad, esta ser la forma de metadatos que se utilizar durante todo el tutorial. Puedes consultar la referencia completa (en ingls) de las anotaciones soportadas en la especificacin EJB 3.0 en la siguiente direccin. El uso de XML para aadir metadatos a nuestro cdigo es la forma heredada de versiones anteriores de la especificacin JavaEE. Para ello, debemos incluir un archivo llamado ejbjar.xml dentro del directorio META-INF del archivo jar/ear a desplegar. La ventaja del uso de XML como fuente de metadatos reside en que no es necesario recompilar nuestro cdigo para cambiar el comportamiento de nuestros componentes, con todas las ventajas que esto puede suponer una vez que un proyecto se encuentra en produccin. De manera adicional, al encontrarse los metadatos en un archivo externo, nuestro cdigo Java no contiene ningn tipo de informacin relativa a la especificacin EJB, y por tanto es ms portable. Por contra, adems de tener que mantener dos fuentes de informacin a la vez (el cdigo Java y el archivo XML), el propio archivo XML puede ser dificil de mantener y entender cuando alcanza cierta longitud y complejidad. En este tutorial no se vern ejemplos de metadados en XML por motivos de simplicidad, aunque si deseas ampliar informacin puedes visitar el captulo 19 de la especificacin EJB 3.1, la cual puedes descargar desde la siguiente direccin. Por ltimo, debes tener muy presente que los metadatos en formato XML sobreescriben cualquier comportamiento ya expresado mediante anotaciones, siempre que ambos hagan referencia a un mismo componente. Por ello, una combinacin de ambos tipos de metadatos sera perfectamente legal, aunque salvo honrosas excepciones, poco recomendable (puede inducir a la confusin, y por tanto a cometer errores).

2.2 EL POOL Un pool es, expresado de forma bsica, un almacn de objetos. El contenedor EJB mantiene uno o varios pools donde almacena componentes que estn listos para ser servidos a un cliente que los solicite. De esta manera, el contenedor gestiona la creacin, mantenimiento, y destruccin de componentes en segundo plano y de manera transparente a la aplicacin EJB (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 la seccin 2.5), o remota (mediante JNDI, como vimos en el ejemplo al final del artculo anterior), esta no apunta a ninguna instancia del componente en cuestin. Solamente cuando se invoca un mtodo en dicha referencia, el contenedor extrae una instancia del pool y la asigna a dicha referencia, de manera que la invocacin pueda tener efecto. El comportamiento de multitud de aspectos del pool es configurable, aunque en este tutorial no necesitamos hacerlo en ningn momento. Los detalles concretos del funcionamiento del pool es algo que tampoco necesitamos saber de antemano para seguir el tutorial; cuando se necesite comprender un aspecto concreto de dicho funcionamiento, se explicar en la seccin correspondiente.

2.3 LOCAL VS REMOTO Como se explic muy brevemente en el primer artculo del tutorial, un Session Bean puede ser declarado de tipo local o remoto. Un Session Bean declarado como local estar destinado a servir solicitudes de otros componentes dentro de la misma Java Virtual Machine (JVM - Mquina Virtual Java) donde est desplegado (dicho con otras palabras, dentro del contenedor donde se ejecuta la aplicacin). El contenedor pasar la referencia que apunta al objeto en cuestin al cliente, pues dentro de la misma JVM est referencia es vlida. Por contra, un Session Bean declarado como remoto est destinado a servir peticiones de clientes externos al propio contenedor (como vimos en el ejemplo al final del artculo anterior). En este caso, lo que se pasa es una copia del objeto en cuestin, pues para una JVM externa no tendr ningun sentido una referencia a un objeto que no se encuentre en ella misma. Recuerda que esa copia no es una copia del componente EJB (una instancia del POJO desplegado en el contenedor), si no una vista/proxy que sabe como acceder a travs de una red al objeto real. La especificacin EJB no permite que la interface (en el sentido de contrato) de un Session Bean sea declarada local y remota al mismo tiempo. Por tanto, lo siguiente no es vlido y producir un error al ser desplegado:
@Local @Remote @Stateless public 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 mismo tiempo, lo cual no est permitido. Sin embargo, lo siguiente si que es vlido:
@Local public interface MiBeanLocalInterface { // declaracin de mtodos locales } @Remote public interface MiBeanRemoteInterface { // declaracin de mtodos remotos } @Stateless public class MiBean implements MiBeanLocalInterface, MiBeanRemoteInterface { // implementacin de mtodos locales y remotos }

En el ejemplo anterior, declaramos una interface local y otra remota, y las implementamos en un Session Bean de tipo Stateless. De esta manera, el Session Bean podr servir solicitudes de ambos tipos (cada una a travs de la interface correspondiente). Otra forma de expresar lo mismo sera de la siguiente manera:
public interface MiBeanLocalInterface {} public interface MiBeanRemoteInterface {} @Local(PrimerBeanLocalInterface.class) @Remote(PrimerBeanRemoteInterface.class) @Stateless public class MiBean implements MiBeanRemoteInterface {}

En el ejemplo anterior, hemos eliminado las anotaciones en las interfaces y las hemos aplicado a la clase MiBean. Aadiendo 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 declaracin de la clase (implements MiBeanRemoteInterface), pues como se vio en el artculo anterior, todo Session Bean declarado como remoto necesita implementar una interface de manera obligatoria (dicha interface es necesaria para la creacin del proxy/vista). Por supuesto tambin podemos aprovechar las caractersticas de polimorfismo y herencia en Java para crear nuestros componentes:
public interface MiInterfaceBase { // contrato general para todas las interfaces que hereden de esta } public interface MiInterfaceLocal extends MiInterfaceBase { // contrato concreto para acceso local } public interface InterfaceRemote extends MiInterfaceBase { // contrato concreto para acceso remoto } public abstract class MiBeanBase implements MiInterfaceBase { // implementacin general para todas las clases que hereden de esta } @Stateless @Local(MiInterfaceLocal.class) public class MiBeanLocal extends MiBeanBase implements MiInterfaceLocal { // implementacin concreta para acceso local } @Stateless @Remote(MiInterfaceRemote.class) public class MiBeanRemote extends MiBeanBase implements MiInterfaceRemote

{ // implementacin concreta para acceso remoto }

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

2.4 EJB CONTEXT Y SESSION CONTEXT A veces, necesitamos acceder al contexto de ejecucin del componente que se est usando. EJBContext (Contexto de EJB) es una interface que provee acceso al contexto de ejecucin asociado a cada instancia de un componente EJB. A travs de l podemos acceder, por ejemplo, al servicio de seguridad: imaginemos que invocamos un mtodo en un Session Bean dentro de una aplicacin donde hay restricciones de seguridad, de manera que el Session Bean necesita comprobar si el cliente que lo est invocndo est autorizado o no a hacerlo. A travs del contexto de ejecucin de dicho Session Bean podemos acceder al servicio de seguridad y realizar dicha comprobacin. Otros ejemplos del uso del contexto de ejecucin son el acceso a transacciones, u obtener el Timer Service (Servicio de Reloj, necesario para programar eventos mediante unidades de tiempo, como veremos en un artculo posterior). (Contexto de Sesin) es una interface que implementa EJBContext, aadiendo mtodos que permiten el uso de servicios adicionales. A travs de l podemos, por ejemplo, obtener una referencia al Session Bean actual para poder pasarla a otro Session Bean como argumento de un mtodo; esto es necesario, pues el contenedor no permite pasar el Session Bean actual usando una referencia de tipo this (los detalles de porqu esto es as no son necesarios para entender el material, y por tanto los omito). Podemos acceder al contexto de sesin asociado a la instancia del componente actual mediante inyeccin de dependencia (asunto que se explicar en la seccin 2.5):
SessionContext @Local @Stateless public class MiBean { @Resource private SessionContext contexto; // ... }

Mediante la anotacin @Resource (Recurso) indicamos al contenedor que debe inyectar el contexto de sesin asociado a dicha instancia en la variable contexto (cuando veamos el ciclo de vida de los componentes EJB se ver cmo y cundo se realiza esta operacin).

Otra manera de acceder al contexto de sesin es usando la misma anotacin, pero sobre un mtodo setter que siga el estandar JavaBean (no confundir con Enterprise JavaBean):
@Local @Stateless public class MiBean { private SessionContext contexto; @Resource public void setContexto(SessionContext contexto) { this.contexto = contexto; } // ... }

Puesto que SessionContext extiende EJBContext, podemos acceder a ambas interfaces desde la primera. Es muy importante que tengas presente que ciertos mtodos en EJBContext estn marcados como deprecated, y adems lanzarn una excepcin de tipo RuntimeException si son invocados. Te recomiendo que, por este motivo, consultes la API de EJBContext en la siguiente direccin.

2.5 INYECCIN DE DEPENDENCIAS La inyeccin de dependencias (Dependency Injection) es un proceso por el cual el contenedor puede inyectar en un componente recursos que son necesarios. Un ejemplo de inyeccin de dependencia lo vimos en la seccin anterior, donde usamos la anotacin @Resource para inyectar una instancia de SessionContext en un SLSB. Otro ejemplo de inyeccin de dependencia surje cuando uno de nuestros componentes necesita de otro componente:
@Stateless public class UnComponente { private OtroComponente dependencia; public String metodo() { return dependencia.otroMetodo(); } }

En el ejemplo anterior, el Session Bean UnComponente requiere otro Session Bean de tipo OtroComponente. Puesto que, como ya se ha explicado, el contenedor EJB gestiona el ciclo de vida de todos los componentes (toda la aplicacin EJB funciona en un entorno gestionado), una instanciacin del tipo new OtroComponente() sera incorrecta (esta instancia no tendra, por ejemplo, un contexto de sesin asociado, pues ha sido inicializada

manualmente). La solucin consiste en informar al contenedor de dicha dependencia mediante la anotacin @EJB, dejando as en sus manos esta tarea:
@Local @Stateless(name="otroComponente") public class OtroComponente { public String otroMetodo() { // ... } } @Stateless public class UnComponente { @EJB(beanName="otroComponente") private OtroComponente dependencia; public String metodo() { return dependencia.otroMetodo(); } }

Gracias a la anotacin @EJB el contenedor sabe que componente instanciar, inicializar, y finalmente inyectar en la variable dependencia. En el ejemplo anterior, se registra un Session Bean con nombre otroComponente (gracias a @Stateless(name="otroComponente")), el cual puede ser accedido mediante inyeccin de dependencia gracias a @EJB(beanName="otroComponente"). La inyeccin de dependencias es una poderosa carecterstica de EJB que libera al programador de muchas responsabilidades, ademas de resultar en cdigo poco acoplado y apto para unit testing (entre otras buenas cualidades). Puedes encontrar ms informacin sobre la anotacin @EJB en la aqu y sobre la anotacin @Resource aqu (ambas pginas en ingls).

2.6 COMPONENTES DEL LADO DEL SERVIDOR La primera pregunta que nos debemos hacer es: Que es un componente del lado del servidor?. No es ni ms ni menos que un componente que es gestionado de manera integra por el contenedor. Esto es, el cliente del componente no interacciona con el componente en si, si no con una representacin (referencia/proxy/vista) del componente real. Los componentes del lado del servidor son dos: - Todos los Session Bean (Stateless, Stateful, y Singleton) - Message-Driven Beans En este artculo veremos los dos primeros tipos de Session Beans, y dejaremos el componente Singleton y los Message-Driven Beans para el prximo artculo. Ahora otra buena pregunta sera: Que NO es un componente del lado del servidor? La respuesta es:

Entity Beans (EB - entidades). Los EB viajan entre el cliente y el servidor siendo siempre la representacin del mismo objeto Java en ambos lados. Todo lo relacionado con EB se tratar en el captulo relacionado con persistencia (si ests interesado en este tema puedes visitar un tutorial sobre Java Persistent API (JPA - API de Persistencia en Java) publicado en esta misma web en la siguiente direccin).

2.7 STATELESS SESSION BEANS: CONCEPTOS BSICOS Como vimos brevemente en el primer artculo del tutorial, los Stateless Session Bean (SLSB - Session Bean Sin Estado) son aquellos que no mantienen estado entre diferentes invocaciones de sus mtodos. Podriamos considerar cada mtodo dentro de un SLSB como un servicio en si mismo, que realiza una tarea y devuelve un resultado (puede no hacerlo) sin depender de invocaciones anteriores o posteriores sobre l mismo o sobre otro mtodo. Debido a este comportamiento, el contenedor puede utilizar un nmero relativamente bajo de SLSB's para servir llamadas de muchos clientes (ya que ninguno de estos clientes estar asociado a un SLSB en concreto), y por ello son muy eficientes. El hecho de no mantener un estado entre invocaciones los hace an ms eficientes. Es importante destacar que un SLSB si puede mantener un estado interno, aunque este no debe escapar nunca hacia el cliente. Un ejemplo de este estado interno sera una variable que almacenara el nmero de veces que una instancia en concreto ha sido invocada: un cliente que dependa del valor de esta variable podra obtener un resultado distinto en cada llamada al SLSB, ya que el contenedor no garantiza devolver la misma instancia al mismo cliente. La regla es: si un SLSB mantiene estado, este debe ser interno (invisible para el cliente).

2.8 STATELESS SESSION BEANS: EL CICLO DE VIDA El ciclo de vida de un componente describe los distintos estados (gestionados ntegramente por el contenedor) por los que puede pasar cada instancia del componente desde que es creada hasta que es destruida. Para los SLSB, este ciclo de vida es muy simple y consta nicamente de dos estados (incluyo el trmino original en ingls): - 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 an. El segundo estado, Preparado en pool, representa una instancia del SLSB que ha sido instanciada y construida por el contenedor, y se encuentra en el pool lista para recibir invocaciones por parte de un cliente (cuando esta invocacin sucede, la instancia es extraida el pool y asociada a una referencia que es pasada al cliente). A este estado se llega durante el arranque del contenedor (el pool es poblado con cierto nmero de instancias), y cuando no existan suficientes instancias en el pool para servir llamadas de clientes (y por tanto se deban crear ms). En resumen, siempre que una nueva instancia del SLSB sea creada.

Durante la transicin entre el primer estado y el segundo, el contenedor realizar tres operaciones (en este orden): - Instanciacin del SLSB - Inyeccin de cualquier recurso necesario y de dependencias - Ejecucin de un mtodo dentro del SLSB marcado con la anotacin @PostConstruct, si existe La instanciacin del SLSB se lleva a cabo mediante reflexin, a traves de Class.newInstance(). Por tanto, el SLSB debe tener un constructor por defecto (sin argumentos), ya sea de forma implcita o explcita. La inyeccin de recursos y de dependencias se vio en las secciones 2.4 y 2.5, donde se utilizaron las anotaciones @Resource y @EJB para tales efectos. Estos y otros recursos son inyectados en el SLSB de forma automtica por el contenedor durante esta operacin. De manera adicional, cada vez que un SLSB sea invocado por un nuevo cliente, todos los recursos son inyectados de nuevo. Por ltimo, el contenedor ejecutar un mtodo dentro del SLSB que est anotado con @PostConstruct (Post construccin), si existe. Esta anotacin declara dicho mtodo (que puede ser uno y solo uno) como un mtodo callback, esto es, un mtodo que reacciona ante cierto evento (en este caso, a la construccin de la instancia, como su propio nombre indica). Dentro de este mtodo tenemos la oportunidad de adquirir recursos adicionales necesarios para el SLSB, como una conexin de red o de base de datos. Este mtodo debe ser void, no aceptar parmetros, y no lanzar ninguna excepcin de tipo checked. Al contrario que la inyeccin de recursos, la ejecucin del mtodo @PostConstruct se ejecuta una y solo una vez (al final de la instanciacin del componente). Cuando el contenedor no necesita una instancia de SLSB, ya sea porque decide reducir el nmero de instancias en el pool, o porque se est produciendo un shutdown del servidor, se realiza una transicin en sentido inverso: del estado preparado en pool al estado no existe. Durante esta transicin se ejecutar, si existe, un mtodo anotado con @PreDestroy (Pre destruccin), el cual es tambin un mtodo callback, y el cual nos da la oportunidad de liberar cualquier recurso adquirido durante la construccin de la instancia (adquisicin que hicimos en @PostConstruct). Al igual que ocurra con @PostConstruct, un mtodo anotado con @PreDestroy es opcional, debe ser void, no aceptar parmetros, no lanzar ninguna excepcin de tipo checked, solo puede ser usado en un nico mtodo, y es ejecutado una y solo una vez (durante la destruccin de la instancia).

2.9 STATELESS SESSION BEANS: UN SENCILLO EJEMPLO Supongamos que estamos desarrollando una aplicacin de banca, y entre otras cosas necesitamos escribir cdigo que represente los conceptos de ingreso, retirada, y transferencia de efectivo. Estos tres conceptos son operaciones que no requieren mantener

un estado dentro de la aplicacin (esto es, una vez invocadas y terminadas no dependen de otras operaciones) y por tanto son excelentes candidatas para ser representadas mediante un Session Bean de tipo Stateless. Como ya hemos visto en multitud de ejemplos, para declarar un Session Bean de tipo Stateless utilizamos la anotacin @Stateless (se omiten otras anotaciones como @Remote o @Local, as como todo cdigo no estrictamente necesario para explicar el tema tratado; desde este momento se dar por hecho esto en la mayora de los ejemplos que veamos):
package es.davidmarco.ejb.slsb; import javax.ejb.Stateless; import es.davidmarco.ejb.modelo.Cuenta; @Stateless public class OperacionesConEfectivo { public void ingresarEfectivo(Cuenta cuenta, double cantidad) { // ingresar cantidad en cuenta } public void retirarEfectivo(Cuenta cuenta, double cantidad) { //retirar cantidad de cuenta } public void transferirEfectivo(Cuenta cuentaOrigen, Cuenta cuentaDestino, double cantidad) { // transferir cantidad de cuenta origen a cuenta destino } }

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

2.10 STATEFUL SESSION BEANS: CONCEPTOS BSICOS Al contrario que los Session Bean de tipo Stateless, los Stateful Session Bean (SFSB Session Bean con Estado) mantienen estado entre distintas invocaciones de un mismo cliente. Podramos considerar un SFSB como una extensin del cliente en el contenedor, ya que cada SFSB est dedicado de manera exclusiva a un nico cliente durante todo su ciclo de vida. Otra diferencia entre SLSB y SFSB es que estos ltimos no son almacenados en un pool, pues no son reusados, como veremos en la seccin 2.11 cuando expliquemos su ciclo de vida. A que nos referimos cuando decimos que un SFSB mantiene un estado? La cuestin es simple: un SFSB almacena informacin a consecuencia de las operaciones que realiza en l

su cliente asociado, y dicha informacin (estado) debe estar disponible en invocaciones posteriores. De esta manera, un SFSB puede realizar una accin compleja mediante multiples invocaciones. Un ejemplo muy comn es el carrito de la compra de una tienda online; aadimos y eliminamos artculos del carrito en diferentes invocaciones, lo cual sera imposible de realizar con un SLSB. A pesar de mantener estado, un SFSB no es persistente, de manera que dicho estado se pierde cuando la sesin del cliente asociado termina. Su misin es servir como lgica de negocio (misin de todos los Session Bean) y nada ms. Para persistir dicha informacin deberemos usar otro tipo de componente EJB (Entity Bean) el cual veremos en el artculo sobre persistencia.

2.11 STATEFUL SESSION BEANS: EL CICLO DE VIDA El ciclo de vida de un SFSB es ligeramente ms complejo que el de su hermano pequeo SLSB, y consta de tres estados: - No existe (Does not exists) - Preparado (Method-ready) - Pasivo (Passive) El primer estado, no existe, es similar al de un SLSB: la instancia del SFSB no ha sido creada an. El segundo estado, preparado, representa una instancia del SFSB que ha sido construida e inicializada, y est lista para servir llamadas de su cliente asociado (recuerda que esta asociacin perdura durante toda la vida del SFSB). El tercer estado, pasivo, representa un SFSB que, despus de un periodo de inactividad, es persistido temporalmente para liberar recursos del servidor. Durante la transicin entre el primer estado y el segundo, el contenedor realizar las siguientes tres operaciones: - Instanciacin del SFSB - Inyeccin de cualquier recurso necesario y de dependencias, y asociacin con el cliente - Ejecucin de un mtodo dentro del SFSB marcado con la anotacin @PostConstruct, si existe La instanciacin del SFSB se lleva a cabo mediante reflexin, a traves de Class.newInstance(). Por tanto, el SFSB debe tener un constructor por defecto (sin argumentos), ya sea de forma implcita o explcita. La inyeccin de recursos y de dependencias se vio en las secciones 2.4 y 2.5, donde se utilizaron las anotaciones @Resource y @EJB para tales efectos. Estos y otros recursos son inyectados en el SFSB de forma automtica por el contenedor durante esta operacin. Una vez finaliza la inyeccin de recursos y dependencias, el SFSB es asociado al cliente que ha generado su creacin.

Por ltimo, el contenedor ejecutar un mtodo dentro del SFSB que est anotado con @PostConstruct (Post construccin), si existe. Esta anotacin declara dicho mtodo (que puede ser uno y solo uno) como un mtodo callback, esto es, un mtodo que reacciona ante cierto evento (en este caso, a la construccin de la instancia, como su propio nombre indica). Al igual que con un SLSB, la funcin de este mtodo es adquirir recursos adicionales que sean necesarios para el SFSB, como una conexin de red o de base de datos. Y tambin al igual que su homnimo en un SLSB, este mtodo debe ser void, no aceptar parmetros, no lanzar ninguna excepcin de tipo checked, y no ser static ni final. La ejecucin del mtodo @PostConstruct se ejecuta una y solo una vez, al final de la instanciacin del componente. En este momento, el SFSB ya se encuentra en el estado preparado, y procede a servir la llamada del cliente que ha generado la creacin de la instancia. Por ltimo, un SFSB en estado preparado puede realizar una transicin a cualquiera de los otros dos estados de su ciclo de vida: no existe o pasivo. La transicin al estado no existe ocurre cuando: - El cliente ejecuta un mtodo dentro del SFSB anotado con @Remove - El SFSB sobrepasa un periodo de inactividad establecido (timeout) En el primer caso, la ejecucin de un mtodo anotado con @Remove informa al contenedor que el cliente ha terminado de usar su SFSB asociado y, por tanto, dicha instancia ya no es necesaria. En este momento la instancia es desasociada de su contexto de sesin y eliminada. Este mtodo no es de tipo callback (como @PostConstruct o @PreDestroy), pues no reacciona a un evento, si no que es ejecutado explicitamente por el cliente. En el segundo caso (timeout), el SFSB ha sobrepasado un periodo de tiempo establecido y el contenedor decide eliminarlo, previa ejecucin de un mtodo (uno y solo uno) anotado con @PreDestroy, y con iguales reglas que en su homnimo en SLSB: opcional, void, sin parmetros, sin excepciones de tipo checked, no static, y no final. Por otro lado, la transicin al estado pasivo se produce cuando el contenedor decide liberar recursos, persistiendo de manera temporal el estado del SFSB. La pasivacin y posterior activacin de un SFSB requiere una seccin exclusiva que veremos enseguida. Por finalizar con el ciclo de vida de un componente SFSB, ten presente que si este lanza una excepcin de sistema (cualquiera de tipo unchecked cuya definicin no est anotada con @ApplicationException), se producir una transicin al estado no existe sin llamar a su mtodo @PreDestroy. Este comportamiento no est muy bien comprendido entre cierta parte de la comunidad, pues a todas luces no parece muy correcto, pero es as y debes tenerlo en cuenta.

2.12 STATEFUL SESSION BEANS: PASIVACIN Y ACTIVACIN Durante la vida de un SFSB, el contenedor puede decidir liberar recursos persistiendo de manera temporal el estado de dicho SFSB, liberando de esta manera memoria del sistema

(u otros recursos). A esto lo llamamos pasivacin. Si durante la pasivacin el cliente realiza una llamada al SFSB, el contenedor recrear en memoria la instancia persistida y procedera con dicha llamada; a esto lo llamamos activacin. Todo el ciclo de activacin-pasivacin puede ocurrir mltiples veces en la vida de un SFSB, o ninguna en absoluto. Sea como sea, es un proceso gestionado por el contenedor y totalmente transparence al cliente, el cual no sabe en ningn momento si se ha producido una pasivacin o activacin del SFSB asociado a su sesin. No toda la informacin (estado) almacenada en un SFSB puede ser persistida. Los siguientes tipos son pasivados por defecto: - Todos los tipos primitivos - Objetos que implementan la interface Serializable - Referencias a factorias de recursos gestionados por el contenedor (como javax.sql.DataSource) - Referencias a otros componentes EJB - javax.ejb.SessionContext - javax.jta.UserTransaction - javax.naming.Context - javax.persistence.EntityManager - javax.persistence.EntityManagerFactory Para todos los dems tipos, la especificacin EJB nos proporciona dos mtodos callback para controlar la pasivacin-activacin de un SFSB cuando existen datos que no deben/pueden ser persistidos: @PrePassivate (Pre-pasivacin) y @PostActivate (Postactivacin). Ambas anotaciones son aplicadas en mtodos dedicados a controlar cada uno de los procesos, segn corresponda (en la seccin 2.13 veremos un ejemplo de un SFSB con mtodos de pasivacin y activacin). nos permite anotar un mtodo (opcional) que deje el SFSB es un estado adecuado para su pasivacin. Puesto que la pasivacin se lleva a cabo mediante serializacin, todas las referencias a objetos no serializables deben ser puestas a null. Otra operacion llevada a cabo mediante @PrePassivate es la liberacin de recursos que no pertenezcan a la lista anterior. Una vez ejecutado el mtodo @PrePassivate, el SFSB es serializado y persistido temporalmente (tal vez en disco, en una cache, etc; esto es decisin de cada implementacin de EJB). Es importante tener en cuenta que si un SFSB en estado pasivo sobrepasa un periodo de inactividad establecido (timeout), el contenedor eliminar la instancia persistida sin ejecutar su mtodo @PreDestroy.
@PrePassivate

Si durante la pasivacin el cliente asociado realiza una llamada al SFSB, el contenedor realizar la activacin del componente, deserializando la instancia desde el lugar donde se encuentra persistida, restaurndola al estado anterior a producirse la pasivacin (inyeccin del contexto de sesin, inyeccin de referencias a otros componentes EJB, etc), y ejecutando un mtodo anotado con @PostActivate (tambin opcional). En este mtodo podemos inicializar todos los objetos no serializables a unos valores adecuados (no debes confiar en los valores por defecto que el contenedor da a objetos no serializables, pues entre

implementaciones pueden ser diferentes), as como restaurar cualquier recurso necesario para el correcto funcionamiento del SFSB. Una vez que la activacin ha concluido, el SFSB puede gestionar la llamada del cliente que gener dicha activacin. Tanto @PrePassivate como @PostActivate deben seguir las siguientes reglas: - El tipo de retorno debe ser void - No puede aceptar ningn parmetro - No puede lanzar ninguna excepcin de tipo checked - No puede ser static ni final Estas reglas son las que se han aplicado a todos los mtodos callback vistos hasta ahora, por lo que una vez aprendidas podrs aplicarlas fcilmente cuando escribas cualquier mtodo callback.

2.13 STATEFUL SESSION BEANS: UN EJEMPLO SENCILLO Supongamos que estamos diseando una aplicacin de tienda online, con un carrito de la compra donde los clientes pueden aadir artculos, eliminarlos, vaciar el carrito, o realizar un pedido. Puesto que cada una de estas operaciones se lleva a cabo de manera independiente a las demas, pero los cambios de cada una de ellas pueden afectar al resto, necesitamos mantener un estado entre distintas invocaciones, y por tanto un componente Session Bean de tipo Stateful. Para declarar un SFSB usamos la anotacin @Stateful y hacemos que la clase en cuestin implemente la interface Serializable (puesto que es este el mecanismo que utilizar el contenedor para la pasivacin-activacin). El ejemplo, como siempre, es una estructura bsica sin apenas implementacin: el objetivo del ejemplo es nicamente mostrar donde encaja cada pieza:
package es.davidmarco.ejb.sfsb; import import import import import import import import import import import java.io.Serializable; java.util.HashMap; java.util.Map; javax.annotation.PostConstruct; javax.annotation.PreDestroy; javax.ejb.PostActivate; javax.ejb.PrePassivate; javax.ejb.Remove; javax.ejb.Stateful; es.davidmarco.ejb.modelo.Articulo; es.davidmarco.ejb.util.BaseDeDatos;

@Stateful public class Carrito implements Serializable { private Map articulosEnCarrito = new HashMap(); private BaseDeDatos bbdd; public void aadirArticulo(Articulo articulo, int cantidad) {

// aadir la cantidad de cierto artculo al carrito } public void eliminarArticulo(Articulo articulo, int cantidad) { // eliminar la cantidad de cierto artculo del carrito } public void vaciarCarrito() { // vaciar el carrito } @Remove public void finalizarCompra() { // procesar el pedido } @PostConstruct @PostActivate private void inicializar() { // obtener conexin con la base de datos } @PrePassivate @PreDestroy private void detener() { // liberar conexin con la base de datos } }

Como puedes ver, la clase Carrito est anotada con @Stateful y marcada como Serializable. Dentro de ella tenemos dos campos: artculosEnCarrito, donde se almacena el estado requerido para el correcto funcionamiento del proceso de compra (HashMap es de tipo Serializable y por tanto es persistida de forma automtica en caso de pasivacin), y BaseDeDatos, que es una clase ficticia que supuestamente nos permite realizar operaciones sobre una base de datos, pero no puede ser pasivada (supongamos que mantiene una conexin con una base de datos real que no puede permanecer abierta durante la pasivacin). Dentro de la clase, los cuatro primeros mtodos nos permiten realizar operaciones de lgica de negocio, como aadir cierta cantidad de cierto artculo al carrito, vaciar el carrito completamente, o procesar el pedido una vez aadidos los artculos deseados. El cuarto mtodo (finalizarCompra), de manera adicional, indica al contenedor que hemos terminado de trabajar con el componente SFSB y que por tanto puede eliminarlo (gracias a la anotacin @Remove). Estos cuatro mtodos son la interface que mostramos al cliente, y por tanto todos ellos son declarados public. Los dos ltimos mtodos (inicializar y detener) se encargar de obtener y liberar recursos en momentos clave del ciclo de vida del SFSB. Fjate como @PostConstruct y @PostActivate anotan el mismo mtodo, pues todas las operaciones que hay en l son comunes a los procesos de construccin del SFSB y una posible activacin posterior. De

igual manera, @PrePassivate y @PreDestroy acompaan al mismo mtodo, por el mismo motivo. Ambos mtodos, de tipo callback, son gestionados por el contenedor en base a eventos, y por tanto el cliente del SFSB no necesita saber de su existencia (nosotros los hemos declarado private, aunque cualquier nivel de visibilidad est permitido).

2.14 RESUMEN En este segundo artculo del tutorial de EJB hemos visto conceptos relacionados con la tecnologa EJB, como la definicin del pool, local vs remoto, metadatos, inyeccin de dependencias, y contexto de sesin. Ademas, hemos visto en cierta profundidad los dos tipos de Session Bean ms comunes: Stateless y Stateful. En el prximo artculo veremos el tercer y ltimo tipo de Session Bean (Singleton) as como un nuevo tipo de componente: Message-Driven Beans. Hasta entonces, feliz inyeccin de dependencias!

Introduccin a EJB 3.1 (III)


Publicado el 28 de Marzo de 2011

En el artculo anterior vimos algunos conceptos comunes a los componentes de una aplicacin EJB, como el contexto de sesin, o la diferencia entre remoto y local. Adems, vimos en cierta profundidad los dos tipos de Session Bean ms comunes en una aplicacin EJB: Stateless y Stateful. En este artculo vamos a ver el ltimo tipo de Session Bean (Singleton Session Bean), dos conceptos aplicables a todos los Session Bean y que son nuevos en la especificacin EJB 3.1 (vista sin interface y llamadas asncronas), y finalmente un nuevo tipo de componente: Message-Driven Bean. Comencemos.

3.1 SINGLETON SESSION BEANS: CONCEPTOS BSICOS El Singleton Session Bean (no existe una traduccin literal de la palabra Singleton, pero viene a expresar el concepto de instancia nica; desde ahora nos referiremos a este componente como Singleton Session Bean, o Singleton a secas) es un nuevo tipo de Session Bean introducido en la especificacin EJB 3.1. Este componente se basa en el patrn de diseo del mismo nombre definido por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides en su libro Design Patterns: Elements of Reusable Object Oriented Software (Patrones de Diseo: Elementos Reusables de Software Orientado a Objetos; una lectura imprescindible). Este patrn de diseo garantiza que de una clase dada solamente pueda crearse una instancia, con un punto de acceso global para acceder a dicha instancia. La naturaleza nica de un componente Singleton conlleva un alto rendimiento dentro del

contenedor para este tipo de Session Bean. Es importante tener en cuenta la sutil diferencia entre un componente de tipo Singleton y cualquiera de los otros dos tipos de Session Bean (SLSB y SFSB): cuando un cliente hace una llamada a un mtodo de un SLSB o SFSB, puesto que la instancia del Session Bean est asociada a ese cliente en concreto (durante la invocacin del metodo para un SLSB, y durante toda la duracin de la sesin para un SFSB), un nico thread (hilo de ejecucin) es capaz de acceder a dicho mtodo, y por tanto el componente es seguro en terminos de multi-threading (ejecucin de mltiples hilos). Sin embargo, cuando trabajamos con un Singleton, multiples llamadas en paralelo pueden estar produciendose en un momento dado a su nica instancia, y por tanto el componente debe garantizar que un hilo de ejecucin no est interfiriendo con otro hilo de ejecucin, produciendo resultados incorrectos. Dicho de otra manera, un componente Singleton debe ser concurrente;

3.2 SINGLETON SESSION BEANS: CONCURRENCIA BSICA Debido a la naturaleza de los Session Bean de tipo Singleton, tenemos que tener muy claros ciertos aspectos al disear este tipo de componente. Veamos un ejemplo de una clase donde no tenemos en cuenta la cuestin de concurrencia:
class ClaseNoConcurrente { private int numeroDeInvocacionesDeEstaInstancia = 0; public int metodoUno() { // lgica de negocio return numeroDeEjecuciones++; } public int metodoDos() { // lgica de negocio return numeroDeEjecuciones++; } }

En la clase del ejemplo anterior, no se ha tenido en cuenta el aspecto de concurrencia (lo cual, dependiendo de las especificaciones del problema de negocio que queremos resolver, puede ser perfectamente legal). Una consecuencia de esto es que, cuando intervienen mltiples hilos de ejecucin en paralelo, una llamada al cualquiera de los mtodos metodoXxx puede devolver un resultado incorrecto. Veamos una posibilidad (entre muchas permitidas por la JVM) de la ejecucin 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 nmero de ejecuciones 3. 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 nmero de ejecuciones

7. El cliente C-2 obtiene el valor 2 para el nmero de ejecuciones Que ha ocurrido en el ejemplo anterior? El cliente C-2 cree que los mtodos metodoXxx han 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 operacin numeroDeEjecuciones++ no es atmica: cuando el cdigo Java es convertido en cdigo de bytes (.class), dicha operacin se compone de muchas otras, de manera que la JVM puede para el hilo actual en mitad del incremento y dar tiempo de ejecucin a otro hilo. Incluso aunque la operacin fuera atmica, la JVM podra seguir deteniendo un hilo durante la lgica de negocio (antes de realizar el incremento), dando tiempo de ejecucin a otro hilo y produciendo de nuevo resultados incorrectos. Puesto que un Singleton es compartido por muchos clientes (accedido por muchos hilos de ejecucin) este comportamiento no es aceptable: el Singleton debe ser concurrente. La especificacin EJB 3.1 nos permite controlar la concurrencia de un Singleton de dos maneras: - 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 CONCURRENCY Mediante CMC, el contenedor es responsable de gestionar toda la concurrencia en el Singleton mediante metadatos, liberando as al programador de la tarea de escribir cdigo concurrente. Por defecto, todos los componentes Singleton son gestionados por el contenedor, aunque podemos especificarlo de manera explicita mediante la anotacin @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 anotacin @Singleton, y hemos indicado al contenedor de forma explcita que gestione por nosotros la concurrencia del componente mediante CMC (aunque, repito, este comportamiento es el ofrecido por defecto para todos los Singleton). Cuando usamos CMC, todos los mtodos de la clase tienen por defecto un bloqueo de tipo write (escritura). Este bloqueo es de utilidad

cuando uno o varios mtodos deben ser accedidos de manera secuencial (hasta que uno no termine, otro no puede comenzar) para evitar que se obtengan resultados incorrectos. Este acceso secuencial es necesario en casos como el del ejemplo de la seccin 3.2, en el que varios mtodos escriban sobre el valor de una variable que despus era devuelta al cliente. Como se puede deducir del comportamiento del bloqueo write, este afecta a todo el objeto, y no a mtodos concretos (todos los mtodos write del mismo Singleton deben compartir un nico bloqueo). Por otro lado, cuando un mtodo realiza solo operaciones de tipo read (lectura), de manera que no se altera el estado del componente, podemos indicarle al contenedor que no bloquee ninguna invocacin a dicho mtodo. De esta manera, el mtodo pueda ser accedido por mltiples hilos de ejecucin simultaneamente (puesto que no modificamos el estado del componente, el acceso en paralelo es seguro). Tenemos que indicar que un mtodo es de tipo read mediante la anotacin @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, podemos expresarlo de forma explcita mediante @Lock(LockType.WRITE). Otra opcin que nos brinda CMC es la liberacin de un bloqueo automticamente si este no ocurre tras un tiempo 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 CONCURRENCY En ocasiones, la concurrencia gestionada por el contenedor no es suficiente (tal vez no nos permite definir con suficiente detalle la manera en la que necesitamos que funcione la concurrencia de nuestra aplicacin, por ejemplo). Bean-Managed Concurrency (BMC -

Concurrencia Gestionada por el Bean) deja en manos del programador toda la gestin de la concurrencia. Esto puede realizarse utilizando bloques synchronized, variables atmicas como java.util.concurrent.atomic.AtomicInteger, etc. Podemos indicar que la concurrencia de un componente Singleton ser gestionada ntegramente por el programador mediante la anotacin @ConcurrencyManagement (la cual usamos en la seccin anterior para declarar explcitamente el comportamiento contrario, CMC):
import javax.ejb.ConcurrencyManagement; import javax.ejb.ConcurrencyManagementType; import javax.ejb.Singleton; @Singleton @ConcurrencyManagement(ConcurrencyManagementType.BEAN) class OtraClaseConcurrente { // Gestin explcita de la concurrencia }

La concurrencia en Java es un tema largo y complejo que no puede ser tratado en este tutorial. Un buen lugar para empezar (en ingls) es The Java Tutorials. Estoy seguro que existen otras buenas fuentes de informacin en espaol, y si ests interesado tu buscador favorito te llevar hasta ellas.

3.5 SINGLETON SESSION BEANS: EL CICLO DE VIDA El ciclo de vida de un Singleton muy parecido al de un SLSB, pues como en este ltimo se compone 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 el despliegue de la aplicacin o tras la primera invocacin al componente), y en su duracin (a lo largo de toda la vida de la aplicacin, esto es, hasta que la aplicacin sea replegada o el servidor sea parado). Evidentemente, solo el primer comportamiento (el momento de creacin) puede ser customizado, y para ello la especificacin EJB nos ofrece, como siempre, metadatos. Aunque el contenedor puede decidir inicializar la nica instancia de un Singleton en el momento que considere ms oportuno, podemos forzar que dicho proceso ocurra durante el despliegue mediante la anotacin @Startup:
import javax.ejb.Singleton; import javax.ejb.Startup; @Singleton @Startup public class MiSingleton { // ...

El cdigo anterior fuerza al contenedor a crear la nica instancia de MiSingleton en el momento del despliegue de la aplicacin EJB de la que forma parte. Esto comportamiento es llamado eager initialization (inicializacin temprana). Ahora supongamos que un Singleton debe ser inicializado antes que otro Singleton (tal vez este ltimo necesite poner la aplicacin en cierto estado necesario para el correcto funcionamiento del primero): podemos indicar esta dependencia en el orden de inicializacin mediante la anotacin @DependsOn:
@Singleton public class MiSingletonA { // ... } @Singleton @DependsOn("MiSingletonA") public class MiSingletonB { // ... }

En el ejemplo anterior, indicamos al contenedor que en el momento de inicializar la nica instancia de MiSingletonB, MiSingletonA debe haber sido inicializado. De esta manera, el contenedor forzar la inicializacin del Singleton dependiente si an no se ha producido. Este comportamiento es totalmente ajeno al uso u omisin de @Startup: si un Singleton depende 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 que con los componentes SLSB y SFSB, disponemos de las anotaciones @PostConstruct y @PreDestroy para responder a los eventos de creacin y destruccin (respectivamente) del componente:
@Singleton public class MiSingletonA { @PostConstruct public void inicializar() { // Cualquier operacin/es necesaria/s para la creacin del componente, // como obtencin de recursos } @PreDestroy public void detener() { // Cualquier operacin/es necesaria/s antes de la destruccin del componente, // como liberacin de recursos }

// lgica de negocio del Singleton }

3.6 SINGLETON SESSION BEANS: UN EJEMPLO SENCILLO La utilidad del componente Singleton est limitada a ciertos problemas de negocio que podemos (o debemos) resolver mediante una nica instancia de un objeto. Ejemplos vlidos de componentes Singleton seran gestores de ventanas, sistemas de ficheros, colas de impresin, caches, etc. Para nuestro ejemplo de Singleton vamos a desarrollar un sistema de logging, el cual debe ser accedido por toda la aplicacin a travs de una nica instancia:
package es.davidmarco.ejb.singleton; import import import import import import import import import java.io.FileWriter; java.io.IOException; java.text.SimpleDateFormat; java.util.Date; javax.annotation.PostConstruct; javax.annotation.PreDestroy; javax.ejb.Lock; javax.ejb.LockType; javax.ejb.Singleton;

@Singleton public class SistemaDeLog { private FileWriter writer; private enum Nivel { DEBUG, INFO, ERROR } @PostConstruct protected void inicializar() throws IOException { writer = new FileWriter("aplicacion.log", true); } @PreDestroy protected 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); }

@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 ms desarrollado que los vistos hasta ahora (es completamente funcional), sigue siendo un ejemplo de juguete; sin embargo, es bastante interesante para explicar 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 mtodos callback @PostConstruct y @PreDestroy. En ellos se obtiene y libera (respectivamente) un recurso dado, en nuestro caso 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 obtendrn todos los clientes del Singleton. Ambos mtodos se han marcado con visibilidad protected para facilitar la tarea de testing (y por un motivo adicional que se explicar en la prxima seccin). Lo segundo que nos interesa del ejemplo son los tres mtodos que se exponen al cliente: debug, info, y error. A traves de ellos el cliente puede realizar las operaciones de logging. Los tres han sido marcados como mtodos write mediante @Lock(LockType.WRITE), y aunque este comportamiento es definido por defecto para todos los mtodos de un Singleton (y por tanto la anotacin es redundante), se han incluido para diferenciarlos del resto (siempre es aconsejable escribir cdigo expresivo). Por ltimo, los mtodos de utilidad escribirMensajeEnArchivo y generarCabecera son mtodos de utilidad usados por la lgica de negocio de nuestro componente.

3.7 SESSION BEANS EN GENERAL: NO-INTERFACE VIEW Como se indic en la seccin anterior, el ejemplo del sistema de logging es completamente funcional. Sin embargo, el componente no ha sido declarado local ni remoto (ejemplos anteriores omitan este aspecto intencionadamente para mantener el cdigo simple). En este momento es preciso introducir el concepto de vista sin interface. Una vista sin interface (no-interface view) es una variacin del concepto de componente local, e introducida en la especificacin EJB 3.1. Como su nombre indica, se trata de un componente que no est anotado con @Local ni @Remote, ni implementa ninguna interface de negocio donde se hayan aplicado cualquiera de las anotaciones anteriores. Si no implementamos ninguna interface de negocio, como sabe el contenedor que mtodos del componente debe exponer a sus clientes? La respuesta es sencilla: todos aquellos declarados public, incluyendo los de sus superclases y los de tipo callback (en el ejemplo de la seccin anterior hemos declarado los mtodos callback con un nivel de visibilidad menor a public precisamente para evitar que sean expuestos). Aunque a efectos prcticos un componente de este tipo es tratado como uno local, puedes encontrar los detalles concretos sobre la vista sin interface en la especificacin EJB 3.1.

3.8 SESSION BEANS EN GENERAL: LLAMADAS ASNCRONAS Otra de las novedades de la especificacin EJB 3.1 es la posibilidad de llamar a los mtodos de nuestros Session Bean de forma asncrona. Hasta ahora, todas las llamadas que hemos realizado han sido sincronas, de manera que los clientes deben esperar hasta que el mtodo invocado termine de ejecutarse para seguir ejecutando el resto de sus sentencias. Este es el comportamiento normal cuando invocamos un mtodo en Java. Sin embargo, cuando realizamos una llamada asncrona, el flujo de ejecucin vuelve automticamente a la aplicacin que realiza la llamada, sin esperar el resultado de la llamada. Ms tarde, podemos comprobar dicho resultado si existe y lo necesitamos. Las llamadas asncronas son de gran utilidad en situaciones en las que un mtodo realiza una operacin que necesita un tiempo considerable para completarse, y no deseamos bloquear al cliente mientras dicha operacin se realiza. Veamos primero el ejemplo ms sencillo posible de llamada asncrona:
package es.davidmarco.ejb.slsb; import javax.ejb.Asynchronous; import javax.ejb.Stateless; @Stateless public class ClaseAsincrona { @Asynchronous public void metodoLento() {

try { Thread.sleep(15000); } catch(InterruptedException ie) { throw new RuntimeException(ie); } } }

En el ejemplo anterior, hemos declarado el mtodo metodoLento() como un mtodo asncrono mediante la anotacin @Asynchronous. Dicha anotacin puede ser aplicada tambin a nivel de clase, en cuya caso todos los mtodos de negocio (los declarados en una interface local, en una interface remota, o los de visibilidad public en una vista sin interface) sern considerados como asncronos. Dentro del mtodo hemos simulado un proceso relativamente largo deteniendo durante quince segundos el hilo de ejecucin que est procesando la instancia del SLSB. Cualquier cliente que llame a este mtodo no tendr que esperar esos quince segundos, ya que nada ms realizar la llamada le ser devuelto en control de ejecucin, sin esperar a que el mtodo asncrono termine. Este comportamiento se conoce como fire-and-forget (disparar y olvidar). Otra posibilidad es que necesitemos el resultado de una invocacin asncrona. Supongamos que necesitamos un mtodo que realiza un clculo intensivo y, cuando finalmente obtiene el resultado, lo devuelve en una variable de tipo Double. La manera de declarar dicho mtodo sera similar a esta:
import java.util.concurrent.Future; import javax.ejb.AsyncResult; import javax.ejb.Asynchronous; // ... @Asynchronous public Future metodoLentoConResultado() { try { Thread.sleep(15000); return new AsyncResult(Math.PI); } catch(InterruptedException ie) { throw new RuntimeException(ie); } }

Como puedes ver, el mtodo metodoLentoConResultado() devuelve un objeto de tipo Future (ms concretamente de su implementacin AsyncResult) parametizado a Double. Es sobre este objeto Future sobre el que el cliente deber comprobar si el mtodo asncrono ha terminado su ejecucin, para obtener a continuacin su resultado:
import java.util.concurrent.ExecutionException;

import java.util.concurrent.Future; import javax.naming.NamingException; // ... Future resultado = bean.metodoLentoConResultado(); System.out.println("Mtodo asncrono invocado"); System.out.println("Realizando otras tareas mientras se ejecuta el mtodo asncrono..."); // ... while(true) { if(!resultado.isDone()) { Thread.sleep(2000); } else { System.out.println("Mtodo asincrono devuelve el resultado " + resultado.get()); break; } }

El ejemplo anterior es parte de un cliente del SLSB asncrono. En el, en un momento dado, se llama al mtodo asncrono, asignando el objeto Future devuelto por dicho mtodo en una variable. En este momento el resultado no est listo (recuerda que tardar quince segundos en producirse), pero el control de la ejecucin del cliente continua sin esperar. Ms adelante el cliente comprueba si el resultado est finalmente disponible mediante el mtodo isDone() de la clase Future. Cuando esto ocurra, podemos obtener nuestro ansiado resultado mediante el mtodo get() (tambin de la clase Future), el cual devuelve un objeto del mismo tipo al que usamos para parametizar la respuesta del mtodo asncrono (en nuestro caso, un objeto Double). Es importante tener presente que el mtodo get() bloquea el hilo de ejecucin del cliente hasta que el resultado est listo, de manera que podemos omitir por completo el bucle while:
Future resultado = bean.metodoLentoConResultado(); System.out.println("Mtodo asncrono invocado"); System.out.println("Realizando otras tareas mientras se ejecuta el mtodo asncrono..."); // ... System.out.println("Mtodo asncrono devuelve resultado " + resultado.get());

La interface Future incluye otros mtodos con los que podemos cancelar la ejecucin del mtodo asncrono, o comprobar si dicha ejecucin ha sido cancelada por el contenedor (por ejemplo en caso de excepcin). Es recomendable que visites la API de Future si vas a trabajar con ella. Antes de terminar con los mtodos asncronos, quiero hacer constar que en la implementacin del contenedor EJB que estamos usando en este tutorial (JBoss 6.0.0 Final)

las llamadas asncronas an no estn completamente implementadas (el hilo de ejecucin del cliente se bloquea al llamar al mtodo asncrono hasta que este ha terminado de ejecutarse; en otras palabras, las llamadas asncronas se comportan como llamadas sncronas). En otros contenedores compatibles con EJB 3.1, como Glassfish v3, el comportamiento de las llamadas asncronas si es el correcto (no lo he probado personalmente, pero as aparece indicado por usuarios de ambos servidores).

3.9 SESSION BEANS EN GENERAL: RESUMEN Con esta seccin terminamos de ver los componentes de tipo Session Bean, que son aquellos que contienen la lgica de negocio de una aplicacin EJB que puede ser invocada por los clientes de dicha aplicacin (ya sea en cualquiera de sus tres variaciones: Stateless, Stateful, o Singleton). Ahora es el momento de pasar a un nuevo tipo de componente con una misin totalmente diferente: Message-Driven Beans.

3.10 MESSAGE-DRIVEN BEANS: CONCEPTOS BSICOS Los componentes de tipo Message-Driven Bean (MDB - Bean Dirigido por Mensajes) son componentes asncronos de tipo listener (oyente). Un MDB no es ms que un componente que espera a que se le envie un mensaje, y realiza cierta accin cuando finalmente recibe dicho mensaje (el MDB escucha por si alguien le llama, de ah su nombre). Algunas propiedades 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 mensajera, el cual se compone de los 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, el cual distribuir los mensajes que consuma a los MDB correspondientes. Los tres primeros subsistemas (clientes y broker) forman parte del servicio de mensajera, que en la especificacin EJB es gestionado por defecto mediante JMS. En este momento es preciso desviarnos del camino para explicar con un mnimo de detalle que es y cmo funciona JMS.

3.11 JAVA MESSAGE SERVICE: CONCEPTOS BSICOS Java Message Service (JMS - Servicio de Mensajera en Java) es una API neutral que puede

ser usada para acceder a sistemas de mensajera. Todos los contenedores EJB 3.x deben proporcionar un proveedor de JMS (el cual define su propia implementacin de la API), de manera que podamos trabajar con mensajes sin necesidad de aadir libreras externas. Puesto que esta API es neutral, podemos cambiar en cualquier momento el proveedor JMS por uno que se adecue ms a nuestras necesidades (o incluso usar un sistema de mensajera diferente a JMS). Una aplicacin JMS se compone, generalmente, de mltiples clientes JMS y un nico proveedor JMS. Un cliente JMS puede ser de dos tipos: - Productor: su misin es enviar mensajes - Consumidor: su misin es recibir mensajes Por otro lado, la misin del proveedor JMS es dirigir y enviar los mensajes que le llegan a traves de un broker (esto es algo bastante ms complejo, pero por simplicidad vamos a pensar que tenemos un subsistema llamado broker donde se almacenan los mensajes enviados hasta que son servidos a todos sus consumidores). JMS proporciona un tipo de mensajera asncrona: los clientes JMS envan mensajes a traves del broker sin esperar una respuesta. Es responsabilidad del broker hacer llegar el mesaje a los clientes JMS que deban consumir el mensaje. De esta manera JMS proporciona un sistema de comunicacin muy poco acoplado, pues el productor y el consumidor no saben el uno del otro en ningn momento.

3.12 JAVA MESSAGE SERVICE: MODELOS DE MENSAJERA JMS proporciona dos modelos de mensajera, los cuales nos permiten definir el comportamiento de nuestro sistema de mensajera: - 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 canal virtual llamado topic (tema). A su vez, uno o varios clientes JMS de tipo consumidor se suscriben a dicho topic si desean recibir los mensajes que en l se publiquen. Si un suscriptor decide desconectarse del topic y ms tarde reconectarse, recibir todos los mensajes publicados durante su ausencia (aunque este comportamiento es configurable). Por todo esto, el modelo pub/sub es de tipo uno-a-muchos (un productor, muchos consumidores). En el modelo p2p, un cliente JMS de tipo productor publica sus mensajes en un canal virtual llamado queue (cola). De manera similar al modelo pub/sub, pueden existir multiples consumidores conectados al queue. Sin embargo, el queue no enviar automticamente 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 consumidor consumir cada mensaje publicado: el primero en solicitarlo. Cuando esto ocurra, el mensaje se borrar del queue, y el resto de consumidores no ser siquiera consciente de la

anterior existencia del mensaje. Por todo esto, el modelo p2p es de tipo uno-a-uno (un productor, un consumidor). En versiones anteriores a JMS 1.1 cada uno de estos modelos usaba su propio conjunto de interfaces y clases para para el envio y recepcin de mensajes. Desde la citada versin de JMS, sin embargo, est disponible una API unificada que es vlida para ambos modelos de mensajera. Ahora es el momento de volver al camino que dejamos dos secciones atrs, y seguir con nuestros amados componentes MDB.

3.13 MESSAGE-DRIVEN BEANS: EL CICLO DE VIDA Tal como vimos en la seccin 3.10, los componentes MDB no mantienen estado entre invocaciones (son stateless, pero no confundir con los componentes SLSB). Por tanto, su ciclo 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 an, y por tanto no existe en memoria. Un MDB en el segundo estado representa una instancia que ha sido instanciada e inicializada por el contenedor. Se llega a este estado cuando se inicia el servidor (que puede decidir crear cierto nmero de instancias del MDB para procesar mensajes), o cuando el nmero de instancias en el pool sea insuficiente para atender todos los mensajes que se estn recibiendo. La especificacin EJB no fuerza a que exista un pool de MDB's, de manera que una implementacin concreta de contenedor EJB ppdra decidir crear una instancia cada vez que se reciba un mensaje (y eliminar esta instancia al terminar de procesar el mensaje) en lugar de mantener un pool de instancias ya preparadas. Este ltimo aspecto, sea como sea, no nos afecta como programadores (al disear un MDB) ni como clientes; la forma en que sea gestionada nuestra aplicacin es, a priori, responsabilidad exclusiva del contenedor. Durante la transicin entre el primer estado y el segundo, el contenedor realizar tres operaciones (en este orden): - Instanciacin del MDB - Inyeccin de cualquier recurso necesario y de dependencias - Ejecucin de un mtodo dentro del MDB marcado con la anotacin @PostConstruct, si existe La instanciacin del MDB se lleva a cabo mediante reflexin, a traves de Class.newInstance(). Por tanto, el MDB debe tener un constructor por defecto (sin argumentos), ya sea de forma implcita o explcita. Durante la inyeccin de dependencias, el contenedor inyectar automticamente cualquier

recurso necesario para el MDB en base a los metadatos que hayamos proporcionado (como una anotacin @MessageDrivenContext, o una entrada en el descriptor XML de EJB). De manera adicional, cada vez que un MDB procese un nuevo mensaje, todas las dependencias se inyectarn de nuevo. Por ltimo, el contenedor ejecutar, si existe, un mtodo dentro del MDB anotado con @PostConstruct (Post construccin). En este mtodo podemos obtener recursos adicionales necesarios para el MDB (como conexiones de red, etc), recursos que permanecern abiertos hasta la destruccin del MDB. Las reglas de declaracin de los mtodos callback como @PostConstruct se vieron una y otra vez en el artculo anterior. Cuando el contenedor no necesita una instancia del MDB (ya sea porque decide reducir el nmero de instancias en el pool, o porque se esta produciendo un shutdown del servidor), se realiza una transicin en sentido inverso: del estado preparado en pool al estado no existe. Durante esta transicin se ejecutar, si existe, un mtodo anotado con @PreDestroy (Pre destruccin), donde podemos liberar los recursos adquiridos en @PostConstruct. Es importante tener presente que dentro del mtodo @PreDestroy todava tenemos acceso al contexto del MDB (lo mismo es vlido para todos los Session Bean).

3.14 MESSAGE-DRIVEN BEANS: DEFINICIN Como ya hemos visto, los componentes de tipo MDB tienen como misin procesar mensajes enviados de forma asncrona. Aunque todo contenedor compatible EJB 3.x debe incluir una implementacin de JMS (de manera que tengamos un servicio de mensajera on the box), MDB puede trabajar con otros servicios de mensajera diferentes. En este tutorial solo se usar JMS como servicio de mensajera (afectando este hecho, por ejemplo, a la interface que debe implementar el MDB, como veremos en el prximo prrafo). Todo MDB debe implementar la interface javax.jms.MessageListener, la cual define el mtodo onMessage(). Es dentro de este mtodo donde se desarrolla toda la accin cuando el MDB procesa un mensaje, como veremos en el ejemplo de la prxima seccin. De manera adicional, debemos indicar al contenedor que nuestra clase es un componente MDB. Para ello, utilizamos la anotacin @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 } }

El ejemplo anterior an no es funcional (producir un error de despliegue), ya que el MDB necesita saber el tipo de canal virtual (topic/queue) al que debe conectarse, as como el nombre de dicho canal virtual. Esta informacin forma parte de la configuracin del MDB, configuracin que proporcionamos al componente a travs del atributo activationConfig (configuracin de activacin) de la anteriormente vista anotacin @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 configuracin necesaria mediante un array de anotaciones @ActivationConfigProperty. Cada una de estas anotaciones contiene dos atributos: propertyName y propertyValue, en los cuales indicamos parejas nombre-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 opcin de configuracin bastante interesante (aunque opcional) es la duracin de la subscripcin:
@ActivationConfigProperty(propertyName="subscriptionDurability", propertyValue="Durable")

Aadiendo la anotacin anterior al array de propiedades de configuracin del MDB, nuestro componente recibir todos los mensajes que se envien a su canal virtual mientras el contenedor EJB (y por tanto el propio componente MDB) est offline. Esta situacin puede ocurrir durante un shutdown del servidor, un problema de conectividad por red, etc. En otras palabras, el mensaje estar disponible hasta que todos sus suscriptores de tipo durable lo hayan recibido y procesado. La opcin contraria se aplica cambiando el valor del atributo propertyValue a NonDurable (no durable). La durabilidad de los mensajes solo afecta a los componentes MDB que trabajan con topics (modelo pub/sub), pues en un canal de tipo queue no tiene ningn sentido almacenar un mensaje para componentes MDB offline (en cuanto un MDB este online y lo consuma, el mensaje se eliminar). Al igual que los componentes Session Bean, los componentes MDB funcionan dentro de un contexto de ejecucin. Y por tanto, al igual que los componentes Session Bean, nuestros MDB pueden acceder a su contexto de ejecucin si necesitan comunicarse con el

contenedor:
import javax.annotation.Resource; import javax.ejb.MessageDriven; import javax.ejb.MessageDrivenContext; @MessageDriven(/*...*/) public class PrimerMDB implements MessageListener { @Resource private MessageDrivenContext context; // ... }

En el ejemplo anterior indicamos al contenedor que inyecte una instancia de MessageDrivenContext mediante la anotacin @Resource. MessageDrivenContext extiende la interface EJBContext, sin aadir ningn mtodo. Solamente los mtodos transaccionales son tiles al MDB, como se ver cuando trabajemos con transacciones en artculos posteriores. El resto de mtodos de la interface lanzar una excepcin si son invocados, ya que no tiene sentido que un MDB pueda, por ejemplo, acceder al servicio de seguridad. Por otro lado, un MDB tambin puede referenciar un Session Bean para realizar el procesamiento del mensaje (mediante la anotacin @EJB, seccin 2.5 del artculo anterior), as como enviar l mismo sus propios mensajes mediante JMS (al final de la prxima seccin veremos un ejemplo de un MDB procesando mensajes, y a su vez enviando nuevos mensajes que sern procesador por otro MDB).

3.15 MESSAGE-DRIVEN BEANS: UN SENCILLO EJEMPLO Ya hemos visto como los servicios de mensajera asncronos desacoplan de forma completa al productor de un mensaje de su/s receptor/es: ninguno de ellos es consciente de la parte contraria. Esto debido a que, entre ellos, existe un broker donde se realiza todo el proceso de almacenaje (ya sea en canales virtuales de tipo topic o queue) y reparto de los mensajes que se reciben. El ejemplo que vamos a ver consta de tres partes: - Creacin de un canal virtual - Creacin de un componente MDB para consumir mensajes - Creacin de un cliente para enviar mensajes mediante JMS El primer paso lgico es crear un canal virtual donde poder enviar mensajes (un topic para modelos pub/sub o un queue para modelos p2p). En JBoss 6.0.0 Final (el servidor de aplicaciones que instalamos en el anexo que acompaa este tutorial), el proveedor JMS incorporado es HornetQ 2.1.2 Final. Para declarar un canal virtual de tipo topic, edita el

archivo llamado hornetq-jms.xml del directorio server/default/deploy/hornetq/ de tu instalacin de JBoss y aade lo siguiente (default es la configuracin por defecto al crear el servidor en Eclipse; si seleccionaste otra configuracin, modifica la ruta anterior a la que corresponda):
<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 el elemento <topic>) con nombre PrimerTopic, y lo hemos asociado a la direccin JNDI /topic/PrimerTopic (es necesario reiniciar JBoss si se encuentra levantado). La forma de configurar un canal virtual puede ser diferente entre distintos contenedores (incluso entre distintas implementaciones de un mismo contenedor), por lo que si ests usando un servidor de aplicaciones diferente a JBoss 6.0.0 Final o un proveedor JMS diferente a HornetQ 2.1.2 Final, quiz necesites revisar la documentacin correspondiente para declarar el canal virtual. Aunque este ejemplo se basar en un canal virtual de tipo topic, puedes declarar un queue (para montar un modelo de mensajera 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 lgico sera escribir un componente MDB que procese los mensajes del topic. Para ello, necesitamos crear un proyecto EJB en Eclipse y declarar una clase como la siguiente:
package es.davidmarco.ejb.mdb;

import import import import import import

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

@MessageDriven(activationConfig={ @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Topic"), @ActivationConfigProperty(propertyName="destination", propertyValue="topic/PrimerTopic")}) public class PrimerMDB implements MessageListener { @Override public 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 anotacin @MessageDriven), lo configuramos para actuar como consumidor de los mensajes del topic registrado con direccin JNDI topic/PrimerTopic (con las anotaciones @ActivationConfigProperty), y finalmente aadimos dentro del mtodo onMessage() la lgica que procesar los mensajes. Recuerda que este mtodo es el nico declarado en la interface MessageListener, la cual deben implementar todos los MDB basados en JMS. Es interesante explicar con un mnimo detalle las operaciones que se realizan dentro de nuestro mtodo onMessage. Este mtodo requiere un parmetro de tipo javax.jms.Message, que es una interface base de la cual extienden otras interfaces ms especficas. Una de esas subinterfaces es TextMessage, la cual provee de mtodos para trabajar con mensajes cuyo contenido es texto. Por tanto, este MDB est diseado para recibir mensajes desde el topic asociado, y procesarlos si son de tipo TextMessage (en caso afirmativo, se imprime en el log del servidor el contenido del mensaje). Si durante dicho procesamiento se produce un error, el MDB lanzar una excepcin. Lo ideal es que el topic asociado solo reciba este tipo de mensajes, y que mensajes con otro tipo de contenido sean almacenados en otros canales virtuales (de esta manera no se crearn instancias que finalmente no procesarn el mensaje). Tras desplegar el proyecto EJB con nuestro primer componente MDB, el ltimo paso

lgico sera crear un cliente JMS que produjera mensajes y los enviara al topic. Este cliente podra ser, por ejemplo, un proyecto Java normal y corriente. Puesto que necesitamos las librerias de JMS (las cuales son parte de EJB 3.x), debemos incluirlas al crear nuestro proyecto. La forma ms sencilla sera pulsando Next en la pantalla de creacin del proyecto Java en Eclipse, seleccionando la pestaa Libraries y aadiendo las librerias del servidor JBoss: Add Library > Server Runtime > Botn Next > JBoss 6.0 Runtime > Botn Finish > Botn Finish Ahora ya estamos listos para escribir el cliente productor JMS:
package es.davidmarco.ejb.cliente; import import import import import import import import import import import java.util.Properties; javax.jms.Connection; javax.jms.ConnectionFactory; javax.jms.JMSException; javax.jms.MessageProducer; javax.jms.Session; javax.jms.TextMessage; javax.jms.Topic; javax.naming.Context; javax.naming.InitialContext; 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("Mensaje enviado desde Java"); productor.send(mensajeDeTexto); conexion.close();

} }

En nuestro cliente JMS externo al contenedor lo primero que hacemos es crear un objeto de propiedades con los valores necesarios para conectar con el contenedor EJB (como hicimos en el cliente del primer artculo). A continuacin creamos un contexto de ejecucin desde el que poder acceder al contenedor, y mediante JNDI obtenemos un objeto factora ConnectionFactory, a travs del cual podremos realizar conexiones para el envo de mensajes. Ahora viene la parte interesante: creamos un objeto Topic que est asociado al canal virtual que hemos declarado en el primer paso lgico de esta seccin (mediante su direccin JNDI), creamos una conexin con el broker mediante el objeto factora, iniciamos una nueva sesin dentro de la conexin recin creada, creamos un objeto MessageProducer asociado al objeto Topic, iniciamos la conexin para poder enviar un mensaje, creamos un mensaje de tipo texto, lo enviamos, y finalmente cerramos la conexin. Al ejecutar el cliente, el mensaje se enviar al topic, y puesto que existen clientes consumidores asociados a ese topic, el contenedor EJB consumir el mensaje, extraer del pool MDB una instancia del componente MDB (o crear la instancia en el aire; a nosotros nos es indiferente), y le pasar el mensaje al mtodo onMessage() de dicha instancia para que procese el mensaje. Si, tras ejecutar el cliente JMS, miras la pestaa Console de Eclipse, vers el mensaje imprimido en el log de JBoss (tal como se defini al escribir el mtodo onMessage). Recuerdas el sencillo esquema de la seccin 3.10?: Cliente (Productor) ---> Broker ---> Cliente (Consumidor) ---> Message-Driven Bean - El cliente Java es el cliente productor - JMS (ms concretamente su implementacin 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 mensajera es que el cliente productor no sabe quien consumir su mensaje, ni como lo har. Podra ser un MDB, o podra ser otro componente en nada relacionado con la especificacin EJB (tal vez ejecutndose en una mquina remota con un sistema operativo distinto). Por ltimo, veamos como hacer que un MDB sea tambin productor:
package es.davidmarco.ejb.mdb; import javax.annotation.Resource; import javax.ejb.ActivationConfigProperty; import javax.ejb.MessageDriven;

import import import import import import import import import import

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

@MessageDriven(activationConfig={ @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Topic"), @ActivationConfigProperty(propertyName="destination", propertyValue="topic/PrimerTopic")}) public class PrimerMDB implements MessageListener { @Resource private MessageDrivenContext contexto; @Override public 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 un mensaje"); } } } 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(); } }

En el ejemplo anterior, dentro del mtodo onMessage() se llama a un mtodo de utilidad que envia un nuevo mensaje a un segundo topic (que debemos haber declarado con anterioridad, por supuesto). La nica diferencia entre el cdigo del mtodo de utilidad y el mtodo main() del cliente Java es que, puesto que el primero se est ejecutando dentro del contenedor EJB, podemos obtener el contexto de ejecucin mediante inyeccin de dependencia, evitando as la necesidad de crear el objeto de propiedades y conectar por red al contenedor (lo cual sera bastante absurdo). Ahora podemos declarar un segundo MDB que procese los mensajes del segundo topic:
package es.davidmarco.ejb.mdb; import import import import import import javax.ejb.ActivationConfigProperty; javax.ejb.MessageDriven; javax.jms.JMSException; javax.jms.Message; javax.jms.MessageListener; javax.jms.TextMessage;

@MessageDriven(activationConfig={ @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Topic"), @ActivationConfigProperty(propertyName="destination", propertyValue="topic/SegundoTopic")}) public class SegundoMDB implements MessageListener { @Override public 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 (pestaa 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 enviado desde Java 19:00:47,475 INFO [STDOUT] SegundoMDB ha procesado el mensaje: Mensaje enviado desde MDB

3.16 RESUMEN En este tercer artculo del tutorial de EJB hemos terminado de ver los componentes de lado del servidor, adems de algunas caractersticas interesantes que son nuevas en la especificacin EJB 3.1: llamadas asncronas y vista sin interface (ambas son aplicables a componentes Session Bean, pero no a Message-Driven Beans). En el prximo artculo 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 aplicacin EJB 3.1. En ningn caso se explicar a fondo JPA (ni que es, ni como funciona), as que si no conoces este framework te aconsejo que visites el tutorial de cuatro artculos sobre JPA que se encuentra publicado en esta misma web.

Introduccin a EJB 3.1 (IV)


Publicado el 07 de Abril de 2011

En los artculos anteriores del tutorial de introduccin a EJB 3.1, hemos visto como declarar y trabajar con componentes de lado del servidor (Session Beans y Message-Driven Beans). El ltimo componente que nos queda por ver es Entity Beans (EB - Beans de Entidad; a partir de ahora nos referiremos a ellos como entidades).

4.1 ENTIDADES: CONCEPTOS BSICOS Las entidades, a diferencia del resto de componentes EJB, son objetos Java reales que son manejados entre componentes (o entre un cliente y un componente) en su forma original, nunca a travs de proxys/vistas. Podemos crearlos con sentencias new, pasarlos como parmetros a un Session Bean, etc. Pero el verdadero valor de las entidades reside en que su estado puede ser almacenado en una base de datos, y ms tarde recuperado en un nuevo objeto del tipo correspondiente. De manera adicional, los cambios que realicemos en el estado de una entidad sern sincronizados con la informacin que tenemos almacenada en la base de datos. Aunque las entidades se consideran componentes EJB, hasta la version JavaEE 1.4 pertenecan a una especificacin independiente llamada Java Persistence API (JPA - API de Persistencia en Java). Fue a partir de la versin 5 de JavaEE que la especificacin EJB absorvi a la especificacin JPA. Sin embargo, podemos trabajar con entidades en una aplicacin no-EJB, aunque, tras bambalinas, se seguirn ejecutando dentro de un contenedor EJB. Desde ahora usaremos el trmino aplicacin JPA para referirnos a aplicaciones que realizan persistencia, y el trmino aplicacin EJB cuando exista integracin de ambas tecnologas.

En este artculo no vamos a tratar en profundidad la especificacin JPA (ni la declaracin ni el uso de entidades), si no como integrarla con una aplicacin EJB 3.1. Si no conoces JPA, es prcticamente obligatorio que visites el tutorial de JPA publicado en este mismo blog, de manera que puedas comprender todo el material que sigue a continuacin.

4.2 ENTIDADES: EL CICLO DE VIDA Como ya se ha mencionado, las entidades no son objetos del lado del servidor. Sin embargo, cuando son usadas dentro del contexto de un contenedor EJB, se convierten en objetos gestionados (gracias al servicio de persistencia, el cual es controlado mediante la interface EntityManager). Esto nos lleva a los dos nicos estados de una entidad cuando se encuentra 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 datos subyacente. En el segundo estado, la entidad es un objeto Java regular, y cualquier cambio que 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 (mediante serializacin).

4.3 ENTIDADES: UNIDAD DE PERSISTENCIA Una unidad de persistencia (persistence unit) representa un conjunto de entidades que pueden ser mapeadas a una base de datos, as como la informacin necesaria para que la aplicacin JPA pueda acceder a dicha base de datos. Se define mediante un archivo llamado persistence.xml, el cual debe acompaar a la aplicacin donde se realizan las tareas de persistencia (recuerda que puede ser una aplicacin EJB o una aplicacin 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"> <!-- configuracin de acceso a la base de datos --> <!-- lista de entidades que pueden ser mapeadas --> </persistence-unit> </persistence>

Podemos definir ms de una unidad de persistencia por aplicacin, declarando cada una de ellas mediante el elemento XML <persistence-unit>. Cada unidad de persistencia debe seguir estas dos reglas: - Debe proporcionar un nombre (identidad) a travs 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 archivo persistence.xml deber encontrarse en una localizacin u otra. En la seccin 4.6 veremos un ejemplo completo de este archivo, as como la informacin necesaria para su correcto despliegue dentro de una aplicacin EJB.

4.4 ENTIDADES: CONTEXTO DE PERSISTENCIA Otro concepto que tenemos que tener claro es el de contexto de persistencia. Un contexto de persistencia representa un conjunto de instancias de entidades que se encuentran gestionadas en un momento dado. Existen dos tipos de contextos de persistencia: - Limitados a una transaccin (Transaction-scoped) - Extendidos (Extended) Cuando trabajamos dentro de un contexto de persistencia limitado a una transanccin, todas las entidades gestionadas pasarn a estar no gestionadas cuando dicha transaccin finalice. Dicho con otras palabras, los cambios realizados tras finalizar la transaccin no sern sincronizados con la base de datos. Cuando trabajamos dentro de un contexto de persistencia extendido, las cosas funcionan de manera diferente: el contexto de persistencia sobrevivir a la transaccin donde se ejecuta, de manera que los cambios que realicemos en el estado de las entidades gestionadas por el contexto de persistencia se sincronizarn con la base de datos en el momento en que entremos en una nueva transaccin. Este comportamiento es til cuando trabajamos con SFSB, pues permite mantener un estado conversacional y mantener nuestras entidades sincronizadas.

4.5 ENTIDADES: ENTITY MANAGER Mediante la interface EntityManager (Gestor de entidades) tenemos acceso al servicio de persistencia de nuestro contenedor. Podemos obtener una instancia de EntityManager en nuestros componentes EJB mediante inyeccin de dependencias:
import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext;

@Stateless public class MiSlsb { @PersistenceContext(unitName="introduccionEJB") private EntityManager em; // operaciones del SLSB }

El ejemplo anterior inyecta una instancia de EntityManager en el SLSB mediante la anotacin @PersistenceContext. A esta anotacin hay que proporcionarle como atributo el nombre de la unidad de persistencia que EntityManager usar para realizar la persistencia (y que hemos definido en el archivo persistence.xml). Con los metadatos proporcionados obtendremos por defecto un contexto de persistencia limitado a una transaccin (ver seccin anterior); si deseamos obtener un contexto de persistencia extendido (solo vlido en SFSB por su naturaleza conversacional), debemos aadir el atributo 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 integracin de EJB con JPA termina aqu. Por tanto, podemos pasar a ver un ejemplo donde conectaremos todas las piezas para realizar persistencia mediante EJB 3.1.

4.6 ENTIDADES: UN SENCILLO EJEMPLO Veamos un sencillo ejemplo de un componente EJB realizando persistencia sobre una base de datos. Al igual que algunos ejemplos anteriores, este consta de varias partes: - Una fuente de datos (data source) donde realizar la persistencia - Una aplicacin EJB donde se realiza las acciones de persistencia - Un cliente EJB desde el que intercambiar entidades con la aplicacin EJB Nuestro primer paso va a ser definir una fuente de datos que conectar nuestro contenedor EJB con nuestra base de datos. Siguiendo el entorno de desarrollo configurado en el anexo que acompaa este tutorial, escribimos un archivo llamado derby-ds.xml y lo guardamos en el directorio server\default\deploy de nuestra instalacin de JBoss:

<?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>DerbyDS</jndi-name> <connectionurl>jdbc:derby://localhost:1527/introduccionEJB;create=true</connectionurl> <driver-class>org.apache.derby.jdbc.ClientDataSource40</driverclass> <user-name></user-name> <password></password> </local-tx-datasource> </datasources>

En el archivo XML anterior definimos una fuente de datos (data source) limitado a transacciones locales, esto es, dentro del propio contenedor (existe otro ambito de ejecucin de una transaccin llamado extendido, capaz de realizar su trabajo a travs de mltiples contenedores). A esta fuente de datos le hemos dado un nombre JNDI desde la que poder invocarla, as como los parmetros de conexin con nuestra base de datos subyacente (url, usuario, y password). Si JBoss 6.0.0 Final est arrancado, nada ms guardar el archivo anterior la fuente de datos ser activada y se producir la conexin con Derby, as que debers tener la base de datos iniciada o se producir un error; nosotros vamos a considerar que en este preciso momento tanto JBoss como Derby estn parados. El siguiente paso es crear un proyecto EJB 3.1 en Eclipse, y continuacin levantar la base de datos Derby desde el propio IDE. Para ello, haz click con el botn derecho sobre el nombre del proyecto EJB en la pestaa Proyect Explorer y selecciona: Apache Derby > Add Apache Derby nature La operacin anterior aade a nuestro proyecto las librerias del servidor embebido Derby, de manera que podamos conectar con l. A continuacin levantamos la base de datos haciendo click con el botn derecho sobre el nombre del proyecto EJB en la pestaa Proyect 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 botn OK para finalizar este proceso. En un entorno en produccin, todas estas operaciones seran innecesarias, pues tericamente tendramos una base de datos externa funcionando de manera continua. Ahora vamos a crear un componente SLSB remoto, de manera que podamos llamarlo desde un cliente Java normal. Como recordars, un componente remoto requiere de manera obligatoria implementar una interface:
package es.davidmarco.ejb.slsb;

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 import import import import javax.ejb.Remote; javax.ejb.Stateless; javax.persistence.EntityManager; javax.persistence.PersistenceContext; es.davidmarco.ejb.entidad.Cuenta;

@Remote @Stateless public class OperacionesConCuentasRemote implements OperacionesConCuentas { @PersistenceContext(unitName="introduccionEJB") private EntityManager em; @Override public void crearCuenta(Cuenta nuevaCuenta) { em.persist(nuevaCuenta); } @Override public Cuenta obtenerCuenta(Long id) { return em.find(Cuenta.class, id); } @Override public void borrarCuenta(Cuenta cuenta) { em.remove(cuenta); } }

El componente anterior es extremadamente sencillo: en l se inyecta una instancia de EntityManager, la cual es usada en los mtodos del Session Bean para realizar las tareas de persistencia. Estos mtodos usan como parmetros o tipos de retorno instancias de la clase Cuenta (la cual define cuentas bancarias) y que ser nuestra entidad:
package es.davidmarco.ejb.entidad;

import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Cuenta implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private 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;

} }

Algunos detalles de la entidad definida en el ejemplo anterior merecen una pequea explicacin: para empezar, nuestra entidad implementa la interface Serializable, necesara cuando nuestra entidad va a viajar a travs de una red (en nuestro caso entre el cliente Java y el contenedor EJB). Dentro de la entidad definimos 4 propiedades: una para la identidad de la entidad, y tres para representar su estado (numeroDeCuenta, nombreDelTitular, y saldo), ms sus correspondientes mtodos getter/setter. De manera adicional, hemos aadido algunas operaciones de lgica de negocio (aumentarSaldo() y reducirSaldo) dentro de la entidad; es una buena prctica que las operaciones relacionadas con cuentas estn dentro de la clase que representa dichas cuentas. Ahora que tenemos un componente EJB y una entidad, vamos a crear la unidad de persistenca asociada a la fuente de datos y nuestra entidad. Crea un archivo llamado persistence.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 nombre introduccionEJB (que fue el que usamos como parmetro de la anotacin @PersistenceContext en el componente SLSB que definimos previamente), as como transacciones de tipo JTA (gestionadas por el contenedor; los tipos de transaccin se explicaron en las secciones 3.2 y 3.3 del tercer artculo del tutorial de JPA). Ya dentro de la declaracin de la unidad de persistencia, hemos declarado el proveedor de persistencia que usaremos (HibernatePersistence), la direccin JNDI de la fuente de datos que declaramos en el archivo derby-ds.xml, y las entidades que gestionaremos en esta unidad de persistencia (en nuestro caso solamente Cuenta). Por ltimo, hemos configurado

algunos detalles relativos a la fuente de datos dentro del elemento <properties>: el dialecto que usar el contenedor para construir sentencias SQL adecuadas a nuestra base de datos, y la creacin automtica de los esquemas necesarios en base a los metadatos de nuestras entidades (de esta manera nos evitamos crear manualmente tanto las tablas como sus columnas, incluyendo la definicin del tipo de dato de cada columna, restricciones de cada columna, etc). Ahora ya podemos desplegar la aplicacin 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 para mantener las cosas sencillas, creamos un proyecto Java en Eclipse y le aadimos las librerias del proyecto EJB haciendo click con el botn derecho del ratn en el nombre del proyecto Java y seleccionando: Build Path > Configure Build Path En la ventana que nos aparece, vamos a la pestaa Proyects, hacemos click en el botn Add, seleccionamos el proyecto EJB donde esta nuestro componente SLSB y nuestra entidad, hacemos click en el botn OK, y de nuevo hacemos click en el botn OK. Ahora ya podemos escribir el cliente:
package es.davidmarco.ejb.cliente; import import import import import import java.util.Properties; javax.naming.Context; javax.naming.InitialContext; javax.naming.NamingException; es.davidmarco.ejb.entidad.Cuenta; 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);

} }

En el ejemplo anterior, creamos e inicializamos una cuenta, obtenemos un proxy/vista al componente EJB, e invocamos su mtodo crearCuenta() pasndole como parmetro la cuenta. 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. Podemos comprobarlo ejecutando una sentencia SQL contra la base de datos: para ello, haz click con el botn derecho sobre el nombre del proyecto EJB en la pestaa Proyect Explorer y selecciona: Apache Derby > ij (Interactive SQL) La consola de ij se abrir en la pestaa Console de Eclipse, y desde el prompt de ij nos conectamos a la base de datos mediante el comando: connect 'jdbc:derby://localhost:1527/introduccionEJB;' Cuando la conexin se realice, volveremos a ver el prompt de ij. Ahora ya podemos realizar la 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 de datos relacional (tablas y columnas) de manera transparente para nosotros. Misin cumplida. Si deseramos realizar el proceso inverso (obtener un objeto Java desde la informacin 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);

// // // //

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 otra manera se insertar un nuevo registro con los misma informacin en la base de datos pero con ID con valor 2), y en su lugar hemos llamado al mtodo obtenerCuenta() del SLSB, para as obtener una cuenta desde la base de datos en base a su ID. Si una cuenta con ese ID no existe, obtendremos como respuesta un valor null. Te invito a que, a modo de prctica, elimines de la base de datos la cuenta que hemos creado llamando al mtodo borrarCuenta() del SLSB; para verificarlo, una vez hayas ejecutado esta operacin vuelve a realizar la consulta SQL contra la base de datos a travs de la consola de ij: select * from cuenta; Si has realizado correctamente el ejercicio, la tabla Cuenta no deber mostrar ninguna entidad (salvo que hayas insertado entidades adicionales).

4.7 RESUMEN Como hemos visto en este artculo, podemos realizar persistencia de manera extremadamente sencilla en nuestras aplicaciones EJB. Las entidades son simples POJO's, la configuracin con la base de datos se configura mediante archivos XML, y en nuestros componentes EJB solo tenemos que inyectar una unidad de persistencia y ejecutar sus mtodos. En este punto ya hemos visto todos los componentes EJB (Session Beans, Message-Driven Beans, y Entities). Los dos prximos artculos estarn dedicados a los diversos servicios que ofrece el contenedor, servicios que nuestros componentes pueden usar para realizar taras ms complejas (y que son necesarias en aplicaciones reales).

S-ar putea să vă placă și