1. Introducción a los Componentes Enterprise JavaBeans

En este módulo vamos a presentar la tecnología Enterprise JavaBeans. Se trata de una tecnología que permite definir componentes (enterprise beans) que implementan lógica de negocio y que son gestionados por un servidor de aplicaciones Java EE. El servidor de aplicaciones, un entorno en tiempo de ejecución, proporciona un conjunto de servicios, como gestión de transacciones y seguridad, a los beans gestionados.

En el módulo de Componentes Web ya vimos un ejemplo de componentes gestionados por el servidor de aplicaciones en forma de managed beans. En ese caso, el servidor de aplicaciones se encargaba de la creación de instancias y de la inyección de estas instancias en las variables anotadas. Pero el trabajo del servidor de aplicaciones se limitaba al momento de la creación, una vez creada la instancia e inyectada en la variable que la referencia termina su trabajo. E

n el caso de los componentes enterprise el servidor de aplicaciones va a tener un trabajo mucho más intenso, ya que los servicios que proporciona duran todo el ciclo de vida del objeto: creación, invocación de sus métodos y borrado. En concreto, los servicios que proporciona son:

  • Acceso remoto de múltiples clientes

  • Seguridad

  • Transaccionalidad distribuida

  • Acceso a recursos

  • Concurrencia

  • Llamadas asíncronas

Veremos que el servidor de aplicaciones proporcionará estas funcionalidades en cualquier invocación a métodos de componente enterprise. De forma que, por ejemplo, comprobará que el llamador del método tiene permisos de acceso al método o incorporará la llamada al método en una transacción previamente creada. El llamador al bean nunca tendrá acceso directo a su código, todas las invocaciones de sus métodos se harán a través de los servicios proporcionados por el servidor de aplicaciones.

La especificación actual de la tecnología Enterprise JavaBeans es la 3.1, incluida en la plataforma Java EE 6. La JSR de la especificación es la JSR 318, aprobada en diciembre de 2009.

Para más información recomendamos el libro EJB 3 in Action de la editorial Manning.

1.1. Acceso a la capa de negocio gestionado por el servidor de aplicaciones

Las aplicaciones y frameworks que hemos venido estudiando y utilizando hasta ahora se basan en el denominado modelo de N-capas que separa claramente la presentación, la lógica de negocio y el acceso a los datos. Este diseño facilita sobremanera el mantenimiento de la aplicación, ya que la separación en capas nos permite modificar una de las partes de la aplicación sin que el resto se vea afectado por el cambio (o, al menos, viéndose afectado de forma muy localizada).

Definimos los siguientes elementos en esta arquitectura:

  • API REST que recibe peticiones GET, POST, PUT y DELETE sobre recursos y que devuelve objetos JSON. La capa de API REST se encarga de transformar objetos JSON en objetos Java con los que se comunica con las funciones proporcionadas por la capa de servicio que implementa la lógica de negocio

  • Capa de servicios que recibe objetos Java y realiza peticiones transaccionales a la capa de datos, definida por DAOs implementados con JPA

  • Capa de datos que trabaja con JPA y con entidades gestionadas por el entity manager y que implementan actualizaciones y consultas realizadas sobre la base de datos

Toda la aplicación, exceptuando el servidor de base de datos, está implementada por clases Java que se ejecutan en el servidor de aplicaciones, ejecutado a su vez por una única JVM (Máquina Virtual Java). Además de la gestión de los servlets y el API REST, el servidor de aplicaciones da soporte a una serie de servicios adicionales como la gestión de la seguridad en el acceso a los servlets, definición de recursos JNDI o el pooling de conexiones JDBC.

Esta arquitectura puede responder perfectamente a un gran número de peticiones simultáneas de clientes (navegadores que consultan nuestro sistema). El servidor utiliza las características de multiproceso de la JVM para poder responder a todas las peticiones HTTP colocando cada servlet en un hilo de ejecución.

La arquitectura EJB permite añadir un elemento más a esta arquitectura: la intermediación automática de los servicios del servidor de aplicaciones en cualquier llamada a los métodos de negocio definidos en los beans. Veamos algunos de los posibles servicios.

1.1.1. Seguridad en el acceso a la capa de negocio

Si diseñamos aplicaciones con requisitos de seguridad estrictos (como banca, telefonía, etc.) es necesario ser muy cuidadoso en el acceso a las operaciones de negocio. Una operación que, por ejemplo, realice una transferencia bancaria de una cuenta a otra sólo podría ser autorizada si quien la solicita tiene un rol determinado. El servidor de aplicaciones puede obligar a que los accesos a los métodos de negocio sólo se puedan realizar si el llamador tiene los permisos adecuados.

Con la tecnología vista hasta ahora no es posible garantizar esto. El acceso de seguridad lo hemos visto a nivel de aplicación web (servlet), pero podría pasar que nos equivocáramos en el código del servlet o al añadir nuevas funcionalidades y termináramos permitiendo acceder desde capas externas de la aplicación sin los permisos adecuados a métodos de negocio restringidos. Es muy conveniente establecer una barrera de seguridad en el nivel de los métodos de negocio de la capa de servicios.

La definición de restricciones de seguridad en la capa de negocio permite también automatizar su comprobación en tests.

La arquitectura EJB permite dar una solución a esta necesidad, definiendo restricciones de acceso en los enterprise beans. Un método de un enterprise bean sólo puede ser llamado si se tiene un rol determinado. Y además, esta configuración de seguridad se define de forma declarativa, simplificándose mucho su mantenimiento.

