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▲
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 :
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.
<?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>
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
(
" "
+
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. |
findClassInternal(String) |
Recherche la classe dont le nom est spécifié en paramètre dans les repositories locaux. |
findLoadedClass0(String) |
Recherche la classe dont le nom est spécifié en paramètre dans le cache. |
findLoadedResource(String) |
Recherche la ressource dont le nom est spécifié en paramètre dans cache. |
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. |
findResourceInternal(String, String) |
Recherche la ressource dont le nom et le chemin sont spécifiés en paramètres dans les repositories locaux. |
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. |
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. |
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. |