Introducción a EJB 3.1 (I)

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

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

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

Con todo esto dicho, ¡comencemos!

1.1 ¿Qué son los Enterprise JavaBeans?

EJB (Enterprise JavaBeans) es un modelo de programación que nos permite construir aplicaciones Java mediante objetos ligeros (como POJO's). Cuando construimos una aplicación, son muchas las responsabilidades que se deben tener en cuenta, como la seguridad, transaccionalidad, concurrencia, etc. El estandar EJB nos permite centrarnos en el código de la lógica de negocio del problema que deseamos solucionar y deja el resto de responsabilidades al contenedor de aplicaciones donde se ejecutará la aplicación.

1.2 El contenedor de aplicaciones

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

Una vez escrita nuestra aplicación EJB, podemos desplegarla en cualquier contenedor compatible con EJB, beneficiandonos de toda el trabajo tras bastidores que el contenedor gestiona por nosotros. De esta manera la lógica de negocio se mantiene independiente de otro código que pueda ser necesario, resultando en código que es más fácil de escribir y mantener (además de ahorrarnos mucho trabajo).

1.3 La especificación EJB 3.1

La especificación 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 construcción de aplicaciones empresariales, entre ellas EJB, JPA, JMS, y JAX-WS. Cada una de ellas se centra en un area específica, resolviendo así problemas concretos. Además, cada API/especificación está preparada para funcionar en compañia de las demás de forma nativa, y por tanto en su conjunto son una solución perfectamente válida para desarrollar una aplicación end-to-end (de principio a fin).

Desde la versión 3.0, EJB no impone ninguna restricción ni obligación a nuestros 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 reutilización de componentes y la tarea de testearlos.

Como se ha dicho, los POJO's son faciles de testear (siempre que estén bien diseñados). Al final de este primer artículo se verá un sencillo ejemplo de programación de un EJB mediante Test-Driven Development (Desarrollo Dirigido por Tests, a partir de ahora TDD). TDD es una metodología de desarrollo en la cual cada bloque de código está respaldado por uno o más 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:

  1. Escribimos un test que define qué queremos hacer
  2. Ejecutamos el test y éste falla (puesto que aún no hay lógica de negocio, o lo que es lo mismo, cómo queremos hacerlo)
  3. Escribimos la lógica de negocio que hace pasar el test (la solución más simple posible)
  4. Mejoramos la lógica de negocio gradualmente, ejecutando el test después de cada mejora para verificar que no hemos roto nada

Escribir el test antes que la lógica de negocio y mantenerlo lo más simple posible nos obliga a escribir código independiente de otro código, con responsabilidades bien definidas (en resumen, buen código). Usado de forma correcta, TDD permite crear sistemas que 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 excepción del citado ejemplo que veremos al final del artículo y que servirá solamente para demostrar que el modelo EJB es un buen modelo de programación), y en el que te animo que profundices si no lo conoces; las ventajas que ofrece para escribir código de calidad son muchas, independientemente del uso de EJB o no.

Por otro lado, el uso de POJO's para encapsular nuestra lógica de negocio nos proporciona un modelo simple que es altamente reutilizable (recuerda que la reutilización de clases es un concepto básico y esencial en programación 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 petición 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 descripción de cada tipo de componente (en capítulos posteriores se explicará cada tipo de componente con más detalle).

1.4 Session Beans

Los componentes Session Beans (Beans de Sesión) son los componentes que contienen la lógica de negocio que requieren los clientes de nuestra aplicación. Son accedidos a través de un proxy (también llamado vista, término 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

Los componentes Stateless Session Beans (Beans de Sesión sin Estado, a partir de ahora SLSB) 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 discrección del contenedor, y puesto que no mantienen estado son muy eficientes a nivel de uso de memoria y recursos en el servidor.

Los componentes Stateful Session Beans (Beans de Sesión con Estado, a partir de ahora SFSB), 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 añadido mientras navegamos por las diferentes páginas), 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 invocación de un cliente, mantenido ligado a ese cliente, y destruido cuando el cliente invoque un método en el SFSB que esté marcado como finalizador (también puede ser destruido por timeout de sesión). Son menos eficientes a nivel de uso de memoria y recursos en el servidor que los SLSB.

