Enterprise Java con J2EE: EJBs, Servlets, JSP y otras Tecnologías de Servidor (I)

por Pedro Agulló Soliveres

Introducción

En los últimos tiempos Java se ha convertido probablemente en el lenguaje más popular para la creación de aplicaciones de servidor. De hecho, Sun ha definido una plataforma específica para el desarrollo de aplicaciones distribuidas, J2EE (Java 2 Enterprise Edition), que proporciona todo lo necesario para la creación de aplicaciones de gran complejidad destinadas al ámbito corporativo.

J2EE no es solo un conjunto de APIs para el desarrollo de aplicaciones distribuidas, sino también una infraestructura en tiempo de ejecución dentro de la cuál se ejecutan dichas aplicaciones: esta infraestructura la proporciona un tipo especial de aplicaciones, llamadas Servidores de Aplicación (Application Servers), tales como WebSphere de IBM, Inprise Application Server de Borland, etc., que estudiaremos más adelante.

Estructura de aplicaciones distribuidas

Las aplicaciones distribuidas suelen tener una estructura en la que se pueden distinguir esencialmente tres capas:

·         El interface de usuario: normalmente corre en PCs o terminales individuales y es típicamente proporcionado por una aplicación independiente. Una alternativa muy común es el uso de un navegador Internet (browser). Esta es la estrategia de Amazon, por ejemplo: el interface de usuario a través del cual escogemos libros u otros productos e introducimos la información necesaria para poder recibirlos es una serie de páginas HTML, generadas dinámicamente y enviadas a nuestro navegador.

·         La lógica del negocio, que encapsula todo lo referente a cómo trabajar con Clientes, Cuentas, Proveedores, etc. Típicamente correrá en otra máquina remota, y es muy posible que de hecho esta capa esté repartida entre varias máquinas.

·         La capa de almacenamiento de datos, posiblemente implementada en otras máquinas, típicamente mediante un Gestor de Base de Datos, como Oracle, DB2 u otro.

Nótese que estas capas pueden disponerse físicamente de muchas maneras, siendo incluso posible (aunque poco común), que todas las capas se ejecuten en la misma máquina, o incluso que una capa esté distribuida en varias máquinas: por ejemplo, como parte de la capa de almacenamiento de datos podemos tener una base de datos técnica en Oracle corriendo en una estación de trabajo HP, mientras que la base de datos de facturación puede correr bajo AS/400. La Figura 1 muestra un sistema en el que varias máquinas cliente están accediendo a información de estas dos bases de datos.

Figura 1. Aplicación distribuida en que varias aplicaciones cliente acceden a la lógica del negocio, ubicada en el Servidor1, que a su vez accede a los datos en los servidores 2 y 3.

Enterprise JavaBeans

Uno de los principales componentes de J2EE es la tecnología de Enterprise JavaBeans, que proporciona un modo estándar de desarrollo de componentes de servidor que encapsulen la funcionalidad del negocio mediante clases tales como Cliente, Artículo, Factura, etc. Para implementar estos objetos deberemos derivar y extender una serie de clases e interfaces estándar definidos en el estándar.

Nos referiremos a los objetos que implementan la lógica del negocio como Enterprise JavaBeans (EJBs) o simplemente Enterprise Beans. Por ahora, bastará con saber que en general la funcionalidad en el servidor se implementará mediante estos Enterprise Beans, los cuáles se ejecutarán dentro de la máquina virtual proporcionada por un servidor de aplicaciones: más adelante se abordará la implementación y funcionamiento de los mismos.

El esquema propuesto por J2EE para aplicaciones distribuidas es el de la Figura 2: el servidor de aplicaciones proporciona una gran cantidad de servicios, como acceso a bases de datos, servidores de correo, etc. Para implementar la funcionalidad del negocio simplemente se crearán una serie de Enterprise Beans (en rojo en la figura), que serán cargados por el servidor de aplicaciones y podrán acceder a los servicios proporcionados por el mismo, incluyendo acceso a bases de datos, etc.

Por otra parte, cualquier aplicación que quiera acceder a la funcionalidad del negocio podrá acceder transparentemente a los métodos de los beans, sin ni tan siquiera advertir que estos se ejecutan dentro del servidor de aplicación. Como veremos más adelante, será posible acceder a esta funcionalidad no solo desde aplicaciones Java, sino desde cualquier aplicación CORBA, por lo que la solución proporcionada por J2EE será universalmente accesible. De hecho, como también veremos más adelante, J2EE va más allá, y proporciona soporte para el acceso a la funcionalidad del negocio desde páginas HTML (Figura 3).

Figura 2. Interrelación entre aplicaciones cliente (en verde), código de negocio (en rojo), servidor de aplicación y otros elementos de una aplicación distribuida basada en J2EE.

Servicios proporcionados por la plataforma J2EE

Una aplicación distribuida de cierto tamaño, además de dar respuesta a las necesidades concretas para la que ha sido diseñada, necesitará enfrentarse a y resolver toda una serie de cuestiones técnicas que contribuyen significativamente a aumentar la dificultad del desarrollo: ente estas están el soporte para distribución de objetos, la necesidad de guardar y recuperar objetos o persistencia (típicamente utilizando una Base de Datos), el soporte para concurrencia y seguridad, el soporte para transacciones, y el soporte para poder encontrar objetos o recursos distribuidos (p.ej., para encontrar una impresora color, o para poder encontrar a los Clientes, incluso aunque se cambie el servidor donde residen).

Soporte de Distribución

Evidentemente, cualquier sistema distribuido requerirá algún tipo de mecanismo para llevar a cabo la comunicación entre objetos en distintas máquinas. Dentro de la arquitectura J2EE el hecho de que los objetos sean distribuidos resulta bastante transparente, y el código fuente raramente tendrá que tener en cuenta esto: si el programador hubiese de codificar el mecanismo de distribución, esto haría el desarrollo prácticamente imposible.

Dentro de J2EE el soporte para la distribución se basa en la API RMI de Java (Remote Method Invocation), implementada sobre IIOP (RMI-IIOP). IIOP (Internet Inter-ORB Protocol) es un protocolo que permite conseguir interoperabilidad entre productos CORBA: esto permite que los EJBs sean accesibles desde cualquier otro componente CORBA, implementado en C++ o cualquier otro lenguaje, lo que hace a nuestra lógica de negocio universalmente accesible, en lugar de quedar limitada a aplicaciones Java.

El hecho de utilizar RMI-IIOP puede permitir también que sea posible acceder desde cualquiera de nuestros Enterprise Beans a productos CORBA: sin embargo, puede darse el caso de que esto no sea así, y Sun recomienda usar JavaIDL para este propósito, también soportado por la plataforma J2EE.

Persistencia

Un elemento importante de cualquier sistema informático es el almacenamiento y recuperación de la información: la información sobre clientes, proveedores, productos, facturas y muchos otros objetos debe poder guardarse y recuperarse según convenga, y por tanto uno de los elementos importantes dentro de la plataforma J2EE es el soporte para guardar y recuperar objetos (a la capacidad de un objeto para guardarse/leerse de un dispositivo de almacenamiento se le llama persistencia).

El soporte de persistencia dentro de J2EE se puede llevar a cabo de varios modos. En primer lugar, algunos servidores de aplicación proporcionan soporte de modo más o menos automático: por ejemplo, algunos proporcionan herramientas que permiten especificar que un EJB se guarda como un registro de una tabla determinada, indicando a qué columna del registro va cada campo del objeto.

También es posible hacer que sea el propio objeto el que se responsabilice de su persistencia: para esto se podría utilizar por ejemplo la API JDBC, que encapsula el acceso a bases de datos relacionales de diversos fabricantes, y también forma parte de J2EE.

Seguridad

Con frecuencia es necesario limitar el acceso a partes sensibles de un sistema, y la plataforma J2EE permite hacer esto de forma excepcionalmente sencilla.

Para cada método de un Enterprise Bean es posible especificar qué roles tienen acceso a dicho método: por ejemplo, todas las personas en el rol de Vendedor más todas las personas en el rol de Administrador pueden tener acceso al método que devuelve las facturas de un cliente, getFacturas. Cada vez que se llama a un método de un EJB se verifica si la persona que llama a dicho método tiene alguno de los roles autorizados, de modo que si no es así se elevará una excepción.

Para indicar los roles que tienen permiso para acceder a cada método no es necesario escribir ningún código fuente, sino que esto se especifica en un archivo en formato XML, llamado descriptor de despliegue (deployment descriptor), el cuál se utiliza para esta y otras tareas similares: es de aquí de donde el servidor de aplicaciones obtendrá la información para realizar los chequeos de permiso de acceso a cada método.

Nótese que este sistema de roles es muy sencillo de utilizar. Cuando una aplicación se distribuya, la persona encargada de instalarla en un servidor de aplicación tendrá simplemente que decidir qué personas tienen cada rol, normalmente utilizando una herramienta visual. Si en lugar de trabajar a nivel de roles se trabajara a nivel de usuario, la persona encargada de la instalación tendría que decidir para cada método qué usuarios tienen permisos, lo que es mucho más trabajoso, complicado y confuso.

Otro aspecto importante de la seguridad es el de la transmisión segura de información, de modo que datos sensibles tales como un número de Visa no sean accesibles a cualquier persona que pueda llegar a interceptar la comunicación (algo relativamente fácil en Internet), lo que normalmente se puede conseguir mediante encriptación. La especificación actual, EJB 1.1, no indica nada al respecto: sin embargo, la mayor parte de los servidores de aplicación soportan comunicaciones seguras, típicamente a través de SSL (Secure Sockets Layer).

Por último, un aspecto importante de la seguridad es la autentificación, que permite garantizar que un usuario es quien dice ser, lo que típicamente se consigue pidiendo una palabra de paso (password). La especificación de J2EE no dice nada al respecto, dejando a cada servidor de aplicación libre para proporcionar esta funcionalidad como prefiera.

Transacciones

Si nuestra aplicación está destinada a la venta de productos a través de Internet, una vez que dispongamos de todos los productos solicitados por un cliente en el almacén querremos generar una orden de envío, actualizar las existencias para reflejar que los productos especificados saldrán del almacén, y por último cargar la Visa del cliente. Nótese que estas tres operaciones deben tener éxito a la vez, o bien ninguna de ellas ha de llevarse a cabo: queremos que sean atómicas, o de lo contrario nos podríamos encontrar con que hemos cargado la Visa del cliente, pero no se ha generado la orden de envío (quizá porque en ese momento ha caído la máquina en la que se encuentra la base de datos con los envíos). Al cliente nunca se le enviarán los productos por los que se le cargó la Visa, algo que no le hará muy feliz.

En aplicaciones de pequeña escala normalmente bastará con usar el soporte para transacciones que suelen ofrecer casi todas las bases de datos, pero en aplicaciones de cierto tamaño esto no es posible, por el mero hecho de que numerosos sistemas pueden formar parte de la operación, siendo necesario coordinarlos todos. De hecho, la mejor forma de hacer esto es dejar que el fabricante del servidor de aplicaciones se entienda con el fabricante de los distintos productos, y que sea el propio servidor de aplicaciones el que se encargue de coordinar a todos los participantes en la operación, tanto si esta tiene éxito como si no, y esta es la política adoptada en la plataforma J2EE.

Por supuesto, todavía es responsabilidad del programador indicar que un método debe ejecutarse dentro de una transacción, pero ni siquiera es necesario que escriba código para hacerlo: bastará con indicar en el archivo descriptor de despliegue que dicho método necesita ser ejecutado dentro de una transacción, y el servidor de aplicaciones se encargará de comenzar una cuando se vaya a ejecutar dicho método, si es que no hay ya una transacción activa: más adelante estudiaremos cómo consigue el servidor de aplicaciones llevar a cabo todas estas acciones de forma transparente.

Por supuesto, aunque es posible y recomendable delegar en el servidor de aplicaciones para que sea él el que se encargue de manejar las transacciones a partir de la información proporcionada en el descriptor de despliegue, existe la posibilidad de que el programador acceda directamente a la API de manejo de transacciones, llamada JTA (Java Transaction API).

Soporte para Concurrencia

El uso de un sistema distribuido implica que numerosos usuarios pueden estar accediendo a la información (objetos) a la vez, lo que plantea un problema. La solución puede ser complicada, por lo que es una vez más el servidor de aplicaciones el que se hace cargo del mismo: por defecto el servidor no permitirá acceder a los métodos de un objeto más que a un único hilo de control (thread), de modo que hasta que un cliente no haya terminado de ejecutar el método no será posible para otro cliente ejecutar ningún otro método de dicho objeto. Es más, si el método forma parte de una transacción entonces los métodos del objeto no serán accesible por otro cliente hasta que no se acabe la transacción.

Para evitar posibles problemas un EJB no puede crear hilos de control en ninguno de sus métodos. Además, está prohibido definir un método como synchronized, algo que no tiene sentido porque no es el usuario el responsable de proporcionar soporte para concurrencia, sino el servidor de aplicaciones. Adicionalmente, el servidor de aplicación puede detectar casos en los que se podría producir un bloqueo, y elevará una excepción en ese caso.

Naming y Servicio de directorios

Un elemento fundamental a la hora de trabajar en entornos distribuidos es la posibilidad de encontrar el objeto deseado, no importa dónde resida, para lo que típicamente se utiliza un Servicio de Nombres (Naming Service).

Un servicio de nombres permite asociar un nombre a uno o más objetos o recursos, de modo que después será posible acceder a dichos objetos utilizando dicho nombre, incluso aunque hayan sido trasladados a otra máquina. Hay servicios de nombres que proporcionan información que describe el objeto asociado a un nombre. Esto puede ser muy útil: por ejemplo, podría permitir encontrar una o más impresoras color sin necesidad de saber su nombre. A estos servicios se les llama Servicios de Directorio.

Para trabajar con servicios de nombres y de directorio de forma portable existe una API estándar, JNDI (Java Naming and Directory Interface), que nos permite acceder usando el mismo interface a distintos servicios, como LDAP o DSML.

Cómo funciona un servidor de aplicaciones

Como se ha podido ver hasta ahora, un servidor de aplicaciones proporciona una gran cantidad de funcionalidad, mucha de ella de forma transparente, sin que se haya de escribir código fuente alguno. Pero, ¿cómo es posible que esto sea así? ¿Cómo es posible, por ejemplo, que el servidor pueda chequear los permisos de seguridad para cada método de nuestros objetos, sin que tengamos que escribir ningún código adicional a la hora de implementar los métodos?

La respuesta es muy sencilla: dado que nuestros Enterprise JavaBeans se ejecutan dentro de la máquina virtual proporcionada por el servidor de aplicación, este puede hacer muchas cosas a escondidas, y de hecho su ocupación favorita es interponerse entre las llamadas que se hacen a los métodos de los beans y las implementaciones de los mismos, de modo que entre otras cosas puede hacer los chequeos necesarios para verificar si el usuario que llama al método tiene los permisos adecuados, antes de llamarlo. El hecho de que el servidor de aplicaciones controle totalmente la máquina virtual donde se ejecuta el Enterprise Bean hace que le sea posible conseguir un control absoluto del mismo y su entorno.

Por supuesto, los detalles reales de cómo se lleva a cabo esta interposición entre el EJB y el servidor de aplicaciones son un poco más complejos de lo que podría dar a entender esta explicación, pero esta ilustra muy adecuadamente la idea básica.

J2EE y HTML

El uso de HTML para implementar el interface de usuario es una alternativa muy interesante, e incluso en algunos casos la única posible.

Las ventajas de utilizar HTML son varias. En primer lugar, no se requiere ningún tipo de instalación en el ordenador del cliente, excepto un navegador como Netscape Navigator o Internet Explorer. Esta es una ventaja enorme, no solo desde el punto de vista de la disminución de los costes de mantenimiento, algo muy importante, sino también porque hay escenarios en los que es imposible instalar una aplicación en el ordenador del cliente: por ejemplo, ¿sería Amazon una alternativa tan popular si todos sus usuarios tuviesen que instalar una aplicación en nuestro ordenador?. Seguramente no, de hecho ni siquiera sería viable.

Otra ventaja importante es que es posible modificar la funcionalidad ofrecida al usuario sin que sea necesario hacer ningún tipo de mantenimiento en su máquina: basta con modificar el HTML que recibirá su navegador, con lo que los costes de actualización serán mucho más bajos.

Una tercera ventaja importante del HTML es que una página puede ocupar un espacio relativamente pequeño, por comparación a otras posibles soluciones, como el uso de applets, por lo que su descarga será relativamente rápida. Por último, hay que tener en cuenta que las páginas HTML se transmiten mediante el protocolo HTTP, el cuál puede pasar a través de un cortafuegos (firewall) sin problemas, algo que no sucede con IIOP, el protocolo utilizado por los objetos distribuidos, sobre todo si el cliente es anónimo. Los applets, por otra parte, cuentan con unas restricciones de seguridad importantes, por lo que pueden no ser adecuados como alternativa.

Todas estas ventajas convierten el uso de HTML en una solución prácticamente universal, por lo que la plataforma J2EE proporciona soporte para el mismo a través de dos APIs: la API para Servlets,  que proporciona una serie de clases que encapsulan la generación de páginas, y Java Server Pages (JSP), que permiten incluir código Java dentro de páginas HTML.

Por supuesto, el uso de HTML también tiene sus desventajas: los interfaces de usuario tienden a ser mucho menos atractivos que los de una aplicación gráfica desarrollada a medida, por ejemplo, y la programación de HTML puede ser más complicada en algunos casos. Sin embargo, J2EE permite el uso de Enterprise Beans desde páginas HTML, lo que resulta fundamental para encapsular adecuadamente las reglas del negocio.

Como complemento, J2EE proporciona una API para el envío de correo electrónico, JavaMail, lo que permite el uso del mismo interface para acceder a distintos servidores de correo.

Servicios obligatorios para un Servidor de Aplicaciones

Uno de los objetivos de J2EE es conseguir que todo servidor de aplicaciones proporcione un número mínimo de servicios y protocolos, independientemente de que un servidor proporcione otros servicios adicionales. Estos servicios mínimos son:

·         Servlets 2.2, Java Server Pages (JSP) 1.1, y posibilidad de servir peticiones HTTP 1.0 y HTTPS (HTTP seguro) sobre SSL 3.0.

·         Enterprise JavaBeans 1.1.

·         Java Transaction API 1.0 (JTA).

·         Java Messaging Services 1.0 (JMS), que proporciona soporte para diversos sistemas de mensajería, como MQSeries. El servidor debe proporcionar el interface, pero no es obligatorio proporcionar una implementación.

·         Java Database Connectivity 2.0 (JDBC). No es obligatorio sin embargo soportar el Optional Package.

·         Java Naming and Directory Interface 1.2 (JNDI).

·         JavaMail 1.1: está garantizado que se podrá enviar correo, pero no que se podrá recibirlo.

·         Java Activation Framework 1.0.

·         JavaIDL. Tanto los Enterprise Beans y como los componentes Web deben ser capaces de acceder a servicios CORBA externos a través de JavaIDL, lo que permite garantizar que las aplicaciones cliente del servidor de aplicaciones tienen conectividad con servicios CORBA externos al servidor.

·         RMI-IIOP 1.0: se debe proporcionar protocolo de invocación remota con la semántica de RMI-IIOP.

 

J2EE proporciona algunos servicios adicionales que aún no hemos estudiado, incluyendo el servicio de mensajería (Java Messaging Services, JMS) o Java Activation Framework (JAF); abordaremos estos servicios en artículos posteriores.

Una visión global de J2EE

Como se puede ver, J2EE proporciona una solución global, estandarizada y relativamente sencilla al desarrollo de aplicaciones corporativas. La funcionalidad del negocio se implementará típicamente mediante Enterprise Java Beans, clases Java que implementan unos interfaces estándar, cuyos métodos pueden ser utilizados directamente por el cliente sin que este sepa que entre él y los beans se encuentra el servidor de aplicaciones. El servidor de aplicaciones llevará a cabo tareas tan fundamentales como el control de seguridad, concurrencia, etc., proporcionando además una serie de interfaces estándar para el acceso a bases de datos (JDBC), acceso a servicios de directorio, uso de correo electrónico, etc., lo que permite acceder de forma transparente a productos de diferentes fabricantes y todo tipo de funcionalidad sin que quedemos atrapados por la necesidad de utilizar una API propietaria, lo que hace a la solución global mucho más abierta y adaptable.

Por lo que respecta al uso de los Enterprise Beans por las aplicaciones de usuario final (clientes, en verde en la Figura 3), J2EE proporciona numerosas alternativas, incluyendo la posibilidad de usar applets, aplicaciones a medida o incluso HTML o XML. Nótese que el protocolo utilizado por los beans hace posible que las aplicaciones cliente estén escritas en cualquier lenguaje, al ser compatible con CORBA.

Para soportar el desarrollo de HTML/XML la especificación J2EE proporciona un par de APIs adicionales, Servlets y Java Server Pages, que hacen al código utilizado para generarlos portable entre diversas plataformas. Por tanto, junto con los Enterprise Beans también será posible desarrollar servlets y JSPs que se cargarán en el servidor de aplicaciones: todos ellos aparecen en rojo en la Figura 3.

Figura 3. Interacción entre aplicaciones cliente/applets/navegador, Enterprise Beans y Servlets/JSPs, servidor de aplicaciones y servicios externos

Conclusiones

Desde nuestro punto de vista, la plataforma J2EE es sin duda la mejor plataforma que nunca se haya diseñado para la creación de aplicaciones corporativas de gran escala, por los siguientes motivos:

·         La funcionalidad del negocio está escrita en Java, un lenguaje portable, potente y orientado a objeto.

·         La funcionalidad del negocio es accesible a cualquier producto CORBA, lo que permite que aplicaciones escritas en C++ o cualquier otro lenguaje que no sea Java puedan acceder a la misma. Esto equivale a decir que la funcionalidad del negocio es universalmente accesible.

·         Gran parte de los servicios requeridos para implementar la funcionalidad del negocio son accesible a través de APIs estándar, en lugar de APIs propietarias, lo que permite evitar caer completamente en las manos de un fabricante.

·         Gran parte de la funcionalidad es proporcionada por el Servidor de Aplicaciones, lo que hace el desarrollo mucho más fácil: piénsese en la sencillez con que está implementado el soporte de seguridad o el soporte para transacciones.