1.1.2. Transacciones gestionadas por el contenedor

Otro elemento interesante que nos proporciona la capa de EJB y que no tenemos hasta ahora es la posibilidad de anidar varias operaciones de negocio en una única transacción. En la definición de los métodos de negocio que hemos hecho hasta ahora cada método define una única transacción atómica siguiendo el siguiente patrón:

  • Creamos un entity manager

  • Abrimos una transacción

  • Realizamos operaciones sobre la base de datos a través de los DAO

  • Cerramos la transacción

  • Cerramos el entity manager

Si queremos agrupar dos o más operaciones no hay otra forma de hacerlo que creando un nuevo método de negocio que realice todas las modificaciones sobre la base de datos.

La arquitectura EJB combinada con JPA en el servidor nos va a permitir utilizar transacciones JTA y propagación del contexto de persistencia para poder englobar más de un método de negocio en la misma transacción. Esto nos permitirá crear métodos de negocio mucho más versátiles y combinables entre si.

Además, la arquitectura EJB va a permitir la realización de transacciones distribuidas que incorporen a más de una base de datos. Por ejemplo, pensemos en una agencia de viajes que debe añadir una reserva de un hotel, una reserva de un vuelo y una entrada de un espectáculo. Supongamos que estos datos se encuentran distribuidos y son gestionados por tres bases de datos diferentes. Cada base de datos tiene su sistema de gestión de transacciones. Necesitamos entonces coordinar los sistemas de transacción de las tres bases de datos para englobarlos en una transacción global que los incluya. De esta forma, podremos realizar una operación de reserva conjunta de hotel, vuelo y espectáculo. Esta operación deberá definir una transacción global que fallará si alguna de las bases de datos falla y que hará que todas las bases de datos vuelvan a su estado anterior si esto sucede.

Al hacer que los enterprise beans sean recursos transaccionales se abstrae el uso de los recursos de datos y son los propios métodos de negocio los que se convierten en transaccionales. De esta forma, cada método de negocio de un enterprise bean proporciona un servicio transaccional que puede participar en una transacción de mayor alcance. En el ejemplo anterior de la agencia de viajes, los servicios de añadir una reserva o comprar una entrada pueden ser implementados de forma transaccional como métodos de enterprise beans (cada uno situado además en su servidor de aplicaciones correspondiente y con sus restricciones de acceso). Como ya hemos dicho previamente, esto facilita el desarrollo y (sobre todo) las pruebas del sistema.

1.1.3. Acceso remoto a la capa de negocio

La última funcionalidad avanzada que nos va a permitir la arquitectura EJB es la posibilidad de exportar los métodos de negocio a clientes remotos, situados en otras aplicaciones desplegadas en el servidor o incluso en otros servidores.

En una organización se suele tener un gran número de aplicaciones que proporcionan servicios y que necesitan comunicarse unas con otras. Esta arquitectura se denomina SOA (Service Oriented Arquitecture). Una de las características de SOA es que las aplicaciones de la empresa se ejecutan de forma distribuida en distintos servidores (atendiendo a necesidades de acceso a ciertos recursos o de políticas de seguridad). Estas aplicaciones necesitan comunicarse entre si, accediendo de forma remota a los servicios definidos por cada una de ellas.

Ya veremos que una forma de proporcionar estos servicios remotos es definiendo servicios REST. Pero la arquitectura EJB permite también la posibilidad de convertir en remotas las interfaces de las operaciones de negocio, de forma que desde cualquier servidor de la empresa se puede acceder a los métodos declarados en un componente EJB, utilizando una llamada similar a la de una llamada a un procedimiento remoto, pero de forma totalmente transparente.

La utilización de los componentes EJB como componentes remotos está bastante extendida, pero no lo estudiaremos en profundidad en el curso. Sirven para definir la versión más tradicional de la arquitectura SOA, utilizando elementos como los servicios web SOAP y JAX-WS, los componentes EJB de mensajes o los buses de conexiones. Frente a esta versión tradicional de las arquitecturas distribuidas, el enfoque que presentamos en el curso se base en la utilización de servicios REST ligeros que se comunican utilizando formatos de datos abiertos y flexibles como JSON.

1.2. Un ejemplo sencillo

Vamos a empezar con un sencillo ejemplo en el que comprobaremos cómo definir un componente EJB y cómo usarlo desde otro componente gestionado, un servlet.

El código que define el enterprise bean es muy sencillo: una clase java anotada con @Stateless. Con esa anotación estamos definiendo un bean de sesión sin estado. Comentaremos más adelante las distintas características de cada uno de los tipos de enterprise bean.

import javax.ejb.Stateless;

@Stateless
public class SaludoServicio {

    private String[] misSaludos = {"Hola, que tal?",
            "Cuanto tiempo sin verte", "Que te cuentas?",
            "Me alegro de volver a verte"};

    public String saludo(String nombre) {
        int random = (int) (Math.random() * misSaludos.length);
        String saludo = nombre + ", " + misSaludos[random];
        return saludo;
    }
}

En el bean definimos el método saludo que recibe un nombre y devuelve un saludo aleatorio precedido por ese nombre.

Un ejemplo de la llamada al bean es el siguiente código, un servlet en el que se obtiene una referencia al bean y se invoca a su método saludo():

@WebServlet(name="holamundo", urlPatterns="/holamundo")
public class HolaMundoServlet extends HttpServlet {

    @EJB
    SaludoServicio saludoServicio; (1)

