JBoss et le classloading

  • Sharebar

Qui n’a jamais transpiré sur des problèmes de chargement de classes lors d’un déploiement applicatif sur un serveur d’application ?
Le serveur JBoss n’étant pas différent des autres, il n’est pas rare d’avoir ce genre de problèmes que vous déployiez des EAR ou des WAR, ou que vous utilisiez la version communautaire (JBoss A.S.) ou bien la version Enterprise (JBoss Enterprise Application Platform – EAP). Evidemment, on a plus de chances de les rencontrer lorsqu’on doit déployer plusieurs EAR et/ou WAR (imbriqués ou non) au lieu d’un seul, mais quelques soient les causes, la ou les solutions ne sont en général pas faciles à identifier ni à résoudre, surtout la première fois où cela arrive.
Un scénario typique et classique étant un conflit d’utilisation de classes de logging lors de l’utilisation de versions différentes de librairies par une application et par le serveur lui-même.
Génigraph étant partenaire Red Hat sur toute la gamme middleware JBoss, c’est donc régulièrement que nous sommes confrontés à des questions sur ce sujet.

Le but de cet article n’est pas d’identifier toutes les sources possibles de conflit de classloading, mais d’expliquer les différences de fonctionnement de classloading entre JBoss EAP 5 (ou A.S. 5) par rapport à JBoss EAP 6 (ou A.S. 7).

1. Présentation rapide du concept
Un chargeur de classes (classloader) représente typiquement un ensemble de jars composant un module, un composant ou une application. Chaque classloader possède un classloader parent. De fait, les classloaders forment une hiérarchie arborescente avec le classloader de démarrage (bootstrap) à son sommet. Lorsqu’un classloader charge une classe, il peut d’abord demander à son parent de charger celle-ci. Si le parent ne réussit pas à charger la classe, ce classloader essaie alors de charger la classe. Dans ce scénario appelé « parent-first class loading », les classes communes sont toujours chargées par le classloader parent. Ceci permet à une application ou à un module de communiquer avec une autre application ou un autre module à l’intérieur de la même VM. Une alternative est de demander à un classloader de trouver lui-même une classe, puis s’il échoue de demander à son parent. Cette stratégie permet à une application d’avoir une version d’une classe différente de celle trouvée dans le classloader de son parent. Cependant, il est parfois nécessaire de modifier la séquence de chargement par défaut, ou bien d’isoler les classes à l’aide de classloaders distincts afin de résoudre des problèmes de déploiement. Le problème le plus courant étant un conflit de version d’une librairie entre celle incluse dans l’applicatif et celle du serveur. Les exceptions suivantes sont les plus courantes lors de problème de classloading avec JBoss EAP 5 : ClassCastException, IllegalAccessError, VerifyError ou LinkageError.

2. Classloading en EAP 5
Le classloading en EAP 5 est basé sur un modèle hiérarchique.

Cette approche soulève les problèmes suivants :
• Tous les JAR sont toujours chargés, utilisés ou non ;
• Conflits de version ;
• Temps de lookup et de chargement élevé ;
• Difficile d’identifier la source des problèmes.

Différentes résolutions de problèmes de classloader sont possibles avec JBoss 5 :
• Identifier les JARs coupables et les supprimer si c’est possible ;
• Isoler les classloaders. Ceci implique les changements suivants :
• Pour un WAR : commenter l’élément WarClassLoaderDeployer dans le fichier $JBOSS_HOME/server/<ma_config>/deployers/jbossweb.deployer/META-INF/war-deployers-jboss-beans.xml
• Pour un EAR : affecter la valeur true au flag isolated (dont la valeur par défaut est false) situé dans l’élément EARClassLoaderDeployer du fichier $JBOSS_HOME/server/<ma_config>/deployers/ear-deployer-jboss-beans.xml.
• Définir un fichier WEB-INF/jboss-classloading.xml qui est à déployer avec l’application et permettant de définir la stratégie à utiliser pour le chargement des classes de l’application.

3. Classloading en EAP 6
Le principe de chargement des classes pour JBoss EAP 6 / JBoss A.S. 7 est radicalement différent de JBoss 5 et est désormais basé sur le principe de modules. Ses particularités sont les suivantes :
• Chaque application déployée est un module isolé des autres ;
• Les modules applicatifs n’ont pas de visibilité des modules du serveur et vice versa ;
• Les modules doivent déclarer explicitement leurs dépendances aux autres modules.

Distinction WAR/EAR :
• Pour les WARs : un WAR est considéré comme un module simple et les classes définies dans WEB-INF/lib sont traitées de la même façon que celles sous WEB-INF/classes. Toutes les classes packagées dans le WAR seront chargées par le même chargeur de classes.
• Pour les EARs : le déploiement de fichiers .ear correspond à un déploiement multi-module, c’est-à-dire que toutes les classes à l’intérieur d’un EAR ne seront pas forcément visibles depuis les autres classes de ce même EAR, sauf si des dépendances explicites sont définies. Par défaut, le répertoire EAR/lib correspond à un module simple, et chaque WAR ou EJB est déployé en tant que module distinct. Ces WAR ou EJB ont toujours une dépendance avec le module parent ce qui leur donne accès aux classes situées dans EAR/lib, mais n’ont néanmoins pas toujours une dépendance automatique les uns par rapport aux autres. Ce comportement peut être contrôlé à l’aide du paramètre ear-subdeployments-isolated situé dans la configuration du sous-système ee (par exemple dans le fichier standalone.xml dans le cas d’une utilisation en mode standalone) :
<subsystem xmlns=”urn:jboss:domain:ee:1.0″ >
<ear-subdeployments-isolated>false</ear-subdeployments-isolated>
</subsystem>

La valeur false par défaut permet aux sous-déploiements (c’est-à-dire aux WARs et EJBs) de voir les classes appartenant aux autres sous-déploiements du même EAR.

Exemple : pour le EAR suivant :
myapp.ear
|
|— web.war
|
|— ejb1.jar
|
|— ejb2.jar
Si la valeur de ear-subdeployments-isolated est à false, alors les classes dans web.war ont accès aux classes situées dans ejb1.jar et ejb2.jar. De même, les classes dans ejb1.jar ont accès aux classes de ejb2.jar (et réciproquement).
Si la valeur de ear-subdeployments-isolated est à true, alors il n’y a pas d’ajout de dépendance de modules mise en place automatiquement : l’utilisateur doit définir manuellement les dépendances :
• soit dans le fichier de manifeste (section Class-Path). L’avantage de cette solution est la portabilité : la spécification Java EE précise que les applications portables ne devraient pas se baser sur le fait que des sous-déploiements peuvent accéder à d’autres sous-déploiements sauf si une entrée Class-Path est explicitement définie dans le fichier MANIFEST.MF.
• soit en définissant de façon explicite les dépendances aux différents modules (fichiers module.xml de chaque module en question). L’avantage de cette méthode est d’avoir une gamme d’options plus larges, ainsi que de pouvoir référencer des modules qui ne sont pas packagés dans l’application.
• soit dans un fichier jboss-deployment-structure.xml spécifique à JBoss et inclus à l’application. L’avantage de cette approche est de permettre de définir les dépendances dans un seul fichier.

Priorités de chargement de classes :
Le serveur se base sur les priorités suivantes lors du chargement des classes afin d’éviter des conflits :
• Les dépendances système chargées automatiquement (y compris les APIs Java EE)
• Les dépendances déclarées explicitement par l’utilisateur, soit dans le fichier de manifeste, soit dans le fichier jboss-deployment-structure.xml.
• Les librairies packagées avec l’application (classes sous WEB-INF/classes ou .jar sous WEB-INF/lib).
• Les dépendances inter-modules.

This entry was posted in JBoss, JEE, JEE6, Red Hat. Bookmark the permalink.

Leave a Reply