Catégorie : Programmation     Tags : ,      

    Retrouvez cet article dans : Linux Magazine 95

    Un développeur d’applications destinées à la plate-forme Java Entreprise a tendance à mettre toutes les bibliothèques qu’il utilise dans le répertoire commun de son serveur d’applications. Plus qu’une mauvaise pratique, cela est nuisible à l’objectif d’indépendance des composants et, plus encore, à celui de robustesse du système.
    Bien que primordial, la logique de chargement de classes dans Java est souvent laissée de côté par les développeurs Java. Cette méconnaissance entraîne parfois des problèmes à l’exécution du code. Ces erreurs ne sont jamais simples à détecter ni à traquer, car beaucoup de paramètres entrent en jeu.
    Dans cet article, nous allons revenir sur les bases du chargement de classes en Java. Nous aborderons ensuite l’architecture des chargeurs de classes de JBoss AS et de ses référentiels. Puis, nous reviendrons sur tout ce qui a une influence sur les classes qui sont utilisées lors de l’exécution d’un code dans le serveur d’applications. Enfin, nous terminerons par l’illustration d’uUn développeur d’applications destinées à la plate-forme Java Entreprise a tendance à mettre toutes les bibliothèques qu’il utilise dans le répertoire commun de son serveur d’applications. Plus qu’une mauvaise pratique, cela est nuisible à l’objectif d’indépendance des composants et, plus encore, à celui de robustesse du système.
    Bien que primordial, la logique de chargement de classes dans Java est souvent laissée de côté par les développeurs Java. Cette méconnaissance entraîne parfois des problèmes à l’exécution du code. Ces erreurs ne sont jamais simples à détecter ni à traquer, car beaucoup de paramètres entrent en jeu.
    Dans cet article, nous allons revenir sur les bases du chargement de classes en Java. Nous aborderons ensuite l’architecture des chargeurs de classes de JBoss AS et de ses référentiels. Puis, nous reviendrons sur tout ce qui a une influence sur les classes qui sont utilisées lors de l’exécution d’un code dans le serveur d’applications. Enfin, nous terminerons par l’illustration d’un cas concret et les moyens qui sont à notre disposition pour diagnostiquer l’origine du problème.

    Rappels sur le chargement dynamique des classes en Java

    Les chargeurs de classes

    Le chargeur de classes Java est un mécanisme puissant de chargement de classes dynamique offrant des fonctionnalités de haut niveau telles que :

    • Le chargement des classes au plus tard (lazy) : pour limiter la quantité de mémoire utilisée et améliorer le temps de réponse du système.
    • La liaison dynamique avec vérification de type : lors d’un chargement dynamique, la vérification du type ne s’effectue qu’une seule fois, au moment de la liaison de la bibliothèque.
    • La personnalisation : permet de donner le contrôle aux développeurs pour ajouter du traitement à exécuter avant le chargement des classes ou des possibilités de chargement (à distance par exemple).
    • La gestion de plusieurs espaces de noms : un chargeur de classes permet de définir un espace de noms pour offrir de la robustesse. Il permet d’identifier et d’isoler un ensemble de classes en cours d’utilisation.

    Dans une machine virtuelle Java, plusieurs chargeurs de classes sont utilisés. Ceux-ci sont organisés en arborescence. À l’exception du chargeur initial (voir ci-dessous), tous les chargeurs de classes Java doivent avoir un parent.
    Pour un développeur Java, il est important de bien comprendre les implications de la présence de plusieurs espaces de noms dans une machine virtuelle. C’est ce que nous verrons au cours de cet article.

    Les types Java

    En Java, une classe est identifiée non seulement par son nom complet (c’est-à-dire un nom de la forme [nom du package].[nom de la classe]), mais aussi par l’instance du chargeur de classes l’ayant définie. Ceci a pour conséquence immédiate que la machine virtuelle Java peut, dans certains cas, considérer que deux classes nommées my.class.A sont différentes et signaler une erreur de type. Cette différentiation est essentielle pour assurer qu’un composant n’utilisera pas une classe définie ailleurs, par un autre composant qui, malheureusement, porte le même nom. Par contre, cela signifie qu’il faudra faire attention à la manière dont sont packagées les applications pour les cas où une classe devrait être utilisée par plusieurs composants.

    La délégation de chargement de classes

    Un principe fondamental dans le chargement des classes Java est le fait qu’un chargeur de classes va toujours demander, dans un premier temps, à son chargeur de classes parent de charger la classe. Si cette requête n’aboutit pas, le chargeur de classes courant la traitera. Un chargeur de classes possède toujours un parent. Dans le cas de la création d’un chargeur de classes via son constructeur sans argument, le parent par défaut sera le chargeur de classes système.
    Le langage Java définit les notions de chargeur de classes initiant la demande de chargement et le chargeur de classes définissant la classe (c’est-à-dire celui qui la charge effectivement).
    Par exemple, quand la classe my.class.A utilisée dans une applet (avec son chargeur de classes spécifique, appelé CDC-A dans la figure 01) utilise une variable de type java.lang.String, le chargeur de classes de l’applet demande au chargeur de classes du système s’il peut charger java.lang.String. Le chargeur de classes du système charge java.lang.String et le chargeur de classes de l’applet peut retourner ce type. Dans cet exemple, le chargeur de classes de l’applet est l’initiant et le chargeur de classes système est le définissant.

    /img-articles/lm/95/cc-art-jboss/fig-1.jpg

    Figure 01

    La visibilité des classes

    Dans une arborescence de chargeurs de classes, toutes les classes chargées au niveau parent sont visibles par les classes chargées au niveau enfant. Cependant, le contraire n’est pas vrai. Un chargeur de classes parent n’a aucune visibilité sur les classes de ses chargeurs de classes enfants. Les classes chargées par deux chargeurs de classes frères (dans la figure 02, CDC-X et CDC-Y sont frères, car ils ont le même parent) ne sont pas visibles les unes des autres.

    /img-articles/lm/95/cc-art-jboss/fig-2.jpg

    Figure 02

    La figure 02 illustre ces domaines de visibilité. Le chargeur de classes CDC-A ne voit que la classe my.class.A. Le chargeur de classes CDC-B voit les classes my.class.B et my.class.A. Les chargeurs CDC-X et CDC-Y voient ces deux classes en plus de celles qu’ils ont chargé (respectivement my.class.X et my.class.Y). Par contre, le chargeur CDC-X ne peut pas voir la classe my.class.Y. Réciproquement, le chargeur CDC-Y ne peut pas voir la classe my.class.X.

    L’unicité des classes chargées

    Une conséquence directe de la délégation est qu’une classe ne sera chargée qu’une seule fois dans l’arborescence des chargeurs de classes. Un chargeur de classes enfant ne chargera une classe que si celle-ci n’est pas déjà chargée par un de ses parents et qu’aucun d’eux ne peut la charger.
    Cependant, l’API Java des chargeurs de classes permet de contourner le système de délégation. C’est la contrepartie de l’aptitude à la personnalisation. Avec Java, il est possible de forcer le chargement d’une classe par un chargeur spécifique (sous réserve que la classe soit accessible).
    Par exemple, le code suivant force l’utilisation du chargeur de classes de my.class.X, CDC-X, pour charger la classe my.class.B qui est déjà chargée par le chargeur de classes CDC-B (voir figure 02) :

    public class X {
     public X() {
     ClassLoader cdc-x = this.getClass().getClassLoader();
     Class B = Class.forName(«my.class.B”, true, cdc-x);
     }
    }

    Cela est nécessaire à l’isolation des applications, pour permettre la présence de plusieurs versions d’une classe (utile pour la plate-forme Java Entreprise, voir ci-dessous). À partir de ce moment, il sera possible de voir apparaître des conflits de classe.
    Le langage Java dispose d’un mécanisme qui permet de récupérer les informations d’une instance de la classe my.class.B chargée par le chargeur CDC-B pour construire une instance de la classe my.class.B chargée par CDC-X. Ce mécanisme s’appelle la sérialisation.

    La sérialisation

    L’opération de sérialisation est le mécanisme standard du langage Java pour les communications entre différentes JVM. Ce mécanisme est très coûteux en termes de ressources CPU, car il consiste à fabriquer une représentation d’un objet (avec son état) sous la forme d’un flux de bits pour pouvoir le transmettre via le réseau, par exemple. Ainsi, on obtient une copie de l’objet qui est recréé dans une enveloppe fournie par le chargeur de classes local.

    Les trois chargeurs de classes fondamentaux (JVM de Sun)

    Au lancement d’une application Java, trois chargeurs de classes fondamentaux sont utilisés pour charger les classes de base.

    • Bootstrap : il charge les classes java.* disponibles dans l’environnement d’exécution Java (JDK/jre/lib). Principalement rt.jar.
    • Extension : il charge les classes des archives placées dans le répertoire JDK/jre/lib/ext comme sunjce_provider.jar, la bibliothèque permettant de faire du cryptage, par exemple.
    • Système : il charge les classes qui sont référencées par la variable d’environnement CLASSPATH, par la propriété système java.class.path, et par la ligne de commande via les options -cp ou -classpath. Il charge aussi les classes référencées, via l’entrée Class-Path, par les fichiers MANIFEST.MF présents dans les archives référencées par ces différents moyens.

    /img-articles/lm/95/cc-art-jboss/fig-3.jpg

    Figure 03

    La plate-forme Java Entreprise

    Dans ce type de plate-forme Java, la notion de "composant" est fondamentale. Un composant doit avoir un minimum de dépendances, voire aucune, avec des classes externes.
    Les chargeurs de classes apportent ici un mécanisme d’isolation primordial. Deux archives EAR ne doivent pas se perturber l’une et l’autre. Un composant doit pouvoir utiliser des bibliothèques dans une version qui n’est pas forcément la même que celle de son voisin, voire qui n’est pas compatible. Cette isolation permet d’atteindre l’objectif de robustesse.
    Pour se conformer à la spécification, les fournisseurs font souvent le choix de bâtir une hiérarchie de chargeurs de classes correspondant au packaging des archives. C’est-à-dire, par exemple, qu’un chargeur de classes est créé pour une archive EAR, puis, pour chacun des composants présents dans celle-ci, un chargeur de classes enfant sera créé. Nous verrons plus loin comment JBoss AS organise ses chargeurs de classes.

    Les exceptions issues de problèmes de chargements de classes

    Dans le meilleur des cas, lorsqu’un problème de chargement de classes est détecté, une des exceptions suivantes est levée. Il est, de loin, préférable de voir une telle exception que d’utiliser, à votre insu, une classe qui n’est pas celle que vous voudriez utiliser (par exemple, celle qui contient la correction d’un bug et qui devrait être issue d’une archive de patch et non de son archive originale).

    ClassCastExceptions


    Cette exception est levée lorsque qu’un trans-typage ("cast") illégal est réalisé. Par exemple, le code ci-dessous provoque une exception java.lang.ClassCastException, car l’objet retourné par la méthode array.get(0) est une instance de la classe java.net.URL, mais que le code tente un trans-typage en une instance de la classe java.lang.String.

    ArrayList array = new ArrayList();
    array.add(new URL(«file:/tmp»));
    String url = (String) array.get(0);

    Le problème souvent rencontré, moins évident, concerne plutôt un code où le bon type est utilisé (URL url = (URL) array.get(0)). Dans un cas d’erreur de chargement, cela signifie que la classe java.net.URL utilisée pour récupérer l’élément de la liste provient du chargement par un autre chargeur de classes que celui utilisé pour la charger.

    IllegalAccessException

    Cette exception est levée, par exemple, lorsque après une erreur de chargement de classe, un code tente un accès à une méthode dont le domaine de visibilité est le package. Bien que la classe appelante appartienne au package de la classe appelée, si celle-ci est chargée par un autre chargeur de classes, la JVM considérera que les deux classes ne font pas partie du même package.

    LinkageError (et ses sous-classes)

    La JVM dispose d’un mécanisme qui génère et vérifie des contraintes de chargement. Ces contraintes permettent d’assurer la cohérence des types au travers de plusieurs espaces de noms, c’est-à-dire la cohérence des classes chargées par différents chargeurs de classes. Différentes versions d’une même classe peuvent comporter des modifications incompatibles comme une modification du type d’un attribut ou le changement du domaine de visibilité d’une méthode (private, package, protected ou public).

    /img-articles/lm/95/cc-art-jboss/fig-4.jpg

    Figure 04

    Par exemple, une classe A utilise une classe B. Ces deux classes sont chargées par le chargeur de classes CDC-1. Une classe C, chargée par le chargeur de classes CDC-2, possède un attribut de classe B chargé lui aussi par le chargeur de classes CDC-2. La classe A possède une méthode qui utilise une classe C (chargée par CDC-2) et appelle une méthode de C qui retourne son attribut de classe B pour affecter son propre attribut de classe B.

    ...
    class A{
    
       public void methodeA(){
          B b = c.getB();
          ...
       }
    ...
    }
    

    Les deux classes B sont incompatibles à la suite de la modification du domaine de visibilité d’un de ses attributs qui est passé de private à public, par exemple (B.v1 et B.v2 les versions 1 et 2 de B). Le code B b = c.getB() ci-dessus génère une contrainte d’égalité entre les attributs de classe B des classes A et C. Si les chargeurs de classes CDC-1 et CDC-2 ont déjà chargé B, une exception est levée dès la génération de la contrainte. Sinon, cette contrainte est ajoutée aux autres. Dans un autre cas de figures où, ailleurs, CDC-2 charge B sans provoquer d’erreur (la contrainte d’égalité n’étant pas encore générée), et que, plus tard la méthode A.methodeA() est appelée, l’exception sera levée au chargement de la classe B par CDC-1.
    En outre, sans la vérification d’une telle contrainte, le code de la classe A pourrait avoir accès à la valeur de l’attribut de la classe B qui n’est normalement pas visible de l’extérieur de la classe B, car son domaine de visibilité est private.

    Le chargement des classes dans JBoss AS

    L’architecture des chargeurs de classes de JBoss AS s’appuie sur le constat qu’une gestion des chargeurs de classes est nécessaire pour atteindre des objectifs de performance tout en continuant à garantir les objectifs de robustesse. En effet, dans un certain nombre de cas, il apparaît nécessaire d’utiliser plusieurs espaces de noms. Ce qui implique une sérialisation comme nous l’avons vu précédement et pénalise fortement les performances. La solution mise en œuvre dans JBoss AS consiste à introduire la notion de référentiel de chargeurs de classes (UnifiedLoaderRepository) et de l’associer à la notion de chargeur de classes "unifié" (UnifiedClassLoader ou UCL) qui est une extension de la classe java.net.URLClassLoader. Ces notions contribuent à bâtir un espace de noms recouvrant un ensemble de chargeurs de classes dans lequel le recours à la sérialisation n’est plus nécessaire. Ainsi, l’architecture de JBoss AS est un ensemble d’espaces de noms (en fait, une hiérarchie) ou de référentiels de chargeurs de classes. Et la beauté de la chose, nous le verrons plus tard, c’est qu’il est possible de configurer précisément quels composants vont participer à l’établissement du contenu de tel ou tel espace de noms.

    Les bases

    /img-articles/lm/95/cc-art-jboss/fig-5.jpg

    Figure 05

    La figure 05 illustre les notions qui sont introduites ci-dessus. Un référentiel de chargeurs de classes pointe sur un ensemble de chargeurs de classes unifiés. Par contre, un chargeur de classes unifié ne s’adresse qu’à un seul référentiel. Un chargeur de classes unifié possède une liste d’URL à partir desquelles il est capable de charger des classes. À sa création, le chargeur de classes unifié déclare à son référentiel les packages des classes qu’il est capable de charger. Ainsi, le référentiel maintient la liste des packages et des chargeurs de classes associés auxquels il peut demander le chargement. Un package peut être déclaré par plusieurs chargeurs de classes unifiés. Le référentiel maintient la liste des chargeurs de classes capables de charger les classes d’un package. L’ordre dans lequel les chargeurs de classes unifiés déclarent les packages auprès du référentiel est important. Ce sera toujours dans l’ordre de la liste qu’une requête de chargement d’une classe sera effectuée. Lorsqu’une classe est chargée par un des chargeurs de classes d’un référentiel, cette classe est placée dans le cache du référentiel.
    Le processus de chargement d’une classe par un chargeur de classes unifié est le suivant :

    • Vérification si la classe est déjà dans le cache du référentiel associé. Si oui, la classe est retournée et le processus se termine.
    • Sinon, le chargement suit son chemin normal :
    • délégation aux parents (par exemple, java.lang.String sera chargée par le chargeur de classes Bootstrap décrit ci-dessus) ;
    • si la classe est chargée par l’un d’eux, elle est retournée et ajoutée au cache du référentiel (même si le chargeur de classes ne fait pas partie de la liste gérée par le référentiel, java.lang.String sera présente dans le cache du référentiel) et le processus se termine ;
    • si la classe n’est pas trouvée par les parents, recherche parmi les URL du chargeur de classes unifié ;
    • si la classe est trouvée, elle est placée dans le cache du référentiel, elle est retournée et le processus se termine ;
    • Sinon, le référentiel est chargé de trouver un chargeur de classes capable de traiter la requête de chargement à l’aide de sa liste de packages et dans l’ordre dans lequel ils se sont déclarés. Si un chargeur de classes traite la requête, la classe est retournée et le processus se termine.
    • Si aucun de ces chargeurs de classes n’est capable de traiter la requête, l’exception java.lang.ClassNotFoundException est levée.

    Le schéma complet pour une EAR par défaut (JBoss AS 4.0.2+)

    Par défaut, le serveur d’applications est configuré pour n’utiliser qu’un seul espace de noms. Nous verrons dans la section suivante le mécanisme d’isolation qui permet la gestion de plusieurs référentiels de chargeurs de classes.
    Le chargement tel qu’il est décrit ci-dessous, concerne une configuration telle qu’elle est par défaut dans JBoss AS, c’est-à-dire que les EAR ne sont pas isolées et Apache Tomcat n’utilise que ses chargeurs de classes et respecte le chargement "sans délégation au parent d’abord", comme il est décrit dans la spécification "Servlet Specification, version 2.4, chapitres 9.4 et 9.6" (valable aussi pour la spécification des servlets en version 2.3).

    /img-articles/lm/95/cc-art-jboss/fig-6.jpg

    Figure 06

    La figure 06 montre les différents chargeurs de classes impliqués au lancement de JBoss AS :

    • Bootstrap, Extension, Système : nous reconnaissons les trois chargeurs de classes de Java à qui le premier chargeur de classes de JBoss AS va déléguer. Lorsque JBoss AS est embarqué au sein d’une autre application, plutôt que d’utiliser directement le chargeur de classes Système de Java, JBoss AS utilisera le chargeur de classes du contexte du thread ("thread context classloader" ou "TCL") de l’application qui va le lancer.
    • NoAnnotationURLClassLoader : au lancement de JBoss AS, ce chargeur de classes est créé. Il charge les bibliothèques nécessaires au lancement du micro-noyau. Il s’agit principalement des bibliothèques contenues dans le répertoire JBOSS_HOME/lib. Il existe des options de la ligne de commande de lancement de JBoss AS, ainsi que des propriétés permettant d’étendre ou de personnaliser cette liste (patchs...).
    • UnifiedClassLoader3 : ce sont les chargeurs de classes unifiés présentés ci-dessus. Tous sont attachés à un seul référentiel. Un seul UnifiedClassLoader est créé par module déployé. Si un module contient des sous-modules, seul le module racine déclenche la création d’un chargeur de classes qui chargera toutes les classes du module est de ses sous-modules.
    • WebappClassLoader (de Tomcat) : par défaut le chargeur de classes de Tomcat pour les applications web est utilisé. Un tel chargeur est créé pour chaque application web. Celui-ci est initialisé avec, comme parent, le chargeur de classes de JBoss AS. Son processus de chargement par défaut consiste à :
    • chercher d’abord dans le répertoire WEB-INF/classes ;
    • puis dans WEB-INF/lib ;
    • puis de déléguer le chargement à son parent (le chargeur de classes unifié qui charge le service jbossweb-tomcat5x.sar.
      Certaines classes, cependant, font exception à ce processus de chargement. Pour les classes java.*, ainsi que celles utilisées par Apache Tomcat pour son fonctionnement interne, la délégation est faite en premier. En outre, sont concernés par cette exception les packages spécifiés dans l’élément de configuration FilteredPackages du fichier de configuration du service jbossweb-tomcat5x.sar/META-INF/jboss-service.xml. Par défaut, la valeur est : javax.servlet,org.apache.commons.logging.
      Avec ce chargeur, les classes de l’application web ne sont pas partagées (mises dans le référentiel de classes). Elles ne sont visibles qu’à l’intérieur de l’application web.

    Isolation : les référentiels hiérarchiques

    La configuration présentée ci-dessus, bien qu’optimale en termes de performance, peut être en contradiction avec le besoin d’étanchéité entre des modules n’ayant rien en commun et déployés au sein du serveur. Il existe donc un moyen d’isoler un module. Il est même possible de généraliser ce comportement, via une configuration au niveau du serveur, pour tous les modules.
    L’isolation consiste à créer un sous-référentiel (un référentiel hiérarchique) contenant les classes spécifiques à un déploiement. C’est un nouvel espace de noms.

    /img-articles/lm/95/cc-art-jboss/fig-7.jpg
    Figure 07

    Avec ce nouveau référentiel, le processus de chargement est légèrement modifié. Par défaut, si une classe n’est pas présente dans le cache du sous-référentiel, il n’y a pas de requête vers le cache du référentiel racine. Lorsqu’une classe non présente dans le cache doit être chargée, une recherche de la liste des chargeurs de classes capables de charger son package est lancée. Cette recherche concerne aussi les chargeurs de classes du référentiel racine, afin de trouver les classes du serveur JBoss AS, ainsi que toutes les classes partagées. En outre, afin de permettre le chargement des classes système, le chargeur de classes NoAnnotationURLClassLoader est ajouté à la fin de la liste retournée. L’ordre de cette liste est importante, car c’est dans cet ordre que les tentatives de chargement de la classe demandée seront effectuées. Les chargeurs de classes attachés aux sous-référentiels sont toujours premiers dans la liste.
    Sur la figure 07, apparaît un chargeur de classes spécial nommé NoParentClassLoader. En effet, le langage Java oblige à fournir un chargeur de classes parent à la création. Si aucun n’est fourni, le chargeur de classes du système est utilisé. Dans le cas de figure d’un référentiel hiérarchique, il est nécessaire de bloquer la délégation au parent pour passer à l’étape de recherche dans les référentiels. La solution consiste à créer un chargeur de classes avec un parent quelconque (NonAnnotationURLClassLoader dans ce cas) et de surcharger la méthode loadClass(...) pour lever systématiquement une exception java.lang.ClassNotFoundException et passer à l’étape suivante du chargement : la recherche de la classe.

    Délégation : privilégier les classes partagées ou non

    Par défaut, le paramètre Java2ParentDelegation est positionné à FALSE. Ce qui a pour conséquence de favoriser le chargement à partir des URL des chargeurs de classes du sous-référentiel local. A l’inverse, il est parfois utile de favoriser les chargeurs de classes du référentiel racine. Pour cela, il faut positionner la valeur de ce paramètre à TRUE.
    La délégation configurée au niveau d’une application web permet de favoriser les classes du serveur Tomcat ou JBoss AS (en fonction de la valeur du paramètre UseJBossWebLoader, voir ci-après).

    UseJBossWebLoader : partager les classes des applications web

    Nous l’avons vu ci-dessus, par défaut, le chargeur de classes utilisé pour les applications web sont ceux de Tomcat. Ainsi les classes locales à l’application web sont utilisées, c’est un espace de noms spécifique. Il est parfois possible et utile de revenir au modèle d’espace de noms unique de JBoss AS. Pour cela, il faut positionner la valeur du paramètre UseJBossWebLoader à TRUE soit au niveau d’une application web soit au niveau du conteneur (ce sera donc la valeur par défaut pour toutes les applications web). À partir de ce moment, toutes les classes de l’application web seront dans le référentiel racine de JBoss AS ou dans un de ces sous-référentiels.

    CallByValue : forcer la sérialisation

    Dans certain cas, où, par exemple, vous ne maîtrisez pas le processus de fabrication des modules que vous déployez, il vous sera utile de forcer la sérialisation. Par exemple, un module déploie des EJB avec ses interfaces clientes. Un autre, client des EJB, embarque aussi les interfaces des EJB. Si vous n’avez pas la possibilité de refaire un packaging approprié, il vous faudra configurer JBoss AS pour qu’il sérialise les appels aux EJB (simulation d’un accès à partir d’une autre JVM). Vous éviterez ainsi les problèmes de chargement de classes.

    Note
    Cette configuration pénalise très fortement les performances. Elle doit être considérée comme une exception et ne doit être utilisée qu’en dernier ressort.

    Éléments influant sur les classes utilisées à l’exécution

    Lorsque vous intervenez sur un environnement d’exécution que vous n’avez pas mis en place, il est recommandé de faire le tour du propriétaire afin de mieux comprendre ce qui peut se passer au moment de l’exécution. En effet, il existe un certain nombre de configurations, aussi bien au niveau du langage Java que du serveur JBoss AS, qui permettent de modifier la version des classes utilisées ou le comportement du système.

    Extensions Java

    Dans l’environnement d’exécution Java de votre serveur, il existe un répertoire JAVA_HOME/jre/lib/ext dont les classes seront chargées très tôt dans le processus de chargement de classe (le chargeur de classes est placé haut dans l’arborescence – voir le chapitre sur les chargeurs de classes fondamentaux ci-dessus).

    Ligne de commande Java

    La machine virtuelle possède un certain nombre d’options de lancement. Notamment, l’option -cp ou -classpath qui permet de spécifier une liste d’archives ou de répertoires d’où les classes seront chargées très tôt dans le processus de chargement. Il est possible aussi de préciser un certain nombre de propriétés système grâce à l’option -D.

    Mécanisme de surcharge des extensions Java

    JBoss AS, pour contourner les problèmes liés à la version de parser XML embarqué dans le JDK5, précise une valeur pour la propriété : java.endorsed.dirs. Par défaut, la valeur est JBOSS_HOME/lib/endorsed. Ainsi, la version de Xerces utilisée par JBoss AS n’est pas celle du JDK.

    Ligne de commande et propriétés JBoss AS

    Au lancement, JBoss AS prend en charge un certain nombre d’options dont certaines visent à préciser des bibliothèques à utiliser à la place de celles faisant partie de la distribution standard. De la même manière, il existe un jeu de propriétés jboss.xxx. Reportez-vous à la documentation pour le détail de ces options et propriétés. Dans le wiki du site de JBoss.org, vous trouverez les pages :

    • http://wiki.jboss.org/wiki/Wiki.jsp?page=JBossRunParameters
    • http://wiki.jboss.org/wiki/Wiki.jsp?page=JBossProperties

    Ordre de chargement des modules

    Comme nous l’avons vu, il peut être important de connaître l’ordre dans lequel une bibliothèque est chargée dans JBoss AS. Celui-ci dispose d’un service qui prend en charge le déploiement de modules à partir d’une liste de répertoires (locaux ou distants). Par défaut, seul le répertoire deploy de la configuration démarrée est spécifié. Si une liste est spécifiée, elle sera parcourue dans l’ordre dans lequel les répertoires apparaissent dans la valeur de l’attribut URL.
    Un autre des attributs importants est URLComparator. C’est lui qui prend en charge l’ordre dans lequel les modules seront déployés. Par défaut, la classe spécifiée pour cet attribut permettra le déploiement des modules dans l’ordre suivant : "sar", "service.xml", "rar", "jar", "war", "wsr", "ear", "zip". Puis le service tentera le déploiement des autres fichiers ou répertoires n’apparaissant pas dans cette liste (une série d’autres attributs permettent d’ignorer un certain nombre de type de fichiers, par exemple, les fichiers se terminant par ".bak", ".tmp"...).

    Ordre de chargement des bibliothèques

    Il n’est pas facile aujourd’hui de déterminer exactement l’ordre dans lequel les bibliothèques sont chargées. Plusieurs paramètres entrent en jeu :

    • Les bibliothèques précisées explicitement dans des variables ou options de classpath sont chargées dans l’ordre où elles sont spécifiées.
    • Lorsqu’un processus charge les bibliothèques contenues dans un répertoire, la liste retournée par l’instruction File.listFiles() de l’API standard Java n’est pas triée (attention, ici on ne parle pas de la liste des modules SAR, EAR, WAR... déployés dans JBoss AS dont l’ordre est maîtrisé et configurable). Aucun standard Java, autant pour la plate-forme standard qu’entreprise, ne précise que le chargement doit être fait avec un ordre particulier. Certaines implémentations ajoutent une phase de tri de cette liste, d’autres non. Il ne faut donc pas compter sur l’ordre des bibliothèques pour surcharger une classe. Dans un répertoire, une seule version de classe doit être disponible sous peine de ne pas maîtriser laquelle sera utilisée.
    • Les bibliothèques spécifiées explicitement dans les fichiers MANIFEST.MF des modules et bibliothèques Java sont chargées dans l’ordre où elles apparaissent dans l’entrée Class-Path.

    Isolation

    Définition comme défaut pour le serveur

    Le service qui prend en charge le déploiement des archives EAR possède un paramètre qui permet de spécifier que, par défaut, les EAR déployées seront isolées. Par défaut, elles ne le sont pas. Suivant la version de JBoss AS, vous trouverez la configuration de ce service dans le fichier JBOSS_HOME/server/ma-config/conf/jboss-service.xml (ma-config étant la configuration que vous utilisez) ou JBOSS_HOME/server/ma-config/deploy/ear-deployer.xml. Voici un extrait de la configuration XML du service :

       <mbean code=»org.jboss.deployment.EARDeployer»
          name=»jboss.j2ee:service=EARDeployer»>
          <!-- A flag indicating if ear deployments should have their own scoped
          class loader to isolate their classes from other deployments.
          -->
          <attribute name=”Isolated”>true</attribute>
          ...
       </mbean>

    Dans ce cas, en plus de créer un référentiel de chargeurs de classes, la délégation est désactivée. Ainsi, les classes partagées du référentiel racine ne sont pas prioritaires.
    En ce qui concerne les applications web, la configuration par défaut isole les déploiements. Cette configuration se fait dans le fichier JBOSS_HOME/server/ma-config/deploy/jbossweb-tomcat5x.sar/META-INF/jboss-service.xml via l’attribut UseJBossWebLoader qui est positionné à FALSE.

    Définition au niveau du module

    Pour une archive WAR, il est possible de spécifier l’isolation en déclarant le référentiel de chargeur de classes dans le fichier jboss-web.xml :

    <jboss-web>
       <class-loading>
          <loader-repository>mon.domaine:loader=monReferentiel</loader-repository>
       </class-loading>
    ...
    </jboss-web>

    Le nom mon.domaine:loader=monReferentiel utilisé pour le référentiel est un nom JMX. Il doit être unique dans la JVM du serveur. Il est possible de spécifier le même nom de référentiel pour plusieurs modules qui feront ainsi partie du même espace de noms.
    Pour une archive EAR, il est possible de spécifier l’isolation en déclarant le référentiel de chargeur de classes dans le fichier jboss-app.xml :

    <jboss-app>
       <loader-repository>mon.domaine:loader=monReferentiel</loader-repository>
    ...
    </jboss-app>

    Pour une archive SAR, il est possible de spécifier l’isolation en déclarant le référentiel de chargeur de classes dans le fichier jboss-service.xml :

    <server>
       <loader-repository>mon.domaine:loader=monReferentiel</loader-repository>
    ...
    </server>

    Note
    Dans le cas d’archives imbriquées, seule la spécification au niveau de l’archive racine est prise en compte. C’est-à-dire que si un SAR non isolé embarque une EAR isolée, la spécification au niveau de l’EAR est ignorée.

    Délégation

    Afin de spécifier le comportement des modules isolés vis à vis de la délégation au référentiel racine, il est possible de spécifier le paramètre Java2ParentDelegation de la manière suivante :

    <loader-repository>mon.domaine:loader=monReferentiel
     <loader-repository-config>java2ParentDelegation=false</loader-repository-config>
    </loader-repository>

    Pour les applications web, la description diffère légèrement pour prendre en compte le paramètre utilisé par Apache Tomcat :

    <class-loading java2ClassLoadingCompliance="false">
     <loader-repository>mon.domaine:loader=monReferentiel
      <loader-repository-config>java2ParentDelegation=false</loader-repository-config>
     </loader-repository>
    </class-loading>

    CallByValue

    Communication entre EAR

    Afin d’éviter les erreurs de chargement de classes lors d’appels entre les EAR déployées au sein du même serveur JBoss AS, il est possible de spécifier l’utilisation systématique de la sérialisation dans le fichier JBOSS_HOME/server/ma-config/conf/jboss-service.xml (ma-config étant la configuration que vous utilisez) ou JBOSS_HOME/server/ma-config/deploy/ear-deployer.xml en fonction de votre version de JBoss AS :

       <mbean code=»org.jboss.deployment.EARDeployer»
          name=»jboss.j2ee:service=EARDeployer»>
          <!-- A flag indicating if ear deployments should have their own scoped
          class loader to isolate their classes from other deployments.
          -->
          <attribute name=»Isolated»>true</attribute>
          <!-- A flag indicating if the ear components should have in VM call
          optimization disabled.
          -->
          <attribute name=»CallByValue»>true</attribute>
       </mbean>

    Communication à partir de composants

    Lorsqu’un composant utilise un EJB et qu’il spécifie la balise <ejb-ref> dans son descripteur ejb-jar.xml, il est possible de spécifier une URL complète dans la balise <jndi-name> correspondante dans le fichier jboss.xml pour forcer la sérialisation, comme montré dans l’exemple suivant :

    <jboss>
     <enterprise-beans>
    ...
      <session>
       <ejb-name>MonEJBSession</ejb-name>
       <ejb-ref>
        <ejb-ref-name>ejb/MonEJBSessionB</ejb-ref-name>
        <jndi-name>jnp://localhost:1099/MonEJBSessionB</jndi-name>
       </ejb-ref>
      </session>
    ...
     </enterprise-beans>
     </jboss>

    De la même manière, n’importe quelle requête JNDI (lookup) utilisant ce type d’URL force JBoss AS à utiliser la sérialisation. En contrepartie, cette manière de faire vous contraint à embarquer une spécificité de comportement qui devrait plutôt être du ressort d’un responsable du packaging et non du développeur. Il serait préférable dans ce cas de centraliser les noms JNDI dans un fichier de propriétés facilement modifiable pour faciliter le changement de politique de packaging.
    Il est possible de généraliser ce comportement au niveau du serveur (et ce, indépendamment de la forme utilisée dans le nom JNDI utilisé pour récupérer une référence à un EJB). Pour cela, il suffit de configurer le service de nommage dans le fichier JBOSS_HOME/server/ma-config/conf/jboss-service.xml (ma-config étant la configuration que vous utilisez) ou JBOSS_HOME/server/ma-config/deploy/naming.sar/META-INF/jboss-service.xml en fonction de votre version de JBoss AS :

    <mbean code=»org.jboss.naming.NamingService»
          name=»jboss:service=Naming»
          xmbean-dd=»resource:xmdesc/NamingService-xmbean.xml»>
      ...
      <attribute name=”CallByValue”>true</attribute>
      ...
    </mbean>

    Bonnes pratiques

    De manière générale, lorsque vous développez des composants destinés à être déployés dans un serveur d’applications, il faut veiller à ne rien déployer en dehors du composant. Seules sont nécessaires les instructions destinées à l’administrateur du serveur. Ces instructions sont du type : "Utilisation d’une source de données nommée jdbc/MaSourceDeDonnées". Ceci permet de dissocier les activités de développement et d’administration ainsi que les décisions qui en découlent.
    Le packaging est au centre du compromis entre robustesse et optimisation. En fonction de vos besoins, vous opterez pour :

    • Un partage total des classes pour une optimisation maximale. C’est envisageable notamment pour un JBoss AS qui héberge peu d’applications.
    • Une isolation totale afin d’éviter les effets de bords entre composants déployés et les bibliothèques du serveur d’applications. Dans ce cas, il faudra faire en sorte qu’il ne soit pas utile de recourir systématiquement à la sérialisation via un packaging adéquat où une EAR sera auto-suffisante.

    Lorsque vous fabriquez une EAR, quelques règles de bon sens doivent être observées :

    • Une classe déployée ne doit être présente qu’une seule fois dans le module J2EE.
    • Les archives placées à la racine de l’EAR doivent être déclarées dans le Class-path du fichier MANIFEST.MF des modules EJB et WAR.
    • Si une classe n’est utilisée que par un module WAR, la bibliothèque doit être placée dans le module WAR.
    • Si une classe n’est utilisée que par les classes déployées à la racine de l’EAR, elles doivent être déployées à la racine de l’EAR. Une relation de dépendance doit être établie via le fichier MANIFEST.MF des archives EJB ou WAR qui l’utilisent.
    • Si une classe est utilisée à la fois dans l’EAR et dans un module WAR, elle doit être déployée dans une archive spécifique à la racine de l’EAR. Une relation de dépendance doit être établie via le fichier MANIFEST.MF des archives EJB ou WAR qui l’utilisent.
    • Les interfaces des EJB ne doivent pas être déployées dans les modules WAR sous peine de subir une erreur à l’exécution.

    Pour rappel :

    • Les classes placées dans les archives déployées à la racine de l’EAR ne voient pas les classes déployées dans l’application web si l’application est isolée (UseJBossWebLoader=false).
    • Les classes déployées dans une application web voient les classes des archives déployées à la racine de l’EAR via les entrées dans les fichiers MANIFEST.MF.

    Analyser un problème

    Lorsque vous êtes face à un problème lié au chargement de classes, prenez le temps de relire cet article et regardez en détail le packaging des composants déployés.

    Les MBeans

    À l’aide de la console JMX, il vous est possible d’accéder aux MBeans des référentiels de chargeurs de classes.

    /img-articles/lm/95/cc-art-jboss/fig-8.jpg

    Figure 08

    Comme montré sur la figure 08, le MBean du référentiel racine est nommé JMImplementation:name=Default,service=LoaderRepository. En parcourant la liste, vous devriez y trouver les autres référentiels portant soit le nom que vous avez spécifié, soit un nom donné par JBoss AS ayant une propriété extension=LoaderRepository. En cliquant sur le lien, la console nous donne accès aux attributs et opérations du MBean du référentiel.

    /img-articles/lm/95/cc-art-jboss/fig-9.jpg

    Figure 09

    Dans les attributs présentés dans la figure 09, nous voyons notamment la taille du cache du référentiel ainsi que la liste des URL qu’il gère.

    /img-articles/lm/95/cc-art-jboss/fig-10.jpg

    Figure 10

    Dans la figure 10, nous nous intéressons aux opérations getPackageClassLoaders() et displayClassInfo(). Ces deux méthodes vont nous permettre d’avoir plus d’information sur le comportement du chargement.

    getPackageClassLoaders

    Cette opération permet de voir la liste des chargeurs de classes capables de charger une classe d’un package. Le nom du package est passé en argument, terminé par un point. Par exemple, voici le résultat pour la valeur org.jboss.jmx.adaptor. (notez le point après adaptor) :

    [
    org.jboss.mx.loading.UnifiedClassLoader3@19cd75a
    {
    url=file:/app/jboss/server/default/deploy/jbossweb-tomcat55.sar/ ,addedOrder=9
    },
    org.jboss.mx.loading.UnifiedClassLoader3@1c1f5b2
    {
    url=file:/app/jboss/server/default/deploy/jbossws14.sar/ ,addedOrder=10
    }
    ]

    Le résultat montre deux chargeurs de classes. Le premier (avec l’ordre 9) est issu du déploiement du service jbossweb-tomcat55.sar et l’autre (avec l’ordre 10) issu du déploiement du service jbossws14.sar.

    DisplayClassInfo

    Cette opération permet de voir les informations des chargeurs de classes capables de charger une classe.
    L’exemple ci-dessous montre le résultat pour la classe org.apache.catalina.Container :

    org.apache.catalina.Container Information
    Repository cache version:
    org.apache.catalina.Container(121ea24).
    ClassLoader=org.jboss.mx.loading.UnifiedClassLoader3@19ba640
       {
       url=file:/app/jboss/server/default/deploy/jbossweb-tomcat55.sar/ ,
       addedOrder=9
       }
    ..org.jboss.mx.loading.UnifiedClassLoader3@19ba640
    {
    url=file:/app/jboss/server/default/deploy/jbossweb-tomcat55.sar/ ,addedOrder=9
    }
    ....file:/app/jboss/server/default/deploy/jbossweb-tomcat55.sar/
    ....file:/app/jboss/server/default/tmp/deploy/tmp55744catalina-manager.jar
    .... (...)
    ....file:/app/jboss/server/default/deploy/jbossweb-tomcat55.sar/ROOT.war/
    ....file:/app/jboss/server/default/tmp/aopdynclasses/ucl55786/
    ..org.jboss.system.server.NoAnnotationURLClassLoader@c2a132
    ..sun.misc.Launcher$AppClassLoader@1a5ab41
    ....file:/app/jboss/bin/run.jar
    ....file:/app/java/jdk1.5.0_11/lib/tools.jar
    ..sun.misc.Launcher$ExtClassLoader@18e3e60
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/sunpkcs11.jar
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/localedata.jar
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/sunjce_provider.jar
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/dnsns.jar
    ++++CodeSource: (file:/app/jboss/server/default/tmp/deploy/tmp55746catalina.jar )
    Implemented Interfaces:
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@19ba640{
    url=file:/app/jboss/server/default/deploy/jbossweb-tomcat55.sar/ ,addedOrder=9}

    Dans ce résultat, nous voyons d’abord une information concernant la version présente dans le cache, ainsi que les informations générales de son chargeur de classes. Ensuite, nous avons le détail de l’arborescence des chargeurs de classes à partir de celui qui a chargé org.apache.catalina.Container. Finalement, les lignes commençant par ### affichent les chargeurs de classes susceptibles de charger la classe montrant ainsi les sources potentielles de problèmes de chargement.

    La journalisation

    JBoss AS consigne au niveau TRACE des informations intéressantes lors du chargement des classes. Pour activer cette consignation dans un fichier journal spécifique (beaucoup d’informations sont générées), il faut ajouter dans le fichier JBOSS_HOME/server/ma-config/conf/log4j.xml un appender ainsi qu’une category, comme montré dans les extraits suivants :

        <appender name=»UCL» class=»org.apache.log4j.FileAppender»>
            <param name=»File» value=»${jboss.server.home.dir}/log/ucl.log»/>
            <layout class=»org.apache.log4j.PatternLayout»>
                <param name=»ConversionPattern» value=»[%r,%c{1},%t] %m%n»/>
            </layout>
        </appender>
        <category name=»org.jboss.mx.loading» additivity=»false»>
            <priority value=»TRACE» class=»org.jboss.logging.XLevel»/>
            <appender-ref ref=»UCL»/>
        </category>

    Vous disposerez ainsi d’informations importantes nécessitant cependant une certaine habitude pour l’interprétation. Une fois l’erreur de chargement reproduite et le fichier généré, vous pourrez l’envoyer à un expert qui saura vous indiquer la source du problème.

    Affichage de la source d’une classe

    Le langage Java permet de retrouver l’URL de l’archive ou du répertoire d’où provient le fichier .class à l’origine du chargement d’une classe. La fonction ci-dessous montre la source de chargement pour l’objet passé en argument :

    String logCl(Object o)
    {
      Class c=o.getClass();
      String msg = “---> [“+ c.getName() + “] chargement depuis : “ +
                  c.getProtectionDomain().getCodeSource();
      return msg;
    }

    Un exemple de résultat serait :

    ---> [org.apache.catalina.Container] chargement depuis : /app/jboss/server/default/tmp/deploy/tmp55746catalina.jar

    Exemple d’erreur de chargement

    Dans cet exemple, nous allons voir comment trouver, avec l’aide de toutes les informations ci-dessus, l’origine d’une erreur de chargement un peu complexe.
    Les données du problème

    Le problème repose sur l’utilisation d’une version de la bibliothèque Apache Xerces différente de celle utilisée par JBoss AS pour une raison quelconque. Ci-dessous, vous trouverez la structure de l’application de test :

    `-- myTest1.war
    |-- WEB-INF
    | |-- classes
    | | `-- MyServlet.class
    | |-- lib
    | | |-- dom3-xercesImpl.jar
    | | `-- dom3-xml-apis.jar
    | `-- web.xml
    |-- index.jsp

    Notez les versions des bibliothèques préfixées par dom3. Le code de la servlet est montré ci-dessous. Celui-ci consiste à transformer un fichier XML.

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException
      {
        DocumentBuilderFactory factory = null;
        DocumentBuilder builder = null;
        TransformerFactory transformerf = null;
        try
        {
          transformerf = TransformerFactory.newInstance();
          factory = DocumentBuilderFactory.newInstance();
          builder = factory.newDocumentBuilder();
          Document document = builder.parse(xmlFile);
          ...
        }
        catch (Exception e) {e.printStackTrace();
      }

    Configuration des chargeurs de classes

    La bibliothèque Apache Xerces étant dans une version différente de celle de JBoss AS, l’EAR est isolée. Un référentiel hiérarchique est déclaré pour l’EAR (dans le fichier META-INF/jboss-app.xml). Pour une raison quelconque (mais servant l’intérêt de cet exemple), nous configurons Apache Tomcat pour qu’il utilise un chargeur de classes de JBoss AS en modifiant le fichier JBOSS_HOME/server/ma-config/deploy/jbossweb-tomcat5x.sar/META-INF/jboss-service.xml pour positionner l’attribut UseJBossWebLoader à TRUE.

    L’erreur de chargement

    A l’appel de la servlet (http://localhost:8080/myTest1/myservlet), la console du serveur JBoss AS montre la trace de l’exception suivante :

    [STDERR] java.lang.ClassCastException: org.apache.xalan.processor.TransformerFactoryImpl
    [STDERR] at javax.xml.transform.TransformerFactory.newInstance(Unknown Source)
    [STDERR] at MyServlet.doGet(MyServlet.java:42)
    [STDERR] at javax.servlet.http.HttpServlet.service(HttpServlet.java:697)
    [STDERR] at MyServlet.service(MyServlet.java:20)
    [STDERR] at javax.servlet.http.HttpServlet.service(HttpServlet.java:810)

    Que s’est-il passé ? La ligne incriminée est la suivante :

    transformerf = TransformerFactory.newInstance();

    La classe Java standard javax.xml.transform.TransformerFactory est une classe abstraite qui s’appuie sur la propriété Java javax.xml.transform.TransformerFactory pour retourner une instance d’implémentation concrète. Dans notre cas, il semble que Apache Xalan soit la bibliothèque fournissant une telle implémentation concrète.

    Assistance de la console JMX

    Dans la console JMX, en me plaçant sur le MBean du référentiel de chargeurs de classes hiérarchique de l’EAR, je lance l’opération DisplayClassInfo() pour la classe en question et j’obtiens :

    javax.xml.transform.TransformerFactory Information
    Repository cache version:
    javax.xml.transform.TransformerFactory(90f19c).ClassLoader=org.jboss.mx.loading.UnifiedClassLoader3@1c9fe7e{ url=file:/app/jboss/server/default/deploy/myTest1.ear/ ,addedOrder=51}
    ..org.jboss.mx.loading.UnifiedClassLoader3@1c9fe7e{ url=file:/app/jboss/server/default/deploy/myTest1.ear/ ,addedOrder=51}
    ....file:/app/jboss/server/default/deploy/myTest1.ear/
    ....file:/app/jboss/server/default/deploy/myTest1.ear/myTest1.war/
    ....file:/app/jboss/server/default/deploy/myTest1.ear/myTest1.war/WEB-INF/classes/
    ....file:/app/jboss/server/default/deploy/myTest1.ear/myTest1.war/WEB-INF/lib/dom3-xercesImpl.jar
    ....file:/app/jboss/server/default/deploy/myTest1.ear/myTest1.war/WEB-INF/lib/dom3-xml-apis.jar
    ..org.jboss.mx.loading.HeirarchicalLoaderRepository3$NoParentClassLoader@120d6b7
    ..org.jboss.system.server.NoAnnotationURLClassLoader@c2a132
    ..sun.misc.Launcher$AppClassLoader@1a5ab41
    ....file:/app/jboss/bin/run.jar
    ....file:/app/java/jdk1.5.0_11/lib/tools.jar
    ..sun.misc.Launcher$ExtClassLoader@18e3e60
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/sunpkcs11.jar
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/localedata.jar
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/sunjce_provider.jar
    ....file:/app/java/jdk1.5.0_11/jre/lib/ext/dnsns.jar
    ++++CodeSource: (file:/app/jboss/server/default/deploy/myTest1.ear/myTest1.war/WEB-INF/lib/dom3-xml-apis.jar )
    Implemented Interfaces:
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@1c9fe7e{ url=file:/app/jboss/server/default/deploy/myTest1.ear/ ,addedOrder=51}
    ### Instance1 via UCL: org.jboss.system.server.NoAnnotationURLClassLoader@c2a132

    Effectivement, comme l’indiquent les deux lignes ### Instance, cette classe est chargée par deux chargeurs de classes distincts. Il y a bien un risque de problème de chargement (mais nous le savions déjà).

    /img-articles/lm/95/cc-art-jboss/fig-11.jpg

    Figure 11

    Analyse et solution

    Il apparaît que l’une des bibliothèques spécifiques Xerces embarquée par l’application web fournit une implémentation de la classe javax.xml.transform.TransformerFactory. Le référentiel hiérarchique de l’EAR ne dispose d’aucune implémentation concrète de cette classe et c’est au niveau du chargeur de classes de JBoss AS (NoAnnotationURLClassLoader) que la requête de chargement est traitée, là où la bibliothèque Xalan, qui propose l’implémentation requise, est disponible. Malheureusement, cette implémentation ayant été créée au lancement de JBoss AS, elle référence sa classe parente issue de la bibliothèque Xerces distribuée avec JBoss AS. C’est une classe de ce type qui est retournée par l’implémentation concrète. Ce qui provoque l’exception. Il y a plusieurs solutions dans notre cas, en voici deux très simples :

    • Embarquer une version de la bibliothèque Apache Xalan dans l’application web. Ainsi, l’implémentation sera chargée au niveau de ce référentiel et utilisera comme parent la classe fournie par la bibliothèque dom3-xml-apis.jar. C’est ce qui est le plus approprié dans le but d’obtenir un référentiel de chargeurs de classes indépendant de celui de JBoss AS.
    • Supprimer la bibliothèque dom3-xml-apis.jar de l’application web. Une seule version de la classe javax.xml.transform.TransformerFactory sera utilisée, celle distribuée avec JBoss AS. Cependant, nous nous éloignons de l’objectif d’indépendance du référentiel.

    Conclusion

    Vous l’aurez compris, le sujet est vaste et mériterait d’y consacrer un livre. Cependant, cet article aura sans doute clarifié un certain nombre de concepts. Désormais, vous disposez des clés vous permettant de comprendre le problème pour mieux l’éviter. Si toutefois vous devez faire face à un problème de chargement, vous êtes armé de moyens pour vous en sortir seul ou à l’aide d’experts à qui vous aurez fourni les informations nécessaires à son étude.

    Posté par (La rédaction) | Signature : Noël Rocher | Article paru dans Creative Commons License

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.

    • Il y a actuellement

    • 807 articles/billets en ligne.