    protected void doGet(HttpServletRequest request,
    	      	   	 HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");

        int errorStatus = 0;
        String errorMsg = "";
        String nombre = request.getParameter("nombre");

        if (nombre == null) {
            errorStatus = HttpServletResponse.SC_BAD_REQUEST;
            errorMsg = "Faltan parámetros en la petición";
            response.setStatus(errorStatus);
            PrintWriter out = response.getWriter();
            out.println(errorMsg);
        } else {
            PrintWriter out = response.getWriter();
            out.println("<!DOCTYPE HTML PUBLIC \"" +
                    "-//W3C//DTD HTML 4.0 " +
                    "Transitional//EN\">");
            out.println("<HTML>");
            out.println("<BODY>");
            out.println("<h1>Stateless</h1>");
            out.println("<ul>");
            out.println("<li>" + saludoServicio.saludo(nombre) + "</li>"); (2)
            out.println("</ul>");
            out.println("</BODY>");
            out.println("</HTML");
        }
    }
}
1 Obtención del bean por inyección de dependencias. El servidor de aplicaciones inyecta en la variable saludoServicio una referencia al bean
2 Invocación al método saludo del bean

Por completitud, la página JSP desde la que se invoca al servlet es la siguiente:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
        <title>Start Page</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <h1>Hello World!</h1>
        <form action="<%=request.getContextPath()%>/holamundo">
        <p>Introduce tu nombre (sin estado): <input type="text" name="nombre"></p>
        <input type="submit" value="Enviar">
        </form>
    </body>
</html>

Al ejecutar el servlet veremos que la llamada al método saludo() del bean devuelve una cadena con uno de los saludos. De esta forma podemos comprobar que la inyección ha funcionado bien.

Pero hasta aquí el funcionamiento es muy parecido al de un managed bean La diferencia es que en este caso el trabajo del servidor de aplicaciones no termina con la inyección, como pasa en el managed bean. En este caso, al ser saludoServicio un enterprise bean el servidor de aplicaciones debe gestionar todas las llamadas a sus métodos, en nuestro caso la invocación al método saludo().

1.2.1. Añadimos restricción de seguridad en el acceso al método

¿Cómo podríamos comprobar que la invocación al método saludo() está gestionada por el servidor de aplicaciones? Podríamos hacerlo de una forma muy sencilla. Hemos dicho que una de las características del servidor de aplicaciones es que permite definir restricciones de seguridad. Vamos a comprobar que añadiendo una sencilla anotación en el método podemos conseguir provocar un error cuando se intenta acceder a él sin tener los permisos necesarios.

    @RolesAllowed({"usuario-saludo"}) (1)
    public String saludo(String nombre) {
        int random = (int) (Math.random() * misSaludos.length);
        String saludo = misSaludos[random];
        return nombre + ", " + saludo;
    }
1 La anotación @RolesAllowed restringe el acceso al método

Si ejecutamos el ejemplo, comprobaremos que la llamada al método desde el servlet provoca una excepción de tipo javax.ejb.EJBAccessException. Realmente a quien estamos invocando cuando ejecutamos el método saludo() es al servidor de aplicaciones, que se encarga de comprobar las restricciones de seguridad y de invocar al bean sólo si se cumplen.

1.3. Arquitectura Enterprise JavaBeans

La arquitectura Enterprise JavaBeans hace posible la creación de clases gestionadas (denominados componentes EJB o enterprise beans) que viven en un contenedor (servidor de aplicaciones) y que proporcionan servicios (métodos) que pueden ser invocados de forma segura, transaccional, remota y concurrente. El contenedor es el encargado de proporcionar los servicios comentados en el apartado anterior (acceso remoto, seguridad y transaccionalidad) así como de gestionar su ciclo de vida.

1.3.1. El enterprise bean vive en un contenedor

Al desplegar el enterprise bean en el servidor de aplicaciones se construye automáticamente un conjunto de clases que se encargarán de realizar la intercepción de las llamadas de los clientes y que son las que realmente inyecta el servidor de aplicaciones en las variables del cliente. El cliente nunca invocará directamente al código del enterprise bean, sino que todas las llamadas serán siempre a través del servidor de aplicaciones.

Cuando programamos un enterprise bean estamos proporcionando su interfaz y su implementación (cuáles son sus métodos de negocio y cómo se implementan), pero dejamos al servidor de aplicaciones la tarea engorrosa de generar las clases de soporte que proporcionan acceso remoto, seguro, concurrente y transaccional.

La intermediación del servidor de aplicaciones en las llamadas de los clientes al bean es un ejemplo más del uso de contenedores en Java EE. El uso de contenedores que gestionan objetos y les proporcionan servicios añadidos es una técnica recurrente en la arquitectura Java EE. Los servlets también son componentes que se despliegan y viven en el contenedor web. Este contenedor da soporte a su ciclo de vida y proporciona un entorno con servicios que éstos pueden invocar. Cuando se recibe una petición HTTP que debe ser respondida por un servlet determinado es el contenedor web el que se encarga de instanciar el servlet (o recuperarlo de un pool) en un thread nuevo y de invocarlo para que responda a la petición.

En la figura anterior vemos los contenedores proporcionados por la arquitectura Java EE. Además de los contenedores de servlets y de enterprise beans, podemos utilizar el contenedor de aplicaciones cliente y el contenedor de applets. Las aplicaciones cliente son aplicaciones de escritorio Java que se encuentran en el servidor de aplicaciones pero que se pueden descargar y ejecutar en máquinas clientes remotas, utilizando la tecnología Java Web Start. El contenedor de aplicaciones cliente proporciona una serie de servicios, como el acceso al servicio JNDI del servidor de aplicaciones, que pueden ser utilizados desde las máquinas cliente. Los applets, por último, como es bien conocido, son aplicaciones Java que se ejecutan en los clientes web.

