Developpez.com

Plus de 14 000 cours et tutoriels en informatique professionnelle à consulter, à télécharger ou à visionner en vidéo.

Mécanisme de chargement des classes de Tomcat

et partage de librairies versionnées entre plusieurs applications

Tomcat


précédentsommairesuivant

I. INTRODUCTION

I-A. Objectif

L'objectif de cet article est dans un premier temps de présenter le mécanisme de chargement des classes et ressources dans Tomcat, puis dans un deuxième temps de proposer une solution pour partager des librairies versionnées entre plusieurs applications.

Tomcat permet de partager des librairies entre plusieurs applications. Mais, il n'est pas possible par défaut que différentes versions d'une même librairie soient partagées entre plusieurs applications.

Exemple : Trois applications utilisent quatre librairies.

  Librairie A Librairie B Librairie C Librairie D
Application 1 Version 1.0 Version 1.0 Non partageable 1 Version partageable
Application 2 Version 1.0 Version 2.0 Non partageable 1 Version partageable
Application 3 Version 2.0 Version 1.0 Non partageable 1 Version partageable
Application 4 Version 2.0 Version 2.0 Non partageable 1 Version partageable

Le cas des librairies C et D est simple. La librairie C est embarquée dans chaque application. La librairie D est ajoutée aux librairies qui sont partagées entre toutes les applications.

Le chargement des classes et des ressources de Tomcat ne permet pas de partager des librairies avec des versions différentes. Donc, chaque application embarque la version de la librairie A et B qui lui est nécessaire.

Mais, il serait intéressant que chaque application ne charge pas individuellement les classes de leurs librairies. Par exemple, si l'application 1 et l'application 2 partagent la version 1.0 de la librairie A, le chargement des classes de cette librairie prendra moins de place en mémoire.

I-B. Connaissances prérequises

Pour une compréhension plus aisée de cet article, il est intéressant d'avoir des connaissances en Java, Tomcat et Class Loading.

I-C. Versions des logiciels

Pour la réalisation de cet article les versions des outils sont :

Compte tenu du niveau technique de cet article, l'installation des outils ne sera pas détaillée.

Les outils sont installés dans les dossier suivant :

  • Java Runtime Environment : C:\Program Files\Java\jre1.5.0_17
  • Tomcat : C:\Program Files\Apache Software Foundation\Tomcat 6.0
  • Eclipse : C:\Program Files\eclipse

I-D. Autres ressources sur developpez.com

I-E. Ressources externes

II. FONCTIONNEMENT DU CHARGEUR DE CLASSES DE TOMCAT

II-A. Paramétrage lié au chargement des classes

Voici une représentation de la "branche" du paramétrage liée au chargement des classes :

Image non disponible

Il est intéressant de remarquer que Tomcat est un serveur fortement paramétrable. En effet, tout les classes implémentant ces éléments de paramétrage sont changeables par configuration.

Lors du démarrage du serveur, le Loader instancie le ClassLoader. Il lui fixe le paramétrage (délégation par exemple). Puis, il lui indique le classpath de l'application (WEB-INF/classes et librairies dans le WEB-INF/lib), avant de le démarrer.

II-B. Hiérarchie des ClassLoaders

Voici une représentation de la hiérarchie des ClassLoaders de Tomcat :

Image non disponible

Au démarrage ces différents ClassLoaders sont créés et organisés selon des liens parents-enfants.

  • Le ClassLoader Bootstrap correspond aux classes et ressources de la JVM, ainsi que celles contenues dans les JAR du dossier $JAVA_HOME/jre/lib/ext.
  • Le ClassLoader System correspond aux classes et ressources internes du serveur Tomcat : celles qui sont contenues dans les JAR $CATALINA_HOME/bin/bootstrap.jar et $CATALINA_HOME/bin/tomcat-juli.jar.
  • Le ClassLoader Common correspond aux classes et ressources partagées entre Tomcat et les applications : celles qui sont contenues dans les JAR ou directement contenues dans du dossier $CATALINA_HOME/lib.
  • Les ClassLoaders Webapp correspondent aux classes et ressources de chaque application : celles qui sont dans les dossiers WEB-INF/classes des applications et celles qui sont dans les JAR ou directement contenues des dossiers WEB-INF/lib des applications.

