Quelle est la différence entre le chargeur de classe de contexte d'un thread et un chargeur de classe normal?
Autrement dit, si Thread.currentThread().getContextClassLoader()
et getClass().getClassLoader()
renvoient des objets de chargeur de classe différents, lequel sera utilisé?
Chaque classe utilisera son propre chargeur de classe pour charger les autres classes. Donc, si ClassA.class
Fait référence à ClassB.class
, Alors ClassB
doit figurer sur le chemin de classe du chargeur de classes de ClassA
ou de ses parents.
Le chargeur de classes de contexte de thread est le chargeur de classes actuel du thread actuel. Un objet peut être créé à partir d'une classe dans ClassLoaderC
, puis transmis à un thread appartenant à ClassLoaderD
. Dans ce cas, l'objet doit utiliser Thread.currentThread().getContextClassLoader()
directement s'il veut charger des ressources qui ne sont pas disponibles sur son propre chargeur de classes.
Un article sur javaworld.com explique la différence => Quel ClassLoader devez-vous utiliser
(1)
Les classloaders de contexte de thread fournissent une porte dérobée autour du schéma de délégation de chargement de classe.
Prenons JNDI par exemple: ses entrailles sont implémentées par bootstrap dans rt.jar (à partir de J2SE 1.3)), mais ces classes JNDI principales peuvent charger des fournisseurs JNDI implémentés par des fournisseurs indépendants et potentiellement déployés application -classpath. Ce scénario demande à un chargeur de classe parent (le principal dans ce cas) de charger une classe visible pour l'un de ses chargeur de classe enfant (celui du système, par exemple). La délégation J2SE normale ne fonctionne pas et la solution de contournement est la suivante: pour que les classes JNDI de base utilisent des chargeurs de contexte de thread, permettant ainsi de "tunneling" dans la hiérarchie du chargeur de classes dans le sens opposé à la délégation appropriée.
(2) de la même source:
Cette confusion va probablement rester avec Java pendant un certain temps. Prenez n'importe quelle API J2SE avec un chargement dynamique des ressources et essayez de deviner quelle stratégie de chargement elle utilise. Voici un exemple:
- JNDI utilise des chargeurs de classes de contexte
- Class.getResource () et Class.forName () utilisent le chargeur de classes actuel.
- JAXP utilise des chargeurs de classes de contexte (à partir de J2SE 1.4)
- Java.util.ResourceBundle utilise le chargeur de classes actuel de l'appelant.
- Les gestionnaires de protocole d'URL spécifiés via la propriété système Java.protocol.handler.pkgs sont recherchés dans le bootstrap et les chargeurs de classe système uniquement).
- L'API de sérialisation Java utilise le chargeur de classes actuel de l'appelant par défaut
Cela ne répond pas à la question initiale, mais comme la question est hautement classée et liée pour toute requête ContextClassLoader
, je pense qu'il est important de répondre à la question connexe de savoir quand le chargeur de classe de contexte doit être utilisé. Réponse courte: n'utilisez jamais le chargeur de classe de contexte ! Mais définissez-le sur getClass().getClassLoader()
lorsque vous devez appeler une méthode pour laquelle il manque un paramètre ClassLoader
.
Lorsque le code d'une classe demande de charger une autre classe, , le chargeur de classe correct à utiliser est le même chargeur de classe que la classe de l'appelant (c'est-à-dire, getClass().getClassLoader()
). C'est ainsi que les choses fonctionnent 99,9% du temps car c'est ce que fait la machine virtuelle elle-même la première fois que vous construisez une instance d'une nouvelle classe, appelez une méthode statique ou accédez à un champ statique.
Lorsque vous souhaitez créer une classe à l'aide de la réflexion (par exemple lors de la désérialisation ou du chargement d'une classe nommée configurable), la bibliothèque qui effectue la réflexion doit toujours demander à l'application quel chargeur de classe utiliser, en recevant le paramètre ClassLoader
de l’application. L'application (qui connaît toutes les classes nécessitant une construction) doit la transmettre getClass().getClassLoader()
.
Toute autre manière d'obtenir un chargeur de classe est incorrecte. Si une bibliothèque utilise des hacks tels que Thread.getContextClassLoader()
, Sun.misc.VM.latestUserDefinedLoader()
ou Sun.reflect.Reflection.getCallerClass()
c'est un bug causé par une déficience de l'API. Fondamentalement, Thread.getContextClassLoader()
n'existe que parce que le créateur de l'API ObjectInputStream
a oublié d'accepter le paramètre ClassLoader
, et cette erreur a hanté la communauté Java. à ce jour.
Cela dit, de nombreuses classes JDK utilisent l’un des quelques outils pour deviner l’utilisation d’un chargeur de classes. Certains utilisent le ContextClassLoader
(qui échoue lorsque vous exécutez différentes applications sur un pool de threads partagé ou lorsque vous quittez le ContextClassLoader null
), D'autres parcourent la pile (ce qui échoue lorsque l'appelant direct de la classe est lui-même une bibliothèque), certains utilisent le chargeur de classes système (ce qui est correct, à condition qu'il soit documenté de n'utiliser que des classes dans le chargeur de classes CLASSPATH
) ou bootstrap, et certains utilisent une combinaison imprévisible des techniques ci-dessus (ce qui ne fait que rendre les choses plus confuses). Cela a entraîné beaucoup de pleurs et de grincements de dents.
commencez par rechercher une surcharge de la méthode qui accepte le chargeur de classe en tant que paramètre . S'il n'y a pas de méthode appropriée, essayez de définir le paramètre ContextClassLoader
avant l'appel de l'API (et de le réinitialiser par la suite):
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
// call some API that uses reflection without taking ClassLoader param
} finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
En ajoutant à la réponse de @David Roussel, les classes peuvent être chargées par plusieurs chargeurs de classes.
Permet de comprendre comment chargeur de classe fonctionne.
De javin paul blog dans javarevisited:
ClassLoader
suit trois principes.
Une classe est chargée en Java, le cas échéant. Supposons que vous ayez une classe spécifique à l'application appelée Abc.class, la première demande de chargement de cette classe parviendra à Application ClassLoader qui déléguera à son extension Extension ClassLoader parent qui délègue d'autres délégués à Primordial ou Bootstrap chargeur de classe
Bootstrap ClassLoader est chargé du chargement des fichiers de classe JDK standard à partir de rt.jar et est le parent de tous les chargeurs de classes en Java. Bootstrap) n'a aucun parent.
Extension ClassLoader délègue la demande de chargement de classe à son parent, Bootstrap et en cas d'échec, charge le répertoire jre/lib/ext sous forme de classe ou tout autre répertoire désigné par Java. Propriété système ext.dirs
Chargeur de classe système ou d'application et est responsable du chargement des classes spécifiques à l'application à partir de la variable d'environnement CLASSPATH, de l'option -classpath ou de l'option de ligne de commande -cp, de l'attribut Class-Path du fichier manifeste dans le fichier JAR.
Chargeur de classe d'application est un enfant de Extension ClassLoader et est implémenté par Sun.misc.Launcher$AppClassLoader
classe.
REMARQUE: Sauf Chargeur de classe d'amorçage, implémenté dans la langue maternelle principalement en C, tous Java sont implémentés en utilisant Java.lang.ClassLoader
.
Selon le principe de visibilité, ClassLoader enfant peut voir la classe chargée par ClassLoader parent mais l'inverse n'est pas vrai.
Selon ce principe, une classe chargée par Parent ne devrait plus être chargée par Child ClassLoader