Los componentes 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 indiscutíblemente los mejores, aunque su uso está restringido a resolver ciertos problemas muy específicos.

1.5 Message-Driven Beans

Los componentes Message-Driven Beans (Beans Dirigidos por Mensajes, a partir de ahora MDB) son componentes de tipo listener que actuan de forma asíncrona. Su misión es la de consumir mensajes (por ejemplo eventos que se producen en la aplicación), los cuales pueden gestionar directamente o enviar (derivar) a otro componente. Los MDB actuan sobre un proveedor de mensajería, por ejemplo Java Messaging System (JMS es además soportado de forma implícita por la especificacion EJB).

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

1.6 Entity Beans

Los componentes Entity Beans (Beans de Entidad, a partir de ahora EB) son representaciones de información (en forma de POJO's) que es almacenada en una base de datos. El encargado de gestionar los EB es EntityManager, un servicio que es suministrado por el contenedor y que está incluido en la especificación Java Persistence API (JPA - API de Persistencia en Java). JPA es parte de EJB desde la versión 3.0 de esta última. Para saber más sobre JPA puedes visitar el tutorial publicado en esta misma web.

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.7 EJB mediante TDD

Para terminar este primer artículo, dejemos de lado la teoría y escribamos una sencilla aplicación 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 mayoría de los ejemplos, incluido este, funcionarán en un contenedor compatible con EJB 3.0). Por otro lado, todo el código se ajusta al estandard EJB 3.1 (no se usarán extensiones exclusivas de un contenedor concreto). Sin embargo, ten presente que las indicaciones relativas a la creación del proyecto, despliegue, y ejecución de los ejemplos estarán condicionadas por el entorno concreto que se ha puesto en marcha mediante el anexo que acompaña este tutorial; si tu entorno es diferente, ciertas acciones (como los opciones a seleccionar en tu IDE) pueden ser otras.

Como se ha indicado en el punto 1.3, este ejemplo de desarrollará de manera puntual mediante Test-Driven Development. En los próximos artículos solo se mostrará código 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 omitirán en artículos posteriores.

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

File > New > EJB Project 
	

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

Ahora es el momento de crear un test que defina y respalde nuestro primer EJB. Lo primero es crear una carpeta dentro del proyecto donde almacenaremos todos los tests que escribamos. En la pestaña Project Explorer haz click con el botón 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 botón Finish. Si expandes el proyecto EJB (con la flecha negra que hay a la izquierda del nombre) verás 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 botón derecho sobre el directorio de tests y selecciona:

New > Other
	

En la ventana que te aparecerá selecciona:

Java > JUnit > JUnit Test Case
	