Cuando se está trabajando con componentes se tiene que dedicar tanta atención al despliegue (deployment) del componente como a su desarrollo. Entendemos por despliegue la incorporación del componente a nuestro contenedor EJB y a nuestro entorno de trabajo (bases de datos, arquitectura de la aplicación, etc.). El despliegue se define de forma declarativa. En la versión 2.1 de la especificación EJB, el despliegue se definía mediante un fichero XML (descriptor del despliegue, deployment descriptor) en el que se definen todas las características del enterprise bean. Desde la versión 3.0 de la especificación es posible definir la configuración de los componentes EJB mediante anotaciones en las clases Java que los implementan.

1.3.2. Servicios proporcionados por el contenedor EJB

En el apartado anterior hemos comentado que la diferencia fundamental entre los componentes y los objetos clásicos reside en que los componentes viven en un contenedor EJB que los envuelve proporcionando una capa de servicios añadidos. ¿Cuáles son estos servicios? Los más importantes son los siguientes:

Manejo de transacciones

Apertura y cierre de transacciones asociadas a las llamadas a los métodos del enterprise bean.

Seguridad

Comprobación de permisos de acceso a los métodos del enterprise bean.

Concurrencia

Llamada simultánea a un mismo bean desde múltiples clientes.

Servicios de red

Comunicación entre el cliente y el bean en máquinas distintas.

Gestión de recursos

Gestión automática de múltiples recursos, como colas de mensajes, bases de datos o fuentes de datos en aplicaciones heredadas que no han sido traducidas a nuevos lenguajes/entornos y siguen usándose en la empresa.

Persistencia

Sincronización entre los datos del bean y tablas de una base de datos.

Gestión de mensajes

Manejo de Java Message Service (JMS).

Escalabilidad

Posibilidad de constituir clusters de servidores de aplicaciones con múltiples hosts para poder dar respuesta a aumentos repentinos de carga de la aplicación con sólo añadir hosts adicionales.

Adaptación en tiempo de despliegue

posibilidad de modificación de todas estas características en el momento del despliegue del enterprise bean.

Pensemos en lo complicado que sería programar una clase "a mano" que implementara todas estas características. Como se suele decir, la programación de EJB es sencilla si la comparamos con lo que habría que implementar de hacerlo todo por uno mismo. Evidentemente, si la aplicación que se está desarrollando no necesita estos servicios, se podría utilizar simplemente páginas JSP y JDBC.

1.4. Tipos de enterprise beans

A partir de la especificación EJB 3.0 se definen principalmente dos tipos de enterprise beans:

  • Beans de sesión (con estado, sin estado y singleton)

  • Beans dirigidos por mensajes dirigidos por mensajes (message-driven beans, MDBs)

Un bean de sesión representa un componente que encapsula un conjunto de métodos o acciones de negocio que pueden ser llamados de forma síncrona. Ejemplos de bean de sesión podrían ser un sistema de peticiones de reserva de libros de una biblioteca o un carrito de la compra. En general, cualquier componente que ofrezca un conjunto de servicios (métodos) a los que se necesite acceder de forma distribuida, segura y transaccional.

Los beans dirigidos por mensajes se diferencian de los anteriores en que la comunicación con ellos no es por medio de invocaciones, sino por el envío de mensajes que se encolan y se procesan de forma asíncrona. Un ejemplo podría ser un MDB ListenerNuevoCliente que se activara cada vez que se envía un mensaje comunicando que se ha dado de alta a un nuevo cliente.

En el curso vamos a ver en profundidad únicamente el primer tipo de beans.

1.4.1. Beans de sesión

Los beans de sesión ofrecen métodos de negocio que pueden ser utilizados de forma transaccional, remota y segura.

Existen tres tipos de beans de sesión: con estado, sin estado y singleton.

Los beans de sesión sin estado responden a peticiones de los clientes y no guardan ningún estado entre una petición y la siguiente. Los clientes del bean realizan una petición a uno de sus métodos. El bean responde a cada petición de forma síncrona (la petición no devuelve el control hasta que el bean no termina de procesarla), el cliente obtiene la respuesta y sigue realizando su procesamiento. Cada petición es independiente. No se establece ningún tipo de sesión entre el cliente y el bean que haga necesario guardar un estado.

Los beans de sesión con estado contienen variables de instancia, campos, en los que se guarda su estado. Los clientes remotos establecen una sesión con ellos y pueden ir modificando sus datos. El estado se mantiene entre una petición y otra.

Los beans de sesión singleton se instancian una vez por aplicación y existen durante el ciclo de vida de la aplicación. Los beans de sesión singleton están pensados para circunstancias en las que un hay que compartir una única instancia de enterprise bean entre todos los clientes concurrentes.

Beans de sesión sin estado

Los beans de sesión sin estado no se modifican con las llamadas de los clientes. Los métodos que ponen a disposición de las aplicaciones clientes son llamadas que reciben datos y devuelven resultados, pero que no modifican internamente el estado del bean. Esta propiedad permite que el contenedor EJB pueda crear una reserva (pool) de instancias, todas ellas del mismo bean de sesión sin estado y asignar cualquier instancia a cualquier llamada de un cliente. Incluso un único bean puede estar asignado a múltiples clientes, ya que la asignación sólo dura el tiempo de invocación del método solicitado por el cliente. Y al revés, si hacemos distintas llamadas desde un cliente cada vez puede ser que nos responda una instancia distinta del bean.

Cuando un cliente invoca un método de un bean de sesión sin estado, el contenedor EJB obtiene una instancia de la reserva. Cualquier instancia servirá, ya que el bean no guarda ninguna información referida al cliente. Tan pronto como el método termina su ejecución, la instancia del bean está disponible para otros clientes. Esta propiedad hace que los beans de sesión sin estado sean muy escalables para un gran número de clientes. El contenedor EJB no tiene que mover sesiones de la memoria a un almacenamiento secundario para liberar recursos, simplemente puede obtener recursos y memoria destruyendo las instancias.

Podemos comprobar que esto es así realizando una pequeña modificación a nuestro bean de ejemplo. Podemos devolver junto con el saludo una referencia de la instancia real que ejecuta el método:

...
public String saludo(String nombre) {
    int random = (int) (Math.random() * misSaludos.length);
    String saludo = nombre + ", " + misSaludos[random] + " [" + this.toString() + "]"; (1)
    return saludo;
}
...
1 Añadimos en el saludo la llamada a this.toString() que devuelve el identificador de la instancia del bean

Y modificamos el servlet para que realice dos llamadas al método:

...
out.println("<h1>Stateless</h1>");
out.println("<ul>");
out.println("<li>" + saludoServicio.saludo(nombre) + "</li>");
out.println("<li>" + saludoServicio.saludo(nombre) + "</li>");
out.println("</ul>");
...

Podemos comprobar en la página devuelta por el servlet que están respondiendo instancias distintas:

Los beans de sesión sin estado se usan en general para encapsular procesos de negocio. Estos beans suelen recibir nombres como ServicioBroker o GestorContratos para dejar claro que proporcionan un conjunto de procesos relacionados con un dominio específico del negocio. A veces se les suele denominar Business Objects, y también usar el sufijo BO para su nombre: GestorContratosBO.

También puede usarse un bean de sesión sin estado como un puente de acceso a una base de datos o a un conjunto de entidades JPA. En una arquitectura cliente-servidor, el bean de sesión podría proporcionar al interfaz de usuario del cliente los datos necesarios, así como modificar objetos de negocio (base de datos o entidades JPA) a petición de la interfaz. Este uso de los beans de sesión sin estado es muy frecuente y constituye el denominado patrón de diseño session facade.

Algunos ejemplos de bean de sesión sin estado podrían ser:

  • Un componente que comprueba si un símbolo de compañía está disponible en el mercado de valores y devuelve la última cotización registrada.

  • Un componente que calcula la cuota del seguro de un cliente, basándose en los datos que se le pasa del cliente.

Beans de sesión con estado

En un bean de sesión con estado, el bean almacenan datos específicos obtenidos durante la conexión con el cliente en sus campos (variables de instancia). Cada bean de sesión con estado, por tanto, almacena el estado conversacional de un cliente que interactúa con el bean. Este estado conversacional se modifica conforme el cliente va realizando llamadas a los métodos de negocio del bean. El estado conversacional no se guarda cuando el cliente termina la sesión.

El hecho de que se establezca un estado entre el bean y el cliente obliga a que, una vez establecida la conexión, siempre sea el mismo bean el que atienda al cliente. El servidor de aplicaciones ya no puede hacer lo de utilizar una instancia distinta en cada llamada. Esto hace que los beans con estado sean menos escalables que los beans sin estado, ya que es necesario mantener un bean activo para cada conexión establecida.

Al igual que en los beans sin estado, la interacción del cliente con el bean se realiza mediante llamadas a sus métodos de negocio.

Los beans con estado son apropiados para aquellos casos en los que necesitamos guardar en memoria un estado que vamos a ir modificando llamada a llamada. Su uso evita tener que almacenar los estados intermedios en una base de datos.

  • Un ejemplo típico es un carrito de la compra, en donde el cliente va guardando uno a uno los ítem que va comprando.

  • Un enterprise bean en una biblioteca que tiene métodos para ir reservando libros y realizar un préstamo conjunto de todos los libros reservados.

Para definir un bean con estado basta con utilizar la anotación Stateful:

@Stateful
public class SaludoConEstadoServicio {
    ArrayList<String> saludos = new ArrayList<String>(); (1)

    @EJB
    SaludoServicio saludoServicio; (2)

    public String saludo(String nombre) {
        String saludo = saludoServicio.saludo(nombre); (3)
        saludos.add(saludo);
        return saludo;
    }

    public ArrayList<String> saludos() {
        return saludos;
    }
}
1 Estado local definido por un ArrayList donde se guardan saludos
2 Se obtiene una referencia al bean SaludoServicio para obtener el saludo
3 Se obtiene un saludo invocando a SaludoServicio y se guarda en el array de saludos

En el ejemplo definimos el enterprise bean SaludoConEstadoServicio que guarda los saludos en un array local antes de devolverlo. También tiene un método saludos() que devuelve el array de todos los saludos emitidos.

Beans de sesión singleton

Un bean de sesión singleton es instanciado una vez por aplicación y proporciona un acceso fácil a estado compartido. Si el contenedor está distribuido en muchas máquinas, cada aplicación tendrá una única instancia del singleton en cada JVM.

Múltiples clientes van a acceder de forma concurrente a la misma instancia del bean:

Para definir un bean de sesión singleton se debe utilizar la anotación @Singleton:

@Startup
@Singleton
public class SaludoSingletonServicio {
    ArrayList<String> saludos = new ArrayList<String>();

    @EJB
    SaludoServicio saludoServicio;

    @Lock(LockType.WRITE)
    public String saludo(String nombre) {
        String saludo = saludoServicio.saludo(nombre);
        saludos.add(saludo);
        return saludo;
    }

    @Lock(LockType.READ)
    public ArrayList<String> saludos() {
        return saludos;
    }
}

La anotación @Lock(LockType.READ) permite accesos concurrentes a los métodos. La anotación @Lock(LockType.WRITE) obliga a un acceso secuencial al método.

El contenedor decide cuándo inicializar la instancia del singleton. Es posible obligar a que la inicialización se realice al arrancar la aplicación usando la anotación @Startup. De esta forma, el contenedor inicializa todos los singleton de arranque y ejecuta sus métodos marcados con @PostConstruct antes de que la aplicación esté disponible y de servir las peticiones de los clientes.

Es posible determinar el orden en el que el contenedor debe inicializar los beans singleton utilizando la anotación @DependsOn:

@Singleton
public class Foo {
	//...
}

@DependsOn("Foo")
@Singleton
public class Bar {
	//...
}

El contenedor se asegura de que el bean Foo se ha inicializado antes de inicializar el bean Bar.

1.4.2. Beans dirigidos por mensajes

Son el segundo tipo de beans propuestos por la especificación de la arquitectura EJB. Estos beans permiten que las aplicaciones J2EE reciban mensajes JMS de forma asíncrona. Así, el hilo de ejecución de un cliente no se bloquea cuando está esperando que se complete algún método de negocio de otro enterprise bean. Los mensajes pueden enviarse desde cualquier componente J2EE (una aplicación cliente, otro enterprise bean, o un componente Web) o por una aplicación o sistema JMS que no use la tecnología J2EE.

La diferencia más visible con los beans de sesión es que los clientes no acceden a los beans dirigidos por mensajes mediante interfaces (explicaremos esto con más detalle más adelante), sino que un bean dirigido por mensajes sólo tienen una clase de implementación.

En muchos aspectos, un bean dirigido por mensajes es parecido a un bean de sesión sin estado.

  • Las instancias de un bean dirigido por mensajes no almacenan ningún estado conversacional ni datos de clientes.

  • Todas las instancias de los beans dirigidos por mensajes son equivalentes, lo que permite al contenedor EJB asignar un mensaje a cualquier instancia. El contenedor puede almacenar estas instancias para permitir que los streams de mensajes sean procesados de forma concurrente.

  • Un único bean dirigido por mensajes puede procesar mensajes de múltiples clientes.

Las variables de instancia de estos beans pueden contener algún estado referido al manejo de los mensajes de los clientes. Por ejemplo, pueden contener una conexión JMS, una conexión de base de datos o una referencia a un objeto enterprise bean.

Cuando llega un mensaje, el contenedor llama al método onMessage del bean. El método onMessage suele realizar un casting del mensaje a uno de los cinco tipos de mensajes de JMS y manejarlo de forma acorde con la lógica de negocio de la aplicación. El método onMessage puede llamar a métodos auxiliares, o puede invocar a un bean de sesión o de entidad para procesar la información del mensaje o para almacenarlo en una base de datos.

Un mensaje puede enviarse a un bean dirigido por mensajes dentro de un contexto de transacción, por lo que todas las operaciones dentro del método onMessage son parten de un única transacción.

1.5. Acceso al enterprise bean

Existen dos formas de obtener una referencia a un bean: usando inyección de dependencias o nombres JNDI. En ningún caso se puede hacer un new de un enterprise bean, siempre es el servidor de aplicaciones el que realiza su construcción y devuelve el objeto con el que interactúa el cliente.

1.5.1. Acceso por inyección de dependencias

La primera forma de obtener una referencia a un enterprise bean es usando inyección de dependencias. Debemos usar la anotación @EJB en una variable para indicar que el servidor debe inyectar un enterprise bean del tipo definido por el tipo de la variable:

@EJB
SaludoServicio saludoServicio;

Podemos realizar inyección de dependencias en componentes gestionados desplegados en la misma aplicación en la que se define el enterprise bean: servlets y otros enterprise beans.

El servidor inyectará en la variable un bean del tipo definido en el momento de creación del componente, inyectando la referencia en la instancia recién creada. Es importante por ello conocer el ciclo de vida del componente.

Por ejemplo, en el caso de un servlet, la creación del servlet sólo se hace una vez, en el momento de la puesta en marcha de la aplicación. Recordemos que las peticiones al servlets se implementan como hilos de ejecución concurrente y que las variables definidas en el servlet son compartidas por todos los hilos.

En un servlet podemos inyectar un enterprise bean de sesión sin estado o un bean singleton. No podemos inyectar un bean de sesión con estado, porque el estado sería compartido por todas las peticiones. Si queremos que cada petición utilice un bean de sesión con estado debemos definir la variable que referencia el bean en el método deGet y obtener la referencia al bean accediendo a su nombre JNDI.

1.5.2. Acceso por nombre JNDI

Se definen tres espacios de nombres JNDI para identificar los componentes enterprise y acceder a ellos utilizando JNDI: java:global, java:module y java:app.

El espacio de nombres java:global permite acceder a enterprise beans remotos y su sintaxis es la siguiente:

java:global[/nombre aplicación]/nombre módulo/nombre enterprise bean

El nombre de la aplicación se requiere si el enterprise bean está empaquetado en un EAR. No es nuestro caso, porque estamos empaquetando los beans en WARs.

