Mécanisme de chargement des classes de Tomcat

Régis POUILLER

Cet article présente le mécanisme de chargement des classes et ressources dans Tomcat et propose une solution pour partager des librairies versionnées entre plusieurs applications.

Une discussion a été ouverte pour les commentaires sur la publication de cet article : [ Commentez ♪

- Titre : Mécanisme de chargement des classes de Tomcat

- Auteur : Régis POUILLER

- Parution : 21 juillet 2009

- Licence : 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.

Sommaire

- I. INTRODUCTION

- I-A. Objectif

- I-B. Connaissances prérequises

- I-C. Versions des logiciels

- 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

- II-B. Hiérarchie des ClassLoaders

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

- II-D. Mécanisme de chargement

- III. PARTAGE DE LIBRAIRIES VERSIONNEES

- III-A. Dépendances possibles entre les catégories de classes

- III-B. Cas pratique de dépendance entre classes

- III-C. Implémentation du cas pratique

- III-C-1. Implémentation des classes

- III-C-2. Implémentation des librairies

- III-C-3. Implémentation des applications

- III-D. Lancement des applications sans partage des librairies versionnées

- III-D-1. Lancement de l'application 1

- III-D-2. Lancement de l'application 2

- III-D-3. Lancement de l'application 3

- III-D-4. Lancement de l'application 4

- III-D-5. Bilan du lancement des applications

- III-E. Solution de partage des librairies versionnées

- III-E-1. Paramétrage du partage des librairies

- III-E-2. Diagramme de classe du système

- III-E-2-a. Initialisation du système

- III-E-2-b. Comportement du système

- III-E-3. Implémentation du système

- III-E-4. Installation du système de partage

- III-F. Lancement des applications avec partage des librairies versionnées

- III-F-1. Lancement de l'application 1

- III-F-2. Lancement de l'application 2

- III-F-3. Lancement de l'application 3

- III-F-4. Lancement de l'application 4

- III-F-5. Bilan du lancement des applications

- IV. CONCLUSION

- V. REMERCIEMENTS

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 :

- Java Runtime Environment : 5.0 Update 17

- Tomcat : 6.0.18

- Eclipse : developpement Java EE 3.4 Ganymede SR2

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 dossiers suivants :

- 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

- Créer et déployer un premier servlet avec TomCat par LFE

- Configuration de Tomcat par Mickael BARON

- FAQ Java EE

I-E - Ressources externes

Documentation Tomcat 6.0 :

- Apache Tomcat Configuration Reference - The Loader Component

- Class Loader HOW-TO

- Javadoc : WebappLoader

- Javadoc : WebappClassLoader

Documentation Java 5.0 :

- Javadoc : URLClassLoader

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 :

- Le Server représente le conteneur de servlet Catalina. L'implémentation par défaut de Server est org.apache.catalina.core.StandardServer.

- Le Service représente une association entre des Connectors (non représentés), qui reçoivent les requêtes, et un Engine. L'implémentation par défaut de Service est org.apache.catalina.core.StandardService.

- L'Engine représente un système de traitement des requêtes. L'implémentation par défaut d'Engine est org.apache.catalina.core.StandardEngine.

- L'Host représente un hôte virtuel dans l'Engine (par défaut l'hôte virtuel est localhost). L'implémentation par défaut d'Host est org.apache.catalina.core.StandardHost.

- Le Context représente une application web. L'implémentation par défaut de Context est org.apache.catalina.core.StandardContext.

- Le Loader représente un système de chargement des classes d'une application. L'implémentation par défaut de Loader est org.apache.catalina.loader.WebappLoader.

- Le ClassLoader représente un chargeur de classe. L'implémentation par défaut de ClassLoader est org.apache.catalina.loader.WebappClassLoader.

Il est intéressant de remarquer que Tomcat est un serveur fortement paramétrable. En effet, toutes 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 :

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

<?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

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.

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 URL 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.

- 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éthodes publiques sont sur fond vert et les méthodes protégées sur fond jaune).

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.

III - PARTAGE DE LIBRAIRIES VERSIONNEES

III-A - Dépendances possibles entre les catégories de classes

On peut distinguer les classes en trois catégories : celles qui sont propres à l'application, celles qui appartiennent à des librairies versionnées et qui seraient partageables entre plusieurs applications, et celles qui font partie des librairies Common, System ou Bootstrap.

Le diagramme ci-dessous représente les dépendances possibles entre catégories de classes.

Les classes de Common, System ou Bootstrap ne peuvent avoir de dépendances que vers d'autres classes de la même catégorie. Tandis que les classes de l'application ou des librairies versionnées peuvent avoir une dépendance sur la même catégorie ou sur l'une des autres catégories.

On obtient donc les dépendances possibles suivantes.

Numéro

Catégorie dépendante

Catégorie de dépendance

1

Common, System ou Bootstrap

Common, System ou Bootstrap

2

Application

Application

3

Application

Common, System ou Bootstrap

4

Application

Versionnee

5

Versionnee

Versionnee

6

Versionnee

Common, System ou Bootstrap

7

Versionnee

Application

III-B - Cas pratique de dépendance entre classes

Le cas pratique couvre les différentes dépendances possibles entre catégories.

Numéro de dépendance

Catégorie dépendante

Catégorie de dépendance

Classe dépendante

Classe de dépendance

Mêmes repositories/librairies

1

Common

Common

CommonClasse1

CommonClasse2

Oui

1

Common

Common

CommonClasse3

CommonClasse4

Oui

2

Application

Application

ApplicationClasse1

ApplicationClasse2

Oui

2

Application

Application

ApplicationClasse3

ApplicationClasse4

Oui

3

Application

Common

ApplicationClasse2

CommonClasse3

Non

4

Application

Versionnee

ApplicationClasse3

VersionnneeClasse7

Non

4

Application

Versionnee

ApplicationClasse4

VersionnneeClasse3

Non

5

Versionnee

Versionnee

VersionnneeClasse1

VersionnneeClasse2

Oui

5

Versionnee

Versionnee

VersionnneeClasse3

VersionnneeClasse4

Oui

5

Versionnee

Versionnee

VersionnneeClasse5

VersionnneeClasse6

Oui

5

Versionnee

Versionnee

VersionnneeClasse7

VersionnneeClasse8

Oui

5

Versionnee

Versionnee

VersionnneeClasse6

VersionnneeClasse1

Non

6

Versionnee

Common

VersionnneeClasse8

CommonClasse1

Non

7

Versionnee

Application

VersionnneeClasse5

ApplicationClasse1

Non

Certaines dépendances ne sont pas représentées : celles entre la Servlet de l'application et sa classe mère HttpServlet (Common) par exemple.

III-C - Implémentation du cas pratique

III-C-1 - Implémentation des classes

Les classes du cas pratique ont toutes les mêmes formes. Elles ont leur nom qui comprend une abréviation de leur catégorie (Common, Versionnee ou Application), « Classe » et le numéro dans le diagramme. Si c'est une classe versionnée, on trouve la valeur de cette version. Comme on le verra juste après pour l'implémentation des interfaces du cas pratique, on trouve un Calendar et un Throwable initialisés statiquement. Chaque classe implémente également une méthode d'instance « methode() » qui est vide dans le cas d'une classe sans dépendance et qui comprend un appel vers la classe de dépendance si dépendance.

package com.developpez.rpouiller.partagelibrairiesversionnees.caspratique;

public class [CategorieClasse]Classe[NumeroClasse] {

public static final String VERSION = "[NumeroVersion]";  // Si la classe est versionnée
public static final java.util.Calendar CALENDAR = java.util.Calendar.getInstance();
public static final Throwable THROWABLE = new Throwable();

public void methode() {
new [ClasseDeDependance]().methode();   // Si la classe a une dépendance
}
}

La classe « CommonClasse2 » n'a pas de version et n'a pas de dépendance.

CommonClasse2.java

package com.developpez.rpouiller.partagelibrairiesversionnees.caspratique;

public class CommonClasse2 {

public static final java.util.Calendar CALENDAR = java.util.Calendar.getInstance();
public static final Throwable THROWABLE = new Throwable();

public void methode() {
}
}

La classe « VersionneeClasse8 » a une version (ici, on considère que c'est la version 1.0) et a une dépendance vers la classe « CommonClasse1 ».

VersionneeClasse8.java

package com.developpez.rpouiller.partagelibrairiesversionnees.caspratique;

public class VersionneeClasse8 {

public static final String VERSION = "1.0";
public static final java.util.Calendar CALENDAR = java.util.Calendar.getInstance();
public static final Throwable THROWABLE = new Throwable();

public void methode() {
new CommonClasse1().methode();
}
}

III-C-2 - Implémentation des librairies

Les librairies du Common et celles propres à l'application ne font l'objet que d'un seul JAR. Tandis que les librairies versionnées font l'objet de plusieurs JAR suffixés avec le numéro de version.

Chaque librairie comporte une ressource texte dans le même package que les classes.

Librairie

Nom du jar

Versions

Nom ressource texte

Contenu ressource texte

Téléchargement JAR

Téléchargement Sources

1re lib. du Common

commonLibA

commonA.txt

commonA

ici (Miroir)

ici (Miroir)

2e lib. du Common

commonLibB

commonB.txt

commonB

ici (Miroir)

ici (Miroir)

Lib. propre à l'Appli

applicationLib

application.txt

application

ici (Miroir)

ici (Miroir)

1re lib. versionnée

versionneeLibA-[NumeroVersion]

1.0
2.0

versionneeA.txt

versionneeA-1.0
versionneeA-2.0

1.0 (1.0 Miroir)
2.0 (2.0 Miroir)

1.0 (1.0 Miroir)
2.0 (2.0 Miroir)

2e lib. versionnée

versionneeLibB-[NumeroVersion]

1.0
2.0
3.0

versionneeB.txt

versionneeB-1.0
versionneeB-2.0
versionneeB-3.0

1.0 (1.0 Miroir)
2.0 (2.0 Miroir)
3.0 (3.0 Miroir)

1.0 (1.0 Miroir)
2.0 (2.0 Miroir)
3.0 (3.0 Miroir)

3e lib. versionnée

versionneeLibC-[NumeroVersion]

1.0
2.0

versionneeC.txt

versionneeC-1.0
versionneeC-2.0

1.0 (1.0 Miroir)
2.0 (2.0 Miroir)

1.0 (1.0 Miroir)
2.0 (2.0 Miroir)

III-C-3 - Implémentation des applications

L'implémentation des applications du cas pratique consiste en quatre applications avec des dépendances différentes.

Chaque application se compose (mis à part les classes « ApplicationClasse3 » et « ApplicationClasse4 ») d'un descripteur de déploiement « web.xml » et d'une Servlet « ApplicationServlet[numeroApplication] ».

web.xml

<?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>application[numeroApplication]</display-name>

<servlet>
<servlet-name>racine</servlet-name>
<servlet-class>com.developpez.rpouiller.partagelibrairiesversionnees.caspratique.ApplicationServlet[numeroApplication]</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>racine</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

ApplicationServlet[numeroApplication].java

package com.developpez.rpouiller.partagelibrairiesversionnees.caspratique;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.SimpleDateFormat;
import java.util.Calendar;

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

public class ApplicationServlet[numeroApplication] extends HttpServlet {

private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS");

private ClassLoader classLoader;

/**
* Retourne les informations d'element de Stack correspondant à une classe ApplicationServletX.
*/
private String getApplicationServlet(final Throwable pThrowable) {
if(pThrowable == null) {
return "&nbsp;";
}

final StringBuffer lStringBuffer = new StringBuffer();
final StackTraceElement[] StackTracesElement = pThrowable.getStackTrace();
for(int i=0;i<StackTracesElement.length;i++) {
final String lClassName = StackTracesElement[i].getClassName();
final int lPosDernierPoint = lClassName.lastIndexOf('.');
final String lNomClasse = (lPosDernierPoint >= 0)?lClassName.substring(lPosDernierPoint + 1):lClassName;
if(lNomClasse.startsWith("ApplicationServlet")) {
lStringBuffer.append(lNomClasse + "." + StackTracesElement[i].getMethodName() +
"(" + StackTracesElement[i].getLineNumber() + ")");
}
}
return lStringBuffer.toString();
}

/**
* Retourne la partie "intéressante" d'une URL d'un ClassLoader.
*/
private String getFinChemin(final URL pURL) {
try {
String lChemin = pURL.toString();
final int lPosTomcat = lChemin.indexOf("Tomcat");
if(lPosTomcat != -1) {
lChemin = "...." + lChemin.substring(lPosTomcat);
}

return lChemin.replaceAll("%20", "&nbsp;");
} catch (ex.ception e) {
return e.getMessage();
}
}

/**
* Retourne une ligne d'informations pour une classe donnée.
*/
private String getInformations(final Class<? extends Object> pClasse, final String pVersion,
final Calendar pDateInit, final Throwable pOrigineInit) {

final StringBuffer lStringBuffer = new StringBuffer();

final ClassLoader lClassLoader = pClasse.getClassLoader();
lStringBuffer.append("<TR><TD>");
lStringBuffer.append(pClasse.getSimpleName());
lStringBuffer.append("</TD><TD>");
lStringBuffer.append(pVersion==null?"&nbsp;":pVersion);
lStringBuffer.append("</TD><TD>");
lStringBuffer.append(DATE_FORMAT.format(pDateInit.getTime()));
lStringBuffer.append("</TD><TD>");
lStringBuffer.append(getApplicationServlet(pOrigineInit));
lStringBuffer.append("</TD><TD>");
if(lClassLoader == classLoader) {
lStringBuffer.append("idem</TD><TD>idem");
}
else {
lStringBuffer.append(lClassLoader.getClass().getSimpleName());
lStringBuffer.append("</TD><TD>");
if(lClassLoader instanceof URLClassLoader) {
final URLClassLoader lURLClassLoader = (URLClassLoader)lClassLoader;
final URL[] lURLs = lURLClassLoader.getURLs();
for(int i=0;i<lURLs.length;i++) {
lStringBuffer.append(getFinChemin(lURLs[i]) + "<BR/>");
}
}
else {
lStringBuffer.append("&nbsp;");
}
}
lStringBuffer.append("</TD></TR>");

classLoader = lClassLoader;

return lStringBuffer.toString();
}

/**
* Retourne une ligne d'informations pour une ressource donnée.
* @throws IOException
*/
private String getInformationsRessource(final String pNomRessource) throws IOException {

final StringBuffer lStringBuffer = new StringBuffer();
lStringBuffer.append("<TR><TD>");
lStringBuffer.append(pNomRessource);
lStringBuffer.append("</TD><TD>");
lStringBuffer.append(getFinChemin(Thread.currentThread().getContextClassLoader().getResource(pNomRessource)));
lStringBuffer.append("</TD><TD>");
final InputStream lInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(pNomRessource);
if(lInputStream == null) {
lStringBuffer.append("<I>null</I>");
}
else {
final BufferedReader lBufferedReader = new BufferedReader(new InputStreamReader(lInputStream));
lStringBuffer.append(lBufferedReader.readLine());
}
lStringBuffer.append("</TD></TR>");

return lStringBuffer.toString();
}

/**
* Affiche les informations sur les classes.
*/
@Override
protected synchronized void doGet(final HttpServletRequest pRequete, final HttpServletResponse pReponse)
throws ServletException, IOException {

new ApplicationClasse3().methode();
new VersionneeClasse5().methode();

final StringBuffer lStringBuffer = new StringBuffer();

lStringBuffer.append("<HTML><BODY>");
lStringBuffer.append("<TABLE BORDER=\"1\" CELLSPACING=\"0\">");
lStringBuffer.append("<TR>");
lStringBuffer.append("<TH>Classe</TH><TH>Version</TH><TH>Heure d'init</TH><TH>Origine de l'init</TH>");
lStringBuffer.append("<TH>ClassLoader</TH><TH>URLs du ClassLoader</TH>");
lStringBuffer.append("</TR>");

classLoader = null;
lStringBuffer.append(getInformations(CommonClasse1.class, null, CommonClasse1.CALENDAR, CommonClasse1.THROWABLE));
lStringBuffer.append(getInformations(CommonClasse2.class, null, CommonClasse2.CALENDAR, CommonClasse2.THROWABLE));
lStringBuffer.append(getInformations(CommonClasse3.class, null, CommonClasse3.CALENDAR, CommonClasse3.THROWABLE));
lStringBuffer.append(getInformations(CommonClasse4.class, null, CommonClasse4.CALENDAR, CommonClasse4.THROWABLE));
lStringBuffer.append(getInformations(ApplicationClasse3.class, null, ApplicationClasse3.CALENDAR, ApplicationClasse3.THROWABLE));
lStringBuffer.append(getInformations(ApplicationClasse4.class, null, ApplicationClasse4.CALENDAR, ApplicationClasse4.THROWABLE));
lStringBuffer.append(getInformations(ApplicationClasse3.class, null, ApplicationClasse3.CALENDAR, ApplicationClasse3.THROWABLE));
lStringBuffer.append(getInformations(ApplicationClasse4.class, null, ApplicationClasse4.CALENDAR, ApplicationClasse4.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse1.class, VersionneeClasse1.VERSION, VersionneeClasse1.CALENDAR,
VersionneeClasse1.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse2.class, VersionneeClasse2.VERSION, VersionneeClasse2.CALENDAR,
VersionneeClasse2.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse3.class, VersionneeClasse3.VERSION, VersionneeClasse3.CALENDAR,
VersionneeClasse3.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse4.class, VersionneeClasse4.VERSION, VersionneeClasse4.CALENDAR,
VersionneeClasse4.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse5.class, VersionneeClasse5.VERSION, VersionneeClasse5.CALENDAR,
VersionneeClasse5.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse6.class, VersionneeClasse6.VERSION, VersionneeClasse6.CALENDAR,
VersionneeClasse6.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse7.class, VersionneeClasse7.VERSION, VersionneeClasse7.CALENDAR,
VersionneeClasse7.THROWABLE));
lStringBuffer.append(getInformations(VersionneeClasse8.class, VersionneeClasse8.VERSION, VersionneeClasse8.CALENDAR,
VersionneeClasse8.THROWABLE));

lStringBuffer.append("</TABLE>");

lStringBuffer.append("<TABLE BORDER=\"1\" CELLSPACING=\"0\">");
lStringBuffer.append("<TR><TH>Ressource recherchée</TH><TH>URL de la ressource</TH><TH>Contenu de la ressource</TH></TR>");
lStringBuffer.append(getInformationsRessource("com/developpez/rpouiller/partagelibrairiesversionnees/caspratique/commonA.txt"));
lStringBuffer.append(getInformationsRessource("com/developpez/rpouiller/partagelibrairiesversionnees/caspratique/commonB.txt"));
lStringBuffer.append(getInformationsRessource("com/developpez/rpouiller/partagelibrairiesversionnees/caspratique/application.txt"));
lStringBuffer.append(getInformationsRessource("com/developpez/rpouiller/partagelibrairiesversionnees/caspratique/versionneeA.txt"));
lStringBuffer.append(getInformationsRessource("com/developpez/rpouiller/partagelibrairiesversionnees/caspratique/versionneeB.txt"));
lStringBuffer.append(getInformationsRessource("com/developpez/rpouiller/partagelibrairiesversionnees/caspratique/versionneeC.txt"));
lStringBuffer.append("</TABLE>");

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

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

Les dépendances sont définies dans le tableau ci-dessous.

Application

Dépendances

Téléchargement WAR

Téléchargement Sources

Application1

commonLibA, commonLibB, applicationLib, versionneeLibA-1.0, versionneeLibB-1.0 et versionneeLibC-1.0

ici (Miroir)

ici (Miroir)

Application2

commonLibA, commonLibB, applicationLib, versionneeLibA-1.0, versionneeLibB-2.0 et versionneeLibC-2.0

ici (Miroir)

ici (Miroir)

Application3

commonLibA, commonLibB, applicationLib, versionneeLibA-2.0, versionneeLibB-3.0 et versionneeLibC-1.0

ici (Miroir)

ici (Miroir)

Application4

commonLibA, commonLibB, applicationLib, versionneeLibA-2.0, versionneeLibB-1.0 et versionneeLibC-2.0

ici (Miroir)

ici (Miroir)

III-D - Lancement des applications sans partage des librairies versionnées

III-D-1 - Lancement de l'application 1

L'URL pour accéder à l'application est : http://localhost:8080/application1/

Le résultat est :

III-D-2 - Lancement de l'application 2

L'URL pour accéder à l'application est : http://localhost:8080/application2/

Le résultat est :

III-D-3 - Lancement de l'application 3

L'URL pour accéder à l'application est : http://localhost:8080/application3/

Le résultat est :

III-D-4 - Lancement de l'application 4

L'URL pour accéder à l'application est : http://localhost:8080/application4/

Le résultat est :

III-D-5 - Bilan du lancement des applications

Les classes des librairies communes sont initialisées une seule fois, donc elles sont chargées une seule fois également. Ce qui est logique, puisqu'elles sont chargées par le ClassLoader Common.

Les classes de l'application (classes simples et librairies) sont initialisées au lancement de chaque application. Elles sont chargées par le ClassLoader de la Webapp, donc elles sont chargées plusieurs fois.

C'est exactement la même chose pour les classes des librairies versionnées.

Les ressources appartenant à des librairies communes, d'application ou versionnées sont chargées depuis les librairies communes ou depuis les librairies qui sont dans les WEB-INF/lib de chaque application.

III-E - Solution de partage des librairies versionnées

III-E-1 - Paramétrage du partage des librairies

Le système de partage doit pouvoir être paramétré. Voici, l'arborescence de ce paramétrage :

Le paramétrage du partage (Partages) consiste en n dossiers de partages (Dossier) et n applications web (Application). Chaque Dossier comporte un nom et le chemin sur disque du dossier comme attributs. Chaque Application comporte un contexte correspondant au contexte de l'application dans ses paramètres de déploiement comme attribut, ainsi que n liens vers un dossier de partage (Partage). Chaque Partage comporte un nom qui correspond au nom d'un dossier de partage.

Cette configuration sera stockée dans un fichier xml contenu dans le dossier « conf » de Tomcat (« C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf »).

III-E-2 - Diagramme de classe du système

III-E-2-a - Initialisation du système

Par défaut le fichier de configuration est « conf/partages.xml », il est possible de le setter grâce à la méthode de classe « setConfigFile() » (surtout utile lors des tests).

Lorsque la méthode « init() » de « WebappLoaderAvecPartage » est appelée, elle demande le chargement de la configuration en appelant « chargerConfiguration() ». Si la configuration n'a pas été déjà chargée (« configurationChargee à true »), « chargerConfiguration() » récupère le fichier de configuration grâce à « configFile() » et utilise le Digester obtenu par « createDigester() » afin de charger la config dans « partages ».

Lorsque la méthode « start() » de « WebappLoaderAvecPartage » est appelée, elle fournit la liste des dossiers (« Dossier ») à l'ensemble des « WebappClassLoaderAvecPartage » par la méthode de classe « setListeDossiers() ». Puis, elle récupère la configuration de l'application (« Application ») correspondant au contexte du « WebappLoaderAvecPartage » grâce à « getApplicationContexte() ». Ensuite, elle fournit cette configuration d'application au « WebappLoaderAvecPartage » par la méthode « setApplication() ».

Lorsque la méthode de classe « setListeDossiers() » est appelée, elle parcourt la liste des dossiers de partage afin de constituer une liste (« listeFichiersJar ») commune à tous les « WebappClassLoaderAvecPartage » des différentes librairies (« FichierJar »).

Lorsque la méthode « setApplication() » est appelée, elle constitue une liste (« listeFichiersJarApplication ») grâce à la liste « listeFichiersJar » précédemment constituée, les informations de partages (« Partage ») de l'application et les libraries contenues dans le « WEB-INF/lib » de l'application.

III-E-2-b - Comportement du système

Deux méthodes ont été ajoutées :

- « getFileContainsResource() » retourne le fichier de « WEB-INF/lib » qui contient une ressource ou null si la ressource n'est contenue dans aucun fichier ou est également contenue dans « WEB-INF/classes ».

- « getFichierJarForResource() » retourne le « FichierJar » de « listeFichiersJarApplication » pouvant contenir la ressource grâce à « getFileContainsResource() ».

D'après les informations collectées lors de l'étude du « Mécanisme de chargement », trois méthodes de « WebappClassLoader » doivent être substituées. Un comportement spécifique pour assurer le partage de librairies versionnées doit être effectué avant de passer la main à la classe mère si la ressource n'est pas concernée par le partage. Chacune des méthodes substituées utilise « getFichierJarForResource() » afin de déterminer le « FichierJar » de « listeFichiersJarApplication » pouvant contenir la ressource.

- « findLoadedClass0() » vérifie si le « FichierJar » pouvant contenir la ressource (fichier class correspondant à la classe) possède la classe dans son cache (« resourceEntries »).

- « findLoadedResource() » vérifie si le « FichierJar » pouvant contenir la ressource possède la ressource dans son cache (« resourceEntries »).

- « findResourceInternal() » charge la ressource depuis le « FichierJar » et la place dans le cache.

III-E-3 - Implémentation du système

Partage.java

package com.developpez.rpouiller.partagelibrairiesversionnees.loader.config;

/** Classe contenant les informations d'un partage pour une application. */
public class Partage {

private String nom;

/** Constructeur sans paramètres */
public Partage() {
}

/** Constructeur avec paramètres */
public Partage(final String pNom) {
nom = pNom;
}

/** Retourne le nom d'un dossier de partage. */
public String getNom() {
return nom;
}

/** Fixe le nom d'un dossier de partage. */
public void setNom(final String pNom) {
nom = pNom;
}
}

Application.java

package com.developpez.rpouiller.partagelibrairiesversionnees.loader.config;

import java.util.LinkedList;
import java.util.List;

/** Classe contenant les informations d'une application. */
public class Application {

private String contexte;
private List<Partage> listePartages = new LinkedList<Partage>();

/** Constructeur sans paramètres */
public Application() {
}

/** Constructeur avec paramètres */
public Application(final String contexte, final List<Partage> listePartages) {
this.contexte = contexte;
this.listePartages = listePartages;
}

/** Retourne le contexte de l'application. */
public String getContexte() {
return contexte;
}

/** Fixe le contexte de l'application. */
public void setContexte(final String pContexte) {
contexte = pContexte;
}

/** Retourne la liste des partages d'une application.*/
public List<Partage> getListePartages() {
return listePartages;
}

/** Ajoute un partage (correspondant à un dossier) à la liste des partages d'une application. */
public void addPartage(final Partage pPartage) {
listePartages.add(pPartage);
}
}

Dossier.java

package com.developpez.rpouiller.partagelibrairiesversionnees.loader.config;

/** Classe contenant les informations concernant un dossier de partage. */
public class Dossier {

private String nom;
private String chemin;

/** Constructeur sans paramètres */
public Dossier() {
}

/** Constructeur avec paramètres */
public Dossier(final String pNom, final String pChemin) {
nom = pNom;
chemin = pChemin;
}

/** Retourne le nom du dossier de partage. */
public String getNom() {
return nom;
}

/** Fixe le nom du dossier de partage. */
public void setNom(final String pNom) {
nom = pNom;
}

/** Retourne le chemin du dossier de partage. */
public String getChemin() {
return chemin;
}

/** Fixe le chemin du dossier de partage. */
public void setChemin(final String pChemin) {
chemin = pChemin;
}
}

Partages.java

package com.developpez.rpouiller.partagelibrairiesversionnees.loader.config;

import java.util.LinkedList;
import java.util.List;

/** Classe contenant les informations des partages. */
public class Partages {

private List<Dossier> listeDossiers = new LinkedList<Dossier>();
private List<Application> listeApplications = new LinkedList<Application>();

/** Retourne la liste des dossiers de partage. */
public List<Dossier> getListeDossiers() {
return listeDossiers;
}

/** Ajoute un dossier à la liste des dossiers de partage. */
public void addDossier(final Dossier pDossier) {
listeDossiers.add(pDossier);
}

/** Retourne la liste des applications avec partage. */
public List<Application> getListeApplications() {
return listeApplications;
}

/** Ajoute une application à la liste des applications avec partage. */
public void addApplication(final Application pApplication) {
listeApplications.add(pApplication);
}
}

FichierJar.java

package com.developpez.rpouiller.partagelibrairiesversionnees.loader;

import java.io.File;
import java.util.HashMap;

import org.apache.catalina.loader.ResourceEntry;

/** Classe représentant un fichier jar dans un dossier de partage */
public class FichierJar {

private String nomDossier;
private File file;
private HashMap<String, ResourceEntry> resourceEntries =
new HashMap<String, ResourceEntry>();

/** Constructeur sans paramètres */
public FichierJar() {
}

/** Constructeur avec paramètres */
public FichierJar(final String pNomDossier, final File pFile) {
nomDossier = pNomDossier;
file = pFile;
}

/** Retourne le nom du dossier (correspond à l'attribut nom de Dossier) */
public String getNomDossier() {
return nomDossier;
}

/** Fixe le nom du dossier */
public void setNomDossier(String pNomDossier) {
nomDossier = pNomDossier;
}

/** Retourne le File correspondant au fichier jar*/
public File getFile() {
return file;
}

/** Fixe le File correspondant au fichier jar */
public void setFile(File pFile) {
file = pFile;
}

/** Retourne les ResourceEntry du cache */
public HashMap<String, ResourceEntry> getResourceEntries() {
return resourceEntries;
}

/** Recherche une ResourceEntry dans le cache */
public ResourceEntry getResourceEntry(final String pNomRessource) {
return resourceEntries.get(pNomRessource);
}

/** Ajoute une ResourceEntry dans le cache */
public void ajouteResourceEntry(final String pNomRessource, final ResourceEntry pResourceEntry) {
resourceEntries.put(pNomRessource, pResourceEntry);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FichierJar other = (FichierJar) obj;
if (file == null) {
if (other.file != null)
return false;
} else if (!file.equals(other.file))
return false;
if (nomDossier == null) {
if (other.nomDossier != null)
return false;
} else if (!nomDossier.equals(other.nomDossier))
return false;
return true;
}
}

WebappLoaderAvecPartage.java

package com.developpez.rpouiller.partagelibrairiesversionnees.loader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.loader.WebappLoader;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.digester.Digester;

import com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Application;
import com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Dossier;
import com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Partages;

/** Loader avec partage des librairies versionnées. */
public class WebappLoaderAvecPartage extends WebappLoader {

private final static Log LOG = LogFactory.getLog(WebappLoaderAvecPartage.class);

/**
* Nom du fichier de configuration du partage.
* Le fichier "partages.xml" est dans le dossier "conf" de Tomcat.
*/
protected static String configFile = "conf/partages.xml";

/** Indique si le fichier de configuration a été chargé */
private static boolean configurationChargee = false;

/** Informations des partages. */
private static Partages partages;

/**
* Retourne un objet File object correspondant au fichier de configuration.
* @return L'objet File
*/
protected static File configFile() {
File file = new File(configFile);
if (!file.isAbsolute())
file = new File(System.getProperty("catalina.base"), configFile);
return (file);
}

/**
* Fixe le nom du fichier de configuration du partage.
* @param pConfigFile Le nom.
*/
protected static void setConfigFile(final String pConfigFile) {
configFile = pConfigFile;
}

/**
* Fixe les informations des partages.
* @param pPartages Les informations des partages.
*/
public void setPartages(final Partages pPartages) {
partages = pPartages;
}

/**
* Retourne l'objet "Application" associé au contexte du Loader.
* @return L'objet "Application".
*/
private Application getApplicationContexte() {
final String lNomContexte = getContainer().getName();
for(final Application lApplication : partages.getListeApplications()) {
if(lNomContexte.equals(lApplication.getContexte())) {
return lApplication;
}
}
return null;
}

/**
* Crée et configure le Digester pour le chargement de la configuration.
*/
protected Digester createDigester() {
final Digester lDigester = new Digester();
lDigester.setValidating(false);
lDigester.setRulesValidation(true);

lDigester.addObjectCreate("Partages",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Partages");
lDigester.addSetNext("Partages", "setPartages",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Partages");

lDigester.addObjectCreate("Partages/Dossier",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Dossier");
lDigester.addSetProperties("Partages/Dossier");
lDigester.addSetNext("Partages/Dossier", "addDossier",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Dossier");

lDigester.addObjectCreate("Partages/Application",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Application");
lDigester.addSetProperties("Partages/Application");
lDigester.addSetNext("Partages/Application", "addApplication",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Application");

lDigester.addObjectCreate("Partages/Application/Partage",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Partage");
lDigester.addSetProperties("Partages/Application/Partage");
lDigester.addSetNext("Partages/Application/Partage", "addPartage",
"com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Partage");

return lDigester;
}

/**
* Charge la configuration depuis le fichier CONFIG_FILE
*/
protected synchronized Partages chargerConfiguration() {
if(!configurationChargee) {
configurationChargee = true;

final Digester lDigester = createDigester();
try {
final InputStream lInputStream = new FileInputStream(configFile());
lDigester.push(this);
lDigester.parse(lInputStream);
lInputStream.close();
} catch (FileNotFoundException e) {
LOG.warn("Chargement impossible du fichier de configuration de partage : " + configFile().getAbsolutePath());
} catch (ex.ception e) {
LOG.warn("Erreur lors du chargement de " + configFile() + " : ", e);
}
}

return partages;
}

/**
* Initialise le Loader.
* Comme il s'agit d'une substitution de la méthode de WebappLoader,
* on appelle dans un premier temps la méthode de WebappLoader.
* Puis, on charge la configuration depuis le fichier.
*/
public void init() {
super.init();

chargerConfiguration();
}

/**
* Start this component, initializing our associated class loader.
* Pareil que pour la méthode init(), dans un premier temps,
* on appelle la méthode de WebappLoader.
* @exception LifecycleException if a lifecycle error occurs
*/
public void start() throws LifecycleException {
super.start();

if(partages != null) {
final List<Dossier> lListeDossiers = partages.getListeDossiers();
WebappClassLoaderAvecPartage.setListeDossiers(lListeDossiers);

final Application lApplication = getApplicationContexte();
((WebappClassLoaderAvecPartage)getClassLoader()).setApplication(lApplication);
}
}
}

WebappClassLoaderAvecPartage.java

package com.developpez.rpouiller.partagelibrairiesversionnees.loader;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.naming.NamingException;

import org.apache.catalina.loader.ResourceEntry;
import org.apache.catalina.loader.WebappClassLoader;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.naming.resources.Resource;

import com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Application;
import com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Dossier;
import com.developpez.rpouiller.partagelibrairiesversionnees.loader.config.Partage;

/** ClassLoader avec partage des librairies versionnées. */
public class WebappClassLoaderAvecPartage extends WebappClassLoader {

private final static Log LOG = LogFactory.getLog(WebappClassLoaderAvecPartage.class);

protected static List<FichierJar> listeFichiersJar;

/** Unique instance du filtre */
private static FilenameFilterJar FILENAME_FILTER_JAR = new FilenameFilterJar();

/**
* Filtre de nom de fichier de jar
*/
private static class FilenameFilterJar implements FilenameFilter {

public boolean accept(File dir, String name) {
return name.toUpperCase().endsWith(".JAR");
}

}

private List<FichierJar> listeFichiersJarApplication;

/**
* Détermine les fichiers jar constituant les possibilités de partage
* à partir de la liste des dossiers de la configuration.
*
* Parcours les différents dossiers contenus dans la configuration.
* Stocke les informations liées aux fichiers jar de ces dossiers dans la liste.
*
* @param pListeDossiers
*/
public static synchronized void setListeDossiers(final List<Dossier> pListeDossiers) {
if(listeFichiersJar == null) {
listeFichiersJar = new LinkedList<FichierJar>();

for(final Dossier lDossier : pListeDossiers) {
final String lNomDossier = lDossier.getNom();
final String lCheminDossier = lDossier.getChemin();
final File lFileDossier = new File(lCheminDossier);
if(lFileDossier.exists()) {
LOG.info("Détermination des librairies du dossier de partage '" + lNomDossier + "' (" + lCheminDossier + ")");
final File[] lFichiersJarDuDossier = lFileDossier.listFiles(FILENAME_FILTER_JAR);

for(final File lFichierJarDuDossier : lFichiersJarDuDossier) {
LOG.info("Fichier '" + lFichierJarDuDossier + "' trouvé");

final FichierJar lFichierJar = new FichierJar();

lFichierJar.setNomDossier(lNomDossier);
lFichierJar.setFile(lFichierJarDuDossier);

listeFichiersJar.add(lFichierJar);
}
}
else {
LOG.warn("Le dossier de partage '" + lNomDossier + "' n'a pas été trouvé au chemin '" + lCheminDossier + "'");
}
}
}
}

/**
* Retourne la liste des fichiers jar constituant les possibilités de partage.
* @return La liste des fichiers jar.
*/
protected static List<FichierJar> getListeFichiersJar() {
return listeFichiersJar;
}

/**
* Constructeur sans paramètres.
*/
public WebappClassLoaderAvecPartage() {
super();
}

/**
* Constructeur avec ClassLoader parent
* @param pParent
*/
public WebappClassLoaderAvecPartage(final ClassLoader pParent) {
super(pParent);
}

/**
* Retourne la liste des fichiers jar pour l'application.
* @return La liste des fichiers jar pour l'application
*/
protected List<FichierJar> getListeFichiersJarApplication() {
return listeFichiersJarApplication;
}

/**
* Fixe la configuration du partage pour une application
* correspondant à ce ClassLoader.
*
* Détermine à partir de ces informations les fichiers jar
* qui concernent ce ClassLoader.
*
* Pour être retenu le fichier jar doit appartenir à un dossier
* de partage de cette application et son nom doit correspondre
* à un jar des URL de ce ClassLoader.
*
* Ces URL ont été settées dans la méthode start() de WebappLoader.
*
* @param pApplication
*/
public void setApplication(final Application pApplication) {
listeFichiersJarApplication = new LinkedList<FichierJar>();

LOG.info("Détermination des librairies partagées pour l'application '" + pApplication.getContexte() + "'");

final List<Partage> lListePartages = pApplication.getListePartages();

boucleFichierJar:
for(final FichierJar lFichierJar : listeFichiersJar) {

for(final Partage lPartage : lListePartages) {

// Si le fichier jar appartient à un dossier de partage
// de l'application
if(lFichierJar.getNomDossier().equals(lPartage.getNom())) {

for(final File lJarRealFile : jarRealFiles) {

final String lNomJarRealFile = lJarRealFile.getName();
final String lNomFichierPartage = lFichierJar.getFile().getName();

// Si le fichier jar correspond à un un des fichiers du classpath
// alors on l'ajout à la liste des jar de l'application
// et on passe au fichier jar suivant
if(lNomJarRealFile.equals(lNomFichierPartage)) {
LOG.info("Librairie retenue : dossier : '" + lFichierJar.getNomDossier() + "' - fichier : '" + lFichierJar.getFile() + "'");
listeFichiersJarApplication.add(lFichierJar);
continue boucleFichierJar;
}
}
}
}
}
}

@Override
protected Class findLoadedClass0(final String name) {
// Trouve le fichier jar concerné par la ressource
final String lTempPath = name.replace('.', '/');
final FichierJar lFichierJar = getFichierJarForResource(lTempPath + ".class");

if(lFichierJar != null) {
// Si la ressource est déjà chargée, on la retourne depuis le cache
final ResourceEntry lLoadedResourceEntry = lFichierJar.getResourceEntry(name);
if(lLoadedResourceEntry != null) {
return lLoadedResourceEntry.loadedClass;
}
}

// Passe la main à la classe mère si la ressource n'est pas concernée par le partage
return super.findLoadedClass0(name);
}

@Override
protected InputStream findLoadedResource(final String name) {

// Trouve le fichier jar concerné par la ressource
final FichierJar lFichierJar = getFichierJarForResource(name);

if(lFichierJar != null) {
// Si la ressource est déjà chargée, on la retourne depuis le cache
final ResourceEntry lLoadedResourceEntry = lFichierJar.getResourceEntry(name);
if(lLoadedResourceEntry != null) {
if (lLoadedResourceEntry.binaryContent != null) {
return new ByteArrayInputStream(lLoadedResourceEntry.binaryContent);
}
}
}

// Passe la main à la classe mère si la ressource n'est pas concernée par le partage
return super.findLoadedResource(name);
}

@Override
public URL findResource(final String name) {
// Trouve le fichier jar concernée par la ressource
final FichierJar lFichierJar = getFichierJarForResource(name);

if(lFichierJar != null) {
// Si la ressource est déjà chargée, on la retourne depuis le cache
ResourceEntry lLoadedResourceEntry = lFichierJar.getResourceEntry(name);
// Si elle n'était pas dans le cache, on essaye de la charger
if (lLoadedResourceEntry == null) {
lLoadedResourceEntry = findResourceInternal(name, name);
}
if (lLoadedResourceEntry != null) {
return lLoadedResourceEntry.source;
}
}

// Passe la main à la classe mère si la ressource n'est pas concernée par le partage
return super.findResource(name);
}

@Override
protected ResourceEntry findResourceInternal(final String name, final String path) {

if (!started) {
LOG.info(sm.getString("WebappClassLoaderAvecPartage.stopped", name));
return null;
}

// Trouve le fichier jar concernée par la ressource
final FichierJar lFichierJar = getFichierJarForResource(path);

if(lFichierJar != null) {
synchronized(lFichierJar) {
// Si la ressource est déjà chargée, on la retourne depuis le cache
final ResourceEntry lLoadedResourceEntry = lFichierJar.getResourceEntry(name);
if(lLoadedResourceEntry != null) {
return lLoadedResourceEntry;
}

// Si la ressource n'est pas dans le cache, on la charge depuis le Jar
try {
final File lFile = lFichierJar.getFile();
final JarFile lJarFile = new JarFile(lFile);
final JarEntry lJarEntry = lJarFile.getJarEntry(path);
if(lJarEntry != null) {
final ResourceEntry lResourceEntry = new ResourceEntry();
lResourceEntry.codeBase = getURL(lFile, false);
lResourceEntry.source = new URL("jar:" + getURI(lFile) + "!/" + path);
lResourceEntry.lastModified = lFile.lastModified();
lResourceEntry.manifest = lJarFile.getManifest();
final InputStream lInputStream = lJarFile.getInputStream(lJarEntry);
final byte[] lBinaryContent = new byte[(int)lJarEntry.getSize()];

int lPosition = 0;
while (true) {
int n = lInputStream.read(lBinaryContent, lPosition, lBinaryContent.length - lPosition);
if (n <= 0)
break;
lPosition += n;
}
lInputStream.close();

if(needConvert && path.endsWith(".properties")) {
final String lStringBynaryContent = new String(lBinaryContent, 0, lPosition);
lResourceEntry.binaryContent = lStringBynaryContent.getBytes("UTF-8");
}
else {
lResourceEntry.binaryContent = lBinaryContent;
}

if (lJarEntry != null) {
lResourceEntry.certificates = lJarEntry.getCertificates();
}

lFichierJar.ajouteResourceEntry(name, lResourceEntry);

return lResourceEntry;
}
} catch (IOException e) {
return null;
}
}
}

// Passe la main à la classe mère si la ressource n'est pas concernée par le partage
return super.findResourceInternal(name, path);
}

/**
* Retourne le fichier jar (du WEB-INF/lib par exemple) qui contient la ressource en fonction de son chemin.
* Si la ressource n'est contenue par aucun jar ou qu'elle est trouvée ailleurs
* que dans un jar (ex. : WEB-INF/classes), la méthode retourne null.
* @param pResourcePath Chemin de la ressource
* @return Le fichier jar contenant la ressource
*/
public File getFileContainsResource(final String pResourcePath) {
for (int i=0;i<repositories.length;i++) {
try {
String fullPath = repositories[i] + pResourcePath;
Object lookupResult = resources.lookup(fullPath);
if (lookupResult instanceof Resource) {
return null;
}
} catch (NamingException e) {
}
}
synchronized (jarFiles) {

if (!openJARs()) {
return null;
}
for (int i=0;i<jarFiles.length;i++) {

final JarEntry lJarEntry = jarFiles[i].getJarEntry(pResourcePath);

if (lJarEntry != null) {
return jarRealFiles[i];
}
}
}
return null;
}

/**
* Retourne le fichier jar (FichierJar) de la liste des fichiers jar
* de l'application correspondant à la ressource passée en paramètre.
* @param pResourcePath chemin de la ressource
* @return Le fichier Jar; null si aucun ne correspond.
*/
public FichierJar getFichierJarForResource(final String pResourcePath) {
final File lFile = getFileContainsResource(pResourcePath);
if(lFile != null) {
final String lNomFichier = lFile.getName();
for(final FichierJar lFichierJar : listeFichiersJarApplication) {
final String lNomFichierJar = lFichierJar.getFile().getName();
if(lNomFichierJar.equals(lNomFichier)) {
return lFichierJar;
}
}
}
return null;
}
}

Téléchargement JAR

Téléchargement Sources

ici (Miroir)

ici (Miroir)

III-E-4 - Installation du système de partage

Créer les dossiers « C:\dossierA », « C:\dossierB » et « C:\dossierC ».

Copier les fichiers « versionneeLibA-1.0.jar » et « versionneeLibA-2.0.jar » dans « C:\dossierA ».

Copier les fichiers « versionneeLibB-1.0.jar », « versionneeLibB-2.0.jar » et « versionneeLibB-3.0.jar » dans « C:\dossierB ».

Copier les fichiers « versionneeLibC-1.0.jar » et « versionneeLibC-2.0.jar » dans « C:\dossierC ».

Copier le jar « partagelibrairiesversionnees.jar » dans le dossier « lib » de Tomcat (« C:\Program Files\Apache Software Foundation\Tomcat 6.0\lib »).

Créer le fichier de configuration « partages.xml » dans le dossier « conf » de Tomcat (« C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf »), avec le contenu suivant :

partages.xml

<?xml version='1.0' encoding='utf-8'?>
<Partages>

<Dossier nom="A" chemin="c:\dossierA"/>
<Dossier nom="B" chemin="c:\dossierB"/>
<Dossier nom="C" chemin="c:\dossierC"/>

<Application contexte="/application1">
<Partage nom="A"/>
<Partage nom="B"/>
<Partage nom="C"/>
</Application>
<Application contexte="/application2">
<Partage nom="A"/>
<Partage nom="B"/>
</Application>
<Application contexte="/application3">
<Partage nom="A"/>
<Partage nom="C"/>
</Application>
<Application contexte="/application4">
<Partage nom="A"/>
<Partage nom="B"/>
<Partage nom="C"/>
</Application>

</Partages>

Dans « C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf\Catalina\localhost\ » (créer les dossiers si nécessaire), créer un fichier « application1.xml », « application2.xml » et « application4.xml ». Ces trois fichiers contiennent le code xml ci-dessous :

<?xml version='1.0' encoding='utf-8'?>
<Context>
<Loader className="com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappLoaderAvecPartage"
loaderClass="com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage"
/>
</Context>

Relancer Tomcat après le paramétrage.

Le fichier de log « catalina.[dateDuJour].log » de « C:\Program Files\Apache Software Foundation\Tomcat 6.0\logs » doit contenir les lignes (notamment 9 à 50) ci-dessous.

1. 19 mai 2009 23:35:29 org.apache.coyote.http11.Http11Protocol init

2. INFO: Initialisation de Coyote HTTP/1.1 sur http-8080

3. 19 mai 2009 23:35:29 org.apache.catalina.startup.Catalina load

4. INFO: Initialization processed in 889 ms

5. 19 mai 2009 23:35:29 org.apache.catalina.core.StandardService start

6. INFO: D?marrage du service Catalina

7. 19 mai 2009 23:35:29 org.apache.catalina.core.StandardEngine start

8. INFO: Starting Servlet Engine: Apache Tomcat/6.0.18

9. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

10. INFO: Détermination des librairies du dossier de partage 'A' (c:\dossierA)

11. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

12. INFO: Fichier 'c:\dossierA\versionneeLibA-1.0.jar' trouvé

13. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

14. INFO: Fichier 'c:\dossierA\versionneeLibA-2.0.jar' trouvé

15. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

16. INFO: Détermination des librairies du dossier de partage 'B' (c:\dossierB)

17. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

18. INFO: Fichier 'c:\dossierB\versionneeLibB-1.0.jar' trouvé

19. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

20. INFO: Fichier 'c:\dossierB\versionneeLibB-2.0.jar' trouvé

21. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

22. INFO: Fichier 'c:\dossierB\versionneeLibB-3.0.jar' trouvé

23. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

24. INFO: Détermination des librairies du dossier de partage 'C' (c:\dossierC)

25. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

26. INFO: Fichier 'c:\dossierC\versionneeLibC-1.0.jar' trouvé

27. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setListeDossiers

28. INFO: Fichier 'c:\dossierC\versionneeLibC-2.0.jar' trouvé

29. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

30. INFO: Détermination des librairies partagées pour l'application '/application1'

31. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

32. INFO: Librairie retenue : dossier : 'A' - fichier : 'c:\dossierA\versionneeLibA-1.0.jar'

33. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

34. INFO: Librairie retenue : dossier : 'B' - fichier : 'c:\dossierB\versionneeLibB-1.0.jar'

35. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

36. INFO: Librairie retenue : dossier : 'C' - fichier : 'c:\dossierC\versionneeLibC-1.0.jar'

37. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

38. INFO: Détermination des librairies partagées pour l'application '/application2'

39. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

40. INFO: Librairie retenue : dossier : 'A' - fichier : 'c:\dossierA\versionneeLibA-1.0.jar'

41. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

42. INFO: Librairie retenue : dossier : 'B' - fichier : 'c:\dossierB\versionneeLibB-2.0.jar'

43. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

44. INFO: Détermination des librairies partagées pour l'application '/application4'

45. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

46. INFO: Librairie retenue : dossier : 'A' - fichier : 'c:\dossierA\versionneeLibA-2.0.jar'

47. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

48. INFO: Librairie retenue : dossier : 'B' - fichier : 'c:\dossierB\versionneeLibB-1.0.jar'

49. 19 mai 2009 23:35:29 com.developpez.rpouiller.partagelibrairiesversionnees.loader.WebappClassLoaderAvecPartage setApplication

50. INFO: Librairie retenue : dossier : 'C' - fichier : 'c:\dossierC\versionneeLibC-2.0.jar'

51. 19 mai 2009 23:35:29 org.apache.catalina.startup.HostConfig deployWAR

52. INFO: D?ploiement de l'archive application3.war de l'application web

53. 19 mai 2009 23:35:30 org.apache.coyote.http11.Http11Protocol start

54. INFO: D?marrage de Coyote HTTP/1.1 sur http-8080

55. 19 mai 2009 23:35:30 org.apache.jk.common.ChannelSocket init

56. INFO: JK: ajp13 listening on /0.0.0.0:8009

57. 19 mai 2009 23:35:30 org.apache.jk.server.JkMain start

58. INFO: Jk running ID=0 time=0/47  config=null

59. 19 mai 2009 23:35:30 org.apache.catalina.startup.Catalina start

60. INFO: Server startup in 1429 ms

Les lignes 9 à 28 indiquent la recherche des fichiers jar dans les différents dossiers de partage. Les lignes 29 à 36 indiquent les fichiers jar retenus pour l'application 1. Les lignes 37 à 42 indiquent les fichiers jar retenus pour l'application 2. Les lignes 43 à 50 indiquent les fichiers jar retenus pour l'application 4.

Selon la configuration fixée ici, l'application 1 et l'application 4 partagent les librairies A, B et C. L'application 2 partage les librairies A et B, mais ne partage pas la librairie C. L'application 3 ne profite pas du partage de librairies versionnées, car elle n'utilise pas le ClassLoader avec partage. Si l'application 3 avait utilisé le ClassLoader avec partage, elle aurait partagé les librairies A et C, mais pas la B.

III-F - Lancement des applications avec partage des librairies versionnées

III-F-1 - Lancement de l'application 1

L'URL pour accéder à l'application est : http://localhost:8080/application1/

Le résultat est :

III-F-2 - Lancement de l'application 2

L'URL pour accéder à l'application est : http://localhost:8080/application2/

Le résultat est :

III-F-3 - Lancement de l'application 3

L'URL pour accéder à l'application est : http://localhost:8080/application3/

Le résultat est :

III-F-4 - Lancement de l'application 4

L'URL pour accéder à l'application est : http://localhost:8080/application4/

Le résultat est :

III-F-5 - Bilan du lancement des applications

Les résultats obtenus correspondent à ce qui est attendu en fonction du paramétrage mis en place.

Le comportement reste inchangé en ce qui concerne les classes et ressources issues de librairies communes ou de l'application.

Les classes partagées entre deux applications sont :

- les classes de la version 1.0 de la librairie A entre les applications 1 et 2

- les classes de la version 1.0 de la librairie B entre les applications 1 et 4

On constate que le partage des classes fonctionne. Les lignes VersionneClasse1 et VersionneeClasse2 du résultat de l'application 2, ainsi que les lignes VersionneClasse3 et VersionneeClasse4 du résultat de l'application 4, indiquent l'heure, la servlet et des URL de ClassLoader de première initialisation correspondant à celle de l'application 1.

Les ressources sont bien chargées depuis les librairies des dossiers de partage :

- versionneA.txt, versionneB.txt et versionneC.txt pour l'application 1 et l'application 4

- versionneA.txt et versionneB.txt pour l'application 2

Comme prévu dans la configuration, l'application 3 n'entre pas dans le partage de librairies versionnées.

IV - CONCLUSION

L'article a permis de mettre en évidence la faisabilité du partage de librairies versionnées entre applications sous Tomcat.

Les attributs de classes des classes appartenant à des librairies partagées seront partagés entre les différentes applications partageant une même version de la librairie, comme c'est le cas pour les librairies communes.

V - REMERCIEMENTS

Je remercie sincèrement par ordre plus ou moins chronologique :

- www.developpez.com me permettant de publier cet article.

- Nono40 pour ses outils.

- Baptiste Wicht pour sa relecture technique et ses encouragements.

- Ricky81 pour sa relecture technique et ses encouragements.