Nous allons vérifier cette hiérarchie grâce à une application simple qui servira de cas pratique. Elle est composée d'un descripteur de déploiement et d'une servlet.

web.xml
CacherSélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<display-name>classloaders</display-name>

	<servlet>
		<servlet-name>racine</servlet-name>
		<servlet-class>com.developpez.rpouiller.partagelibrairiesversionnees.classloaders.Servlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>racine</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>
Servlet.java
CacherSélectionnez
package com.developpez.rpouiller.partagelibrairiesversionnees.classloaders;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.loader.WebappClassLoader;
import org.apache.catalina.startup.Bootstrap;

import sun.net.spi.nameservice.dns.DNSNameService;

public class Servlet extends HttpServlet {

    private static final ClassLoader CLASSLOADER_SYSTEM = ClassLoader.getSystemClassLoader();
    private static final ClassLoader CLASSLOADER_DNS_NAME_SERVICE = DNSNameService.class.getClassLoader();
    private static final ClassLoader CLASSLOADER_BOOTSTRAP = Bootstrap.class.getClassLoader();
    private static final ClassLoader CLASSLOADER_WEBAPP_CLASS_LOADER = WebappClassLoader.class.getClassLoader();
    private static final ClassLoader CLASSLOADER_SERVLET = Servlet.class.getClassLoader();
    
    @Override
    protected void doGet(HttpServletRequest pRequete, HttpServletResponse pReponse) throws ServletException, IOException {
        
        final StringBuffer lStringBuffer= new StringBuffer();
        
        lStringBuffer.append("<HTML><TITLE>ClassLoaders</TITLE>\n");
        lStringBuffer.append("<BODY>\n");
        lStringBuffer.append("<FONT FACE=\"Courier New\">\n");

        ClassLoader lClassLoader = Thread.currentThread().getContextClassLoader();
        while(lClassLoader != null) {
            lStringBuffer.append(lClassLoader.getClass().getCanonicalName() + " : ");
            if(CLASSLOADER_SYSTEM == lClassLoader)              lStringBuffer.append("[ClassLoader Système]");
            if(CLASSLOADER_DNS_NAME_SERVICE == lClassLoader)    lStringBuffer.append("[ClassLoader DNSNameService]");
            if(CLASSLOADER_BOOTSTRAP == lClassLoader)           lStringBuffer.append("[ClassLoader Bootstrap]");
            if(CLASSLOADER_WEBAPP_CLASS_LOADER == lClassLoader) lStringBuffer.append("[ClassLoader WebappClassLoader]");
            if(CLASSLOADER_SERVLET == lClassLoader)             lStringBuffer.append("[ClassLoader Servlet]");
            if(lClassLoader instanceof URLClassLoader)          lStringBuffer.append("[URLClassLoader]");
            lStringBuffer.append("<BR/>\n");
            if(lClassLoader instanceof URLClassLoader) {
                final URLClassLoader lURLClassLoader = (URLClassLoader)lClassLoader;
                final URL[] lURLs = lURLClassLoader.getURLs();
                for(int i=0;i<lURLs.length;i++) {
                    lStringBuffer.append("&nbsp;&nbsp;&nbsp;&nbsp;" + lURLs[i] + "<BR/>\n");
                }
            }
            lClassLoader = lClassLoader.getParent();
        }

        lStringBuffer.append("</FONT>\n");
        lStringBuffer.append("</BODY></HTML>");

        pReponse.getWriter().print(lStringBuffer.toString());
    }

}

Elle est téléchargeable directement en war : ici (Miroir)

Une fois l'application déployée sur le serveur Tomcat, elle est accessible à l'adresse : http://localhost:8080/classloaders/. On obtient le résultat suivant.

 
CacherSélectionnez
org.apache.catalina.loader.WebappClassLoader : [ClassLoader Servlet][URLClassLoader]
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/webapps/classloaders/WEB-INF/classes/
org.apache.catalina.loader.StandardClassLoader : [ClassLoader WebappClassLoader][URLClassLoader]
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/annotations-api.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/catalina-ant.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/catalina-ha.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/catalina-tribes.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/catalina.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/el-api.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/jasper-el.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/jasper-jdt.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/jasper.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/jsp-api.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/servlet-api.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/tomcat-coyote.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/tomcat-dbcp.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/tomcat-i18n-es.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/tomcat-i18n-fr.jar
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/lib/tomcat-i18n-ja.jar
sun.misc.Launcher.AppClassLoader : [ClassLoader Système][ClassLoader Bootstrap][URLClassLoader]
    file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%206.0/bin/bootstrap.jar
sun.misc.Launcher.ExtClassLoader : [ClassLoader DNSNameService][URLClassLoader]
    file:/C:/Program%20Files/Java/jre1.5.0_17/lib/ext/dnsns.jar
    file:/C:/Program%20Files/Java/jre1.5.0_17/lib/ext/sunjce_provider.jar
    file:/C:/Program%20Files/Java/jre1.5.0_17/lib/ext/sunpkcs11.jar

L'application affiche les ClassLoaders en remontant la hiérarchie, avec les informations suivantes :

  • Le nom de la classe du ClassLoader (ex : org.apache.catalina.loader.WebappClassLoader)
  • Un tag indiquant que ce ClassLoader est le ClassLoader Système ([ClassLoader Système])
  • Un tag indiquant que ce ClassLoader est le ClassLoader permettant de charger la classe com.developpez.rpouiller.classloaders.Servlet ([ClassLoader Servlet])
  • Un tag indiquant que ce ClassLoader est le ClassLoader permettant de charger la classe org.apache.catalina.loader.WebappClassLoader ([ClassLoader WebappClassLoader])
  • Un tag indiquant que ce ClassLoader est le ClassLoader permettant de charger la classe org.apache.catalina.startup.Bootstrap ([ClassLoader Bootstrap])
  • Un tag indiquant que ce ClassLoader est le ClassLoader permettant de charger la classe sun.net.spi.nameservice.dns.DNSNameService ([ClassLoader DNSNameService])
  • Un tag indiquant que ce ClassLoader est compatible avec java.net.URLClassLoader ([URLClassLoader])
  • Une liste des URLs composant le repository du ClassLoader

II-C. Mécanisme de délégation

Dans un environnement Java, lorsqu'un ClassLoader doit charger une classe ou une ressource, il doit en premier demander à son ClassLoader parent. Si son parent ne retourne pas la classe ou la ressource, alors il cherche parmi ses repositories. La requête remonte ainsi la hiérarchie jusqu'au ClassLoader sans parent, puis redescend jusqu'à ce qu'un ClassLoader puisse y répondre.

Dans une application web (conformément au document "Servlet Specification version 2.3"), cela est différent. Tant que la classe ou ressource n'a pas été trouvée, le chargement se déroule selon le mécanisme suivant.

Image non disponible
  • En tout premier lieu, le ClassLoader WebappX (celui de l'application) demande au ClassLoader System.
  • Si le paramètre "delegate" est true, le ClassLoader WebappX demande au ClassLoader Common (son parent).
  • Le ClassLoader WebappX recherche dans ses propres repositories.
  • Si le paramètre "delegate" est false, le ClassLoader WebappX demande au ClassLoader Common.
  • Le ClassLoader WebappX lève une exception ClassNotFoundException.

II-D. Mécanisme de chargement

Le schéma ci-dessous représente les principaux appels entre les méthodes de chargement de classes et/ou de ressources (les méthode publiques sont sur fond vert et les méthodes protégées sur fond jaune).

Image non disponible

Les différentes méthodes sont décrites dans le tableau ci-dessous. De manière générale, pour les paramètres, les noms de classes correspondent aux noms des classes déclarées en Java (ex : java.lang.Long), les chemins des classes aux chemins des classes dans les repositories (ex : /java/lang/Long.class), les noms et chemins des ressources correspondent aux chemins des ressources dans les repositories (ex : /com/developpez/donnees.txt).

Méthode Fonctionnement
findClass(String) Recherche la classe dont le nom est spécifié en paramètre dans les repositories locaux et externes.
La méthode appelle "findClassInternal(String)" pour rechercher la classe dans les repositories locaux, puis effectue une recherche dans les repositories externes s'il y a lieu.
Si la classe n'a pas été trouvée, la méthode lève une "ClassNotFoundException".
findClassInternal(String) Recherche la classe dont le nom est spécifié en paramètre dans les repositories locaux.
La méthode détermine le chemin de la ressource liée à la classe à partir du nom de la classe et appelle "findResourceInternal(String)" avec ces informations. Ensuite, la méthode constitue la classe à partir des informations retournées par "findResourceInternal(String)".
Si la ressource liée à la classe n'est pas trouvée, la méthode lève une "ClassNotFoundException".
findLoadedClass0(String) Recherche la classe dont le nom est spécifié en paramètre dans le cache.
La méthode recherche le nom de la classe parmi les ressources déjà chargées.
Si la classe a déjà été chargée, la méthode la retourne; sinon elle retourne null.
findLoadedResource(String) Recherche la ressource dont le nom est spécifié en paramètre dans cache.
La méthode recherche le nom de la ressource parmi les ressources déjà chargées.
Si la ressource a déjà été chargée, la méthode la retourne; sinon elle retourne null.
findResource(String) Recherche l'URL de la ressource dont le nom est spécifié en paramètre dans le cache, les repositories locaux et externes.
La méthode vérifie tout d'abord si la ressource n'a pas été déjà chargée. Si la ressource n'a pas été trouvée dans le cache, la méthode recherche la ressource dans les repositories locaux en appelant "findResourceInternal(String)". Si la ressource n'a pas été trouvée dans les repositories locaux, la méthode recherche dans les repositories externes.
Si la ressource n'a été pas trouvée, la méthode retourne null.
findResourceInternal(String, String) Recherche la ressource dont le nom et le chemin sont spécifiés en paramètres dans les repositories locaux.
Si la ressource est trouvée parmi les repositories, la méthode la charge, la stocke dans les ressources déjà chargées sous la forme d'un objet "ResourceEntry" et retourne cet objet.
Si la ressource n'a été pas trouvée, la méthode retourne null.
getResource(String) Recherche l'URL de la ressource dont le nom est spécifié en paramètre, en tenant compte de la délégation.
La méthode délègue la recherche avant ou après l'appel à "findResource(String)", selon le paramètre "delegate".
Si la ressource n'a été pas trouvée, la méthode retourne null.
getResourceAsStream(String) Recherche le flux entrant correspondant à la ressource dont le nom est spécifié en paramètre, en tenant compte de la délégation.
La méthode appelle tout d'abord la méthode "findResourceInternal(String)" vérifiant parmi les classes déjà chargées.
Puis, la méthode délègue la recherche avant ou après l'appel à "findResource(String)", selon le paramètre "delegate".
Si la ressource n'a été pas trouvée, la méthode retourne null.
loadClass(String) Correspond à un appel à "loadClass(String, boolean)" avec "false" au deuxième paramètre.
loadClass(String, boolean) Charge la classe dont le nom est spécifié en paramètre, en tenant compte de la délégation.
La méthode appelle tout d'abord les méthodes (dont "findLoadedClass0(String)" pour les classes des repositories locaux) vérifiant parmi les classes déjà chargées. Puis, la méthode appelle la méthode de chargement du ClassLoader Système. Si la classe n'a toujours pas été trouvée, la méthode délègue la recherche avant ou après l'appel à "findClass(String)", selon le paramètre "delegate".
Si la classe a été trouvée et que le deuxième paramètre ("resolve") est true, la classe sera liée.
Si la classe n'a pas été trouvée, la méthode retourne null.

précédentsommairesuivant

  

Copyright © 2009 Régis POUILLER. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.