El espacio de nombres java:module se utiliza para acceder a beans local dentro del mismo módulo. Por ejemplo para acceder desde un servlet a un bean desplegado en el mismo WAR. Su sintaxis es la siguiente:

java:module/nombre enterprise bean

El espacio de nombres java:app se utiliza para acceder a enterprise beans en otra aplicación dentro del mismo servidor. Su sintaxis es

java:app[/nombre módulo]/nombre enterprise bean

Por ejemplo, si el enterprise bean MiBean se empaqueta dentro del archivo de aplicación web miApp.war, el nombre del módulo es miApp. Sus nombres JNDI son java:module/MiBean, java:app/miApp/MiBean y java:global/miApp/MiBean.

Cuando se despliega la aplicación el servidor WildFly muestra por la salida estándar los distintos nombres JNDI de los beans desplegados. Por ejemplo, suponiendo que hayamos definido un enterprise bean SaludoConEstadoServicio en el WAR saludo.war:

java:global/saludo/SaludoConEstadoServicio
java:app/saludo/SaludoConEstadoServicio
java:module/SaludoConEstadoServicio

Desde cualquier componente gestionado podemos obtener una referencia a un enterprise bean llamando al método lookup de un objeto InitialContext. Hay que pasar el nombre JNDI del bean que queremos obtener. Hay que capturar la excepción checked que puede lanzar el método.

Por ejemplo, si queremos acceder al bean SaludoConEstadoServicio desde un servlet podríamos hacerlo de la siguiente forma:

@WebServlet(name="holamundoestado", urlPatterns="/holamundoestado")
public class HolaMundoConEstadoServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request,
    	                  HttpServletResponse response)
                          throws ServletException, IOException { }

    protected void doGet(HttpServletRequest request,
    	      	   	 HttpServletResponse response)
                         throws ServletException, IOException {

        response.setContentType("text/html");

        int errorStatus = 0;
        String errorMsg = "";
        String nombre = request.getParameter("nombre");

        if (nombre == null) {
            errorStatus = HttpServletResponse.SC_BAD_REQUEST;
            errorMsg = "Faltan parámetros en la petición";
            response.setStatus(errorStatus);
            PrintWriter out = response.getWriter();
            out.println(errorMsg);
        } else {
            try {
                SaludoConEstadoServicio saludoServicio = (SaludoConEstadoServicio)
                        new InitialContext().lookup("java:module/SaludoConEstadoServicio"); (1)

                PrintWriter out = response.getWriter();
                out.println("<!DOCTYPE HTML PUBLIC \"" +
                        "-//W3C//DTD HTML 4.0 " +
                        "Transitional//EN\">");
                out.println("<HTML>");
                out.println("<BODY>");
                out.println("<h1>Stateful</h1>");
                out.println("<p>Saludos: </p>");
                out.println("<ul>");
                out.println("<li>" + saludoServicio.saludo(nombre) + "</li>"); (2)
                out.println("<li>" + saludoServicio.saludo("Juan") + "</li>");
                out.println("<li>" + saludoServicio.saludo("Isabel") + "</li>");
                out.println("<li>" + saludoServicio.saludo("Antonio") + "</li>");
                out.println("</ul>");
                out.println("<p> Recuperamos la lista de saludos guardada: </p>");
                ArrayList<String> saludos = saludoServicio.saludos(); (3)
                out.println("<ul>");
                for (String str : saludos) {
                    out.println("<li>" + str + "</li>");
                }
                out.println("<ul>");
                out.println("</BODY>");
                out.println("</HTML");

            } catch (NamingException e) {
                e.printStackTrace();
            }
        }
    }
1 Obtención de la referencia al bean mediante su nombre JNDI
2 Cambiamos el estado del bean, enviándole distintos nombres
3 Recuperamos todos los saludos almacenados

1.6. Pruebas de enterprise beans con Arquillian

El framework de JBoss Arquillian permite realizar testing de componentes desplegándolos en el servidor de aplicaciones y lanzando los tests, que se ejecutan como si se invocaran dentro del servidor.

Por ejemplo, el siguiente código prueba el método saludo del bean SaludoServicio:

package org.expertojava.ejb;

import static org.junit.Assert.assertTrue;

@RunWith(Arquillian.class)
public class SaludoServicioTest {

    @EJB
    private SaludoServicio saludoServicio;

    @Deployment
    public static Archive<?> deployment() {
        return ShrinkWrap.create(JavaArchive.class)
                .addClass(SaludoServicio.class);
    }

    @Test
    public void deberiaDevolverUnoDeLosSaludos() {
        String[] misSaludos = {"Hola, que tal?",
                "Cuanto tiempo sin verte", "Que te cuentas?",
                "Me alegro de volver a verte"};
        String nombre = "Pepito";
        String saludo = saludoServicio.saludo(nombre);
        assertTrue(saludo.startsWith(nombre + ", " + misSaludos[0]) ||
                   saludo.startsWith(nombre + ", " + misSaludos[1]) ||
                   saludo.startsWith(nombre + ", " + misSaludos[2]) ||
                   saludo.startsWith(nombre + ", " + misSaludos[3]));
    }
}

Podemos utilizar Arquillian de dos formas: con un servidor de aplicaciones gestionado o remoto. En el primer caso Arquillian se encarga de poner en marcha el servidor de aplicaciones, desplegar los componentes y los tests, lanzarlos y cerrar el servidor de aplicaciones. En el segundo caso debemos tener un servidor de aplicaciones en marcha y Arquillian se comunica con él para desplegar los componentes y los tests. Esta forma es más eficiente.