En la ventana de creación de un test de JUnit selecciona la opción New JUnit 4 test, introduce el nombre del paquete donde deseas alojar la clase en el campo Package (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 botón Finish; si es el primer tests que escribes para el proyecto (como es nuestro caso) aparecerá una ventana donde Eclipse nos informa que la librería JUnit no está incluida en el classpath. Selecciona la opción Perform the following action: Add JUnit4 library to the build path (Realizar la siguiente acción: añadir la libreria JUnit 4 al path de construcción) y haz click en el botón 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 método que debe ser void, no aceptar parámetros, y estar anotado con la anotación de JUnit @Test) declara las intenciones (el contrato) del código que estamos diseñando: crear un objeto de la clase PrimerEJB con un método 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 código 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 sí (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 físicamente en disco (por motivos de organización 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 métodos en las respectivas clases de negocio que han sido declarados como 'protected'). En la pestaña 'Project Explorer' haz click con el botón derecho en la carpeta 'ejbModule' (la carpeta por defecto que crea Eclipse en un proyecto EJB para 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 botón 'Finish' para crear la clase de negocio y añade la lógica de negocio. TDD nos dice que debemos comenzar con la solución mas simple posible:

package es.davidmarco.ejb.slsb;

public class PrimerEJB {
    public String saluda(String nombre) {
        return null;
    }
} 
	

Si en este momento ejecutamos el test que hemos escrito (haciendo click con el botón derecho sobre el editor donde tenemos el código del test y seleccionando:

Run As > JUnit Test
	

El test debería fallar (verás 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 pestaña de resultados de JUnit, verás un mensaje que, traducido al castellano, 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 función estática AssertEquals de JUnit. Volvamos al editor donde tenemos la clase de negocio y arreglemos el código que está fallando:

package es.davidmarco.ejb.slsb;

public class PrimerEJB {
    public String saluda(String nombre) {
        return "Hola usuario";
    }
}
	

Si ahora ejecutamos el test, veremos en la pestaña de JUnit que la barra es ahora de color verde, lo cual indica que todos los tests se han ejecutado sin fallos (la ventana Failure Trace esta ahora vacía, evidentemente). Ahora decidimos que, cuando un cliente pase un argumento de tipo null a nuestra función, esta deberá devolver un saludo por defecto. Renombremos el nombre del primer test que hemos escrito y escribamos un segundo test que pruebe esta nueva condición (desde ahora y hasta el final de esta sección omitiré en ambas clases las sentencias package e import para mantener el código breve y así resulte más sencillo enfocar la lógica que nos interesa):

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 código antes incluso de escribirlo, como se puede ver en el segundo tests. En él, esperamos recibir como respuesta la cadena de texto Hola desconocido cuando invoquemos el método saluda() con un argumento de tipo null. Y mientras tanto, el primer test (que hemos renombrado para darle más claridad y expresividad a nuestros tests) debe seguir pasando, por supuesto. Ejecutamos de nuevo los tests (Run As > JUnit Test) y el nuevo test que hemos escrito falla (podemos ver en la ventana a la izquierda de la pestaña 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 lógica 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, ¿qué ocurriría si en lugar de la cadena de texto usuario pasamos al método saluda() una cadena de texto distinta?. Añadamos un test que pruebe esta condición:

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 lógica de negocio contiene un bug: siempre que invocamos el método saluda() con un parámetro de tipo String (diferente de null) obtenemos la cadena de texto Hola usuario, ignorando así el parametro que le hemos pasado (y que supuestamente queremos usar). He aquí otra ventaja más que surge del uso de TDD: descubrir bugs lo antes posible. Cuanto más tiempo tardemos en descubrir un bug, más dificil nos resultará encontrarlo y solucionarlo, sin olvidar las posibles molestias que produciremos a los usuarios de nuestra aplicación y el consecuente enfado de nuestro/s cliente/s. Vamos a resolver este último error en nuestra lógica 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 aún nos quedaría la tarea de refactorizar los tres métodos de tests (hay código redundante en todos ellos) y tal vez añadir algún test más (o eliminar...), vamos a dejar las cosas aquí. TDD es un tema demasiado amplio y complejo que está fuera del propósito de este tutorial. Aunque este ejemplo ha sido extremadamente simple/tonto/llamalo-como-quieras, nos ha servido para demostrar lo facil que es diseñar 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 código que escribes muy en serio. Test-Driven Development es una manera muy sencilla y divertida de diseñar software de calidad.

1.8 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 anotación @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 añadir una segunda anotación a nuestro (ahora sí) 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 anotación @Remote permite a nuestro EJB ser invocado remotamente (esto es, desde fuera del contenedor). Si omitimos esta anotación, el EJB sería considerado como Local (concepto que veremos en el próximo artículo) y solo podría ser invocado por otros componentes ejecutandose dentro del mismo contenedor. Nosotros la hemos incluido pues en la próxima sección vamos a escribir un cliente Java externo al contenedor que solicitará a este el componente que estamos diseñado. De manera adicional, todos los 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 a priori pueda parecer una restricción (o una limitación), es necesaria por motivos técnicos que no vienen al caso para que el contenedor pueda construir la vista/proxy que será enviada a los clientes remotos (externos al contenedor). Además, se considera una buena práctica que nuestras clases y métodos de negocio se construyan sobre interfaces: de esta manera los clientes que usan nuestro código trabajan con la interface, ignorando la implementación concreta. De esta manera podemos cambiar dicha implementación en el futuro sin romper el código de nuestros clientes.

Ahora ya podemos desplegar nuestra primera aplicación EJB en el contenedor. En la pestaña 'Project Explorer' haz click con el botón 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 botón Finish. La pestaña Console se volverá activa y en ella veremos multitud de información relativa al proceso de arranque del servidor (puesto que no estaba arrancado). Tras unos momentos (30-40 segundos en mi equipo) el contenedor se habrá levantado, y con él nuestra aplicación 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 válidas 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 sección, veamos un último asunto relativo al despliegue. Una vez que ya tenemos desplegada nuestra aplicación en JBoss, si realizamos un cambio en nuestra lógica de negocio y deseamos volver a desplegar la aplicación en el contenedor, debemos hacerlo desde la pestaña Servers de Eclipse (y no desde la pestaña Project Explorer como hicimos la primera vez que desplegamos la aplicación). Para ello, primero abrimos la pestaña Servers si no es visible en el Workbench de Eclipse:

Window > Show Views > Servers
	

Si miramos a la recién abierta pestaña Servers veremos la instancia de JBoss asociada 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 botón derecho sobre el nombre del proyecto y seleccionamos Full Publish. En unos segundos nuestro proyecto estará re-desplegado (puedes ver el proceso en la pestaña 'Console').

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

1.9 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 sección 1.6 y desplegado en la sección 1.7. Con esto veremos como el contenedor gestiona todo el ciclo de vida de una aplicación EJB así como de sus componentes, mientras los clientes sólo 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 pestaña 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 botón 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 botón derecho sobre el paquete recién 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 método main() automáticamente, y hacemos click en el botón Finish.

Antes de mostrar el código del cliente es preciso mencionar que para ejecutarlo se necesitan ciertas librerias que, por motivos de simplicidad, vamos a obtener del primer proyecto (la aplicación EJB). Para ello, en la pestaña 'Project Explorer' haz click con el botón 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 pestaña Projects, hacemos click en el botón Add, y marcamos la casilla correspondiente al proyecto donde está la aplicación EJB que hemos desplegado (y que, repito, contiene todas las librerias que necesita el cliente). Para finalizar, hacemos click en el botón OK, y de nuevo hacemos click en el botón OK. El código del cliente es el siguiente:

package es.davidmarco.ejb.cliente;

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

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 aplicación, como vimos al final de la sección 1.7). Dentro del método 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 continuación viene lo realmente interesante: no obtenemos un objeto de tipo InterfaceEJB mediante el constructor new (como haríamos en una aplicación Java normal), si no que se lo solicitamos al contenedor EJB a traves del método lookup() del contexto que acabamos de crear. A este método 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 métodos que definimos en su interface asociada (MiInterfaceEJB). Para ejecutar el cliente nada tan sencillo como hacer click con el botón derecho sobre el editor donde lo tenemos y seleccionar:

Run As > Java Application
	

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

Resumen

Este primer artículo del tutorial de introducción a EJB 3.1 ha sido muy fructífero. En el hemos visto de manera superficial los conceptos básicos de la especificación EJB, una breve introducción 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 través del contenedor) desde un cliente Java.

En el próximo artículo 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!.