domingo, 15 de marzo de 2009

WAR/EAR classloaders

Un clásico: tenemos un componente web empaquetado como un WAR que realiza invocaciones de métodos a otro componente de negocio empaquetado como un EAR.
En la invocación a métodos se envían objetos de clases X.class. Esta clase X está presente en un librería JAR que es compartida por el WAR y EAR (de hecho este JAR está incluído en el WAR y EAR). Asumimos que la clase X es la misma en los dos paquetes.
Estamos usando el JBoss 4.2.2 con la configuración default.
Cuando intentamos invocar al método desde el WAR obtenemos un maldito "ClassCastException".

Qué es lo que está pasando?

El tema de los classloaders es un problema complejo. Intentaré ser breve y claro:
Por defecto el WAR usa un classloader propio (lo cual en ciertas circunstancias es bueno porque permite desplegar aplicaciones web sin problema de conflicto de clases con otra aplicación).
El EAR por su lado utiliza un unified classloader propio (UCL) que está asociado con el UnifiedLoaderRepository de JBoss. Este último es un repositorio de clases que permite a las diferentes aplicaciones que se ejecutan en JBoss compartir clases.
La clase X en consecuencia está levantada por dos classloaders diferentes. Como para Java2 la identidad en runtime de una clase está definida por el fully qualified class name más su defining class loader, la JVM va a interpretar que la clase X que levantó un classloader es diferente de la misma clase levantada por otro classloader. De ahí surge el ClassCastException.

Cómo solucionarlo

Discutiremos diferentes soluciones

- Sacar el JAR compartido del WAR y el EAR y ponerlo en el directorio lib de la configuración de JBoss.
Puede andar, sin embargo si ese JAR depende de otras librerías que están en el WAR o en el EAR podemos llegar a terminar metiendo un montón de librerías específicas de la aplicación en el lib. Por otro lado el hecho que un WAR o EAR ya contenga sus librerías facilita su instalación en otras plataformas.

- Forzar el pasaje por valor para la comunicación entre el WAR y EAR
Horrible si está en la misma JVM!

- Hacer que el Tomcat use un UCL
Para esto hay que editar el archivo jboss-web.deployer/META-INF/jboss-service.xml y setear el atributo "UseJBossWebLoader" en true:


<!-- A flag indicating if the JBoss Loader should be used. This loader
uses a unified class loader as the class loader rather than the tomcat
specific class loader.
The default is false to ensure that wars have isolated class loading
for duplicate jars and jsp files.
-->


<attribute name="UseJBossWebLoader">true</attribute>



De esta forma las clases cargadas por las aplicaciones web serán compartidas por las demás aplicaciones que se ejecutan en JBoss.

No hay comentarios:

Publicar un comentario