Asegúrate que WildFly esté funcionando antes de lanzar los tests de Arquillian. La configuración de Arquillian definida en el POM es la de acceso remoto al servidor de aplicaciones.

Se pueden comprobar las dependencias necesarias en el siguiente POM completo:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.expertojava.ejb</groupId>
    <artifactId>saludo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>saludo</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.1.10.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.wildfly</groupId>
            <artifactId>wildfly-arquillian-container-remote</artifactId>
            <version>8.1.0.Final</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.name}</finalName>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>1.0.2.Final</version>
                <configuration>
                    <hostname>localhost</hostname>
                    <port>9990</port>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

1.7. Ejercicios

1.7.1. (1,5 puntos) Construir el módulo saludo

En este ejercicio deberás construir el módulo saludo con los ejemplos vistos en la teoría de todos los tipos de enterprise beans:

  • SaludoServicio: bean de sesión sin estado

  • SaludoRestringidoServicio: bean de sesión sin estado con autorización

  • SaludoConEstadoServicio: bean de sesión con estado

  • SaludoSingletonServicio: bean de sesión singleton

Empezamos por crear el proyecto ejercicios-ejb-expertojava:

  1. Haz un fork del proyecto vacío IntelliJ ejercicios-ejb-expertojava en la cuenta de java_ua que contiene un repositorio git inicial en el que iremos añadiendo los módulos IntelliJ que vamos a crear como ejercicios de

  2. Descárgalo en tu máquina con el comando git clone. Tendrás un directorio ejercicios-ejb-expertojava que contiene un fichero .idea que podrás abrir con IntelliJ

    $ git clone https://<usuario>@bitbucket.org/java_ua/ejercicios-ejb-expertojava.git

Creamos dentro del proyecto el módulo IntelliJ saludo con un proyecto web inicial en el que vamos a añadir los distintos enterprise beans y los servlets que los usan. Podemos hacerlo creando un nuevo módulo Maven en el proyecto o a partir de la plantilla webapp-expertojava que tenemos en Bitbucket. A continuación se indican los pasos para hacerlo de la segunda forma:

  1. Descarga en el directorio recién creado la plantilla webapp-expertojava, cambia su nombre a saludo y borra los datos de git (va a estar controlado por el proyecto git en el directorio padre):

    $ cd ejercicios-ejb-expertojava
    $ git clone https://<usuario>@bitbucket.org/java_ua/webapp-expertojava.git
    $ mv webapp-expertojava saludo
    $ cd saludo
    $ rm -rf .git
    $ rm -f .gitignore
  2. Edita el fichero pom.xml para actualizar las coordenadas del proyecto Maven, modificando los atributos como sigue:

    <groupId>org.expertojava.ejb</groupId>
    <artifactId>saludo<artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    
    <name>saludo</name>
  3. Por último, abre en IntelliJ el proyecto del directorio padre ejercicios-ejb-expertojava y procede a importar el módulo saludo como un módulo Maven. Para ello, escogemos la opción Import Module desde el panel de Project Sctructure o New > Import Module > Import module from externa model > Maven. Una vez importado el módulo podemos añadir una configuración de despliegue/ejecución y nos aseguramos de que la aplicación funciona correctamente.

  4. Añade los cambios al control de versiones, abriendo el panel Changes y pulsando el botón derecho sobre Unversioned Files y seleccionando Add to VCS.

Una vez tengas el abierto en Intellij el proyecto con el primer submódulo, y tengas correctamente configurado git, debes añadir todos los distintos tipos de componentes enterprise que hemos visto en el tema, junto con servlets en los que se invocan.

El fichero index.jsp contiene una sencilla página HTML para invocar a los distintos servlets:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
        <title>Start Page</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <h1>Hello World!</h1>
        <form action="<%=request.getContextPath()%>/holamundo">
        <p>Introduce tu nombre (sin estado): <input type="text" name="nombre"></p>
        <input type="submit" value="Enviar">
        </form>
        <form action="<%=request.getContextPath()%>/holamundoestado">
            <p>Introduce tu nombre (con estado): <input type="text" name="nombre"></p>
            <input type="submit" value="Enviar">
        </form>
        <form action="<%=request.getContextPath()%>/holamundosingleton">
            <p>Introduce tu nombre (singleton): <input type="text" name="nombre"></p>
            <input type="submit" value="Enviar">
        </form>
        <form action="<%=request.getContextPath()%>/holamundorestringido">
            <p>Introduce tu nombre (sin estado, restringido): <input type="text" name="nombre"></p>
            <input type="submit" value="Enviar">
        </form>
    </body>
</html>

Por último, añade la clase de test SaludoServicioTest con la prueba Arquillian que hemos visto en teoría que comprueba el funcionamiento del bean de sesión sin estado. Deberás añadir las dependencias necesarias en el pom.xml.

1.7.2. (2 puntos) Construir el módulo calculadora

  1. Crea dentro de ejercicios-ejb-expertojava un nuevo módulo IntelliJ llamado calculadora con las siguientes características

    • Módulo Maven de tipo WAR con el groupId org.expertojava.ejb y el artifactID calculadora

    • Contiene un enterprise bean de sesión sin estado org.expertojava.ejb.Calculadora con los siguientes métodos:

      • public double mult(double num1, double num2)

      • public double div(double num1, double num2)

  2. Escribe la página index.jsp y un servlet para comprobar su funcionamiento.

  3. Escribe un fichero de test de Arquillian con dos tests que comprueben el funcionamiento de ambos métodos.