Empaquetando aplicaciones J2EE para JBOSS 3.2.1

17/10/2007

JBoss Logo
La documentación de JBoss es muy pobre. Para empezar, la documentación oficial solamente está disponible comprándola. El principal problema es que la mayoría de desarrolladores, aun habiendo comprado y leído la documentación oficial, no sabrán cual es la mejor manera de empaquetar sus aplicaciones J2EE para JBoss. La web está llena de historias de terror de desarrolladores que no consiguieron averiguar cómo evitar los problemas de los classloaders (cargadores de clases). La mayoría acaban por colocar todos los jars de los que dependen en el directorio lib global de su servidor. Pero esto viola claramente las consideraciones de ámbito y aumenta los riesgos de conflictos entre aplicaciones no relacionadas entre sí pero descargadas en un mismo contenedor. Por ejemplo, si dos aplicaciones necesitan diferentes versiones de un mismo jar, esta solución requeriría del uso de dos servidores JBoss diferentes.

Este documento es un esfuerzo por recoger las averiguaciones obtenidas en un JBoss 3.2.1 de tal modo que:

  • Usuarios más expertos en JBoss puedan decidir si esta es o no la mejor estrategia.
  • Otros puedan hacer uso de las averiguaciones aquí descritas y así evitar redescubrir la rueda.

Fundamentos básicos del Classloader

Es necesario conocer los fundamentos del classloading (carga de clases) para poder comprender totalmente la alternativa aquí presentada. Los más importantes son:

  • Dos instancias java.lang.Class cargadas por diferentes classloaders (cargadores de clases) no son consideradas iguales.
  • Cuando instancias una clases por primera vez, el classloader utilizado es aquel que cargó la clases que está realizando la instanciación.
  • Los classloaders pueden formar parte de una jerarquía. Un classloader hijo puede hacer uso de las clases cargadas por sus ancestros pero no vice-versa.

Un buen modo de digerir estos principios es revisar el Tomcat’s classloader HOWTO.

Introducción a JARs, WARs, SARs, RARs y EARs

Cualquiera que se llame a si mismo desarrollador java sabrá que es un fichero JAR. Es un archivo, como lo son los ZIPs y TARs. Además de ser un simple archivo, un fichero JAR puede almacenar alguna meta información valiosa a cerca de su contenido. El fichero especial META-INF/MANIFEST.MF es donde el jar guarda la meta información.

Los EJBs también se empaquetan como ficheros jar, aunque contienen uno o más ficheros especiales bajo META-INF, llamados descriptores de despliegue (deployment descriptors).

Un fichero WAR empaqueta una aplicación web. ¿En qué consiste una aplicación web? consiste en

  • Páginas HTML/JSP así como otros recursos tales como imágenes/archivos javascript/hojas de estilo que van a ser accesibles mediante URLs.
  • Ficheros adicionales tales como servlets java, clases de utilidad y jars usados en servlets/JSPs así como descriptores de despliegue que no deberían ser accesibles mediante URLs.

Los últimos son almacenados bajo una carpeta especial llamada WEB-INF. Ambos se archivan en un JAR cuya extensión es modificada a WAR para distinguirla como aplicación web java.

De modo similar, los ficheros RAR son ficheros JAR usados para empaquetar módulos adaptadores de recursos tal y como se define en la J2EE Connector Architecture (JCA) y los ficheros SAR se utilizan para empaquetar servicios con capacidades JMX en JBoss.

Del mismo modo que el destino de todos los ríos es juntarse en un mar, todos los archivos relacionados con una aplicación empresarial se pueden empaquetar juntos bajo un EAR.

El problema de las dependencias

Ha sido una práctica común entre los servidores de aplicaciones utilizar diferentes cargadores de clases para la carga de cada archivo de aplicación – WARs, SARs y EJB JARs. ¿Qué ocurre si todos ellos necesitan unas clases de utilidad comunes? Bien, estas se cargaban por una ancestro común del classloader del archivo de aplicación buscando en un directorio lib global de alguna parte (por ejemplo JBOSS_HOME/server/jboss-instance-name/lib/) para clases compartidas. (O peor, incluirlas redundantemente como parte de cada archivo de aplicación.)

Pero ¿qué ocurre si usamos un lib global de clases compartidas en un contenedores J2EE usado para hospedar múltiples aplicaciones empresariales, cada una proveniente un vendedor diferente? Idealmente, deberíamos poder poner cada fichero EAR que es completamente auto-contenido y no debería causar ningún impacto en cualquier otra aplicación desplegada en el mismo contenedor. El desafío está entonces en encontrar un modo de indicar al servidor de aplicaciones que el WAR de dentro del EAR depende de otro JAR que está dentro del mismo EAR. De este modo podemos agrupar todos los JARs de utilidad como parte del EAR.

La solución: Extensión de empaquetado introducidas en JAR 1.2

La especificación de archivos JAR fue mejorada en la versión 1.2 de Java para incluir un modo de especificar dependencias de otros archivos de clases. Aquí tenemos un extracto de la especificación del Mecanismo de Extensión de Java 1.2:

El manifesto (manifest) de una aplicación o extensión puede especificar una o más URLs relativas referentes a los ficheros JAR y los directorios para las extensiones (y otras librerías) que necesite. Estas URLs relativas serán tratadas relativas al código base desde el que la aplicación o fichero JAR de extensión fueron cargados. Una aplicación (o, de modo más genérico, un fichero JAR) especifica las URLs relativas de las extensiones (y librerías) que necesita mediante el atributo Class-Path del manifesto. Este atributo lista las URL en las que buscar las implementaciones de las extensiones (u otras librerías) si no se pueden encontrar como extensiones instaladas en la máquina virtual anfitriona. Estas URLs relativas pueden incluir ficheros JAR y directorios para cualquier librería o recurso necesario por la aplicación o extensión. Se asume que las URLs que no finalicen un ‘/’ se refieren a ficheros JAR. Por ejemplo,

Class-Path: servlet.jar infobus.jar acme/beans.jar images/

Se pueden especificar múltiples encabezados Class-Path y estos se combinan secuencialmente.

De este modo podemos referenciar a los JARs de utilidad empaquetados en el EAR dentro del manifesto de cada WAR, SAR o JAR EJB que los necesite. Así podemos desplegar el EAR como una unidad independiente bajo JBOSS_HOME/server/jboss-instance-name/deploy/.

Truco del cargador de clases de JBoss

Hay una cosa más que debemos hacer para conseguir nuestro objetivo de un EAR auto-contenido que no “le toque los pies” a otras aplicaciones del mismo contenedor.

Probablemente pretendiendo aliviar los problemas iniciales que los usuarios noveles tienen con toda esta complejidad de los cargadores de clases, JBoss rodea la naturaleza jerárquica de los cargadores de clases con lo que denominan “Loader Respositories” (repositorios o almacenes de cargadores). Un conjunto de classloaders (llamados UnifiedClassLoader, UCLs, para distinguirlos) se unen para formar un repositorio de todas las clases que han cargado. Cada cargador de clases del grupo comprueba en el almacén si la clase ha sido cargada por él mismo o por un compañero. Si la encuentra, se utiliza la clase, sin importar quién la cargó. Si no, el cargador de clases al que se le ha pedido que cargue la clase consulta su ancestros y su propio classpath para encontrar la clase. Si aún así no encuentra la clase, se consultan los otros UCLs del grupo.

¿Qué significa esto? Nos permite tratar los classpaths combinados de todos los UCLs de un grupo como un gran classpath. ¿Cuál es la parte negativa? Si se encuentran múltiples versiones de una clase en el classpath combinado, solamente se podría utilizar una de ellas. Las otras son ignoradas de un modo silencioso.

Por defecto, todos lo EARs de una instancia JBosss, bajo JBOSS_HOME/server/jboss-instance-name/deploy/ utilizan el mismo repositorio de carga y por tanto, una clase encontrada en un EAR se puede utilizar en otro. Pero a nosotros no nos gusta llamar a este bug una característica ya que va en contra de la idea de ámbito de un EAR. Afortunadamente, JBoss proporciona un modo de suprimir este comportamiento mediante lo que denominan despliegue basado en ámbito (deployment based scoping). Aquí tenemos un extracto de la documentación de JBoss:

Con el despliegue basado en ámbito, cada despliegue crea su propio almacén de carga de clases en forma de un HierarchicalLoaderRepository3 que primero busca en las instancias UnifiedClassLoader3 de las unidades de despliegue incluidas en el EAR antes de delegar en el defaultUnifiedLoaderRepository3. Para habilitar el almacén de carga específico del EAR, es necesario crear un descriptor META-INF/jboss-app.xml tal como se muestra en el Listado 2-10

LISTADO 2-10. Un ejemplo de descriptor jboss-app.xml para la habilitación de la carga de clases de ámbito a nivel de ear.


<jboss-app>
<loader-repository>some.dot.com:loader=webtest.ear</loader-repository>
</jboss-app>

El valor del elemento loader-repository es el ObjectName JMX a asignar al almacén creado por el EAR. Este debe ser un ObjectName JMX válido y único, pero el nombre real no importa.

Otros temas relacionados

Si por alguna razón quisiéramos desplegar una archivo de aplicación antes que otro, podemos forzar a JBoss a hacer esto cambiando el URLComparator de JBOSS_HOME/server/jboss-instance-name/conf/jboss-service.xml por org.jboss.deployment.scanner.PrefixDeploymentSorter y añadiendo un prefijo numérico a cada uno de los nombres de fichero de la aplicación. Ej. 1appA.ear se cargará antes que 2aaB.ear. Todos los archivos que no tengan un prefijo numérico se cargan antes que cualquiera que lo tenga, conservando el orden de carga original excepto para aquellos que se añade el prefijo.

Vía

Algunos enlaces de interés
ClassLoadingConfiguration
ClassLoading Isolation Issues with JBoss 5.1

One Response to “Empaquetando aplicaciones J2EE para JBOSS 3.2.1”

  1. jayala84 Says:

    Hola

    Soy Julio Ayala, trabajo como programador J2EE. Este post es muy aclaratorio, por ello le agradezco su publicación. A continuación le expongo mi duda, la cual estaría muy agradecido si pudiera resolverla:

    Tengo 2 ear (A y B) con dependencias en un único sentido(El ear B necesita librerías de A). El caso es que A está completamente aislado por motivos de compatibilidad con otras aplicaciones, y por tanto no tengo acceso a dichas librerías desde B.

    Quisiera configurar el nivel de isolation de A para permitir acceder a B, pero a nadie más. ¿Es posible?¿Alguna sugerencia?

    Muy agradecido


Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: