Différence entre le chargeur de classe de contexte du thread et le chargeur de classe normal

242

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()renvoyez différents objets de chargeur de classe, lequel sera utilisé?

abracadabra
la source

Réponses:

151

Chaque classe utilisera son propre chargeur de classe pour charger d'autres classes. Donc , si les ClassA.classréférences ClassB.classalors ClassBbesoin d' être sur le chemin de classe du classloader ClassAou ses parents.

Le chargeur de classe de contexte de thread est le chargeur de classe actuel pour le thread actuel. Un objet peut être créé à partir d'une classe dans ClassLoaderC, puis passé à un thread appartenant à ClassLoaderD. Dans ce cas, l'objet doit être utilisé Thread.currentThread().getContextClassLoader()directement s'il souhaite charger des ressources qui ne sont pas disponibles sur son propre chargeur de classe.

David Roussel
la source
1
Pourquoi dites-vous que cela ClassBdoit être sur le chemin de classe du ClassAchargeur (ou ClassAdes parents du chargeur)? N'est-il pas possible pour ClassAle chargeur de surcharger loadClass(), de sorte qu'il puisse se charger avec succès ClassBmême s'il ClassBn'est pas sur son chemin de classe?
Pacerier
8
En effet, tous les chargeurs de classe n'ont pas de chemin de classe. Quand écrit "ClassB doit être sur le chemin de classe du chargeur de classe de ClassA", je voulais dire "ClassB doit être chargeable par le chargeur de classe de ClassA". 90% du temps, ils signifient la même chose. Mais si vous n'utilisez pas de chargeur de classe basé sur une URL, seul le deuxième cas est vrai.
David Roussel
Que signifie dire « ClassA.classréférences ClassB.class»?
jameshfisher
1
Lorsque ClassA a une instruction d'importation pour ClassB, ou s'il existe une méthode dans ClassA qui a une variable locale de type ClassB. Cela déclenchera le chargement de ClassB, s'il n'est pas déjà chargé.
David Roussel
Je pense que mon problème est lié à ce sujet. Que pensez-vous de ma sollution? Je sais que ce n'est pas un bon modèle, mais je n'ai aucune autre idée de comment le réparer: stackoverflow.com/questions/29238493/…
Marcin Erbel
109

Cela ne répond pas à la question d'origine, mais comme la question est hautement classée et liée à toute ContextClassLoaderrequête, 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 à laquelle il manque un ClassLoaderparamètre.

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 appelante (c.-à getClass().getClassLoader()-d.). C'est ainsi que les choses fonctionnent 99,9% du temps car c'est ce que la JVM fait elle-même la première fois que vous construisez une instance d'une nouvelle classe, invoquez une méthode statique ou accédez à un champ statique.

Lorsque vous souhaitez créer une classe à l'aide de la réflexion (comme 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 ClassLoadercomme paramètre de l'application. L'application (qui connaît toutes les classes qui ont besoin d'être construites) doit la passer getClass().getClassLoader().

Toute autre façon 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()s'il s'agit d'un bogue provoqué par une déficience de l'API. Fondamentalement, cela Thread.getContextClassLoader()n'existe que parce que celui qui a conçu l' ObjectInputStreamAPI a oublié d'accepter le ClassLoaderparamètre, et cette erreur a hanté la communauté Java à ce jour.

Cela dit, de nombreuses classes JDK utilisent l'un des quelques hacks pour deviner quel chargeur de classe utiliser. 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), certains parcourent la pile (qui échoue lorsque l'appelant direct de la classe est lui-même une bibliothèque), certains utilisent le chargeur de classe système (ce qui est bien, tant qu'il est documenté d'utiliser uniquement des classes dans le CLASSPATH) ou le chargeur de classe 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.

Lorsque vous utilisez une telle API, essayez d'abord de trouver une surcharge de la méthode qui accepte le chargeur de classe comme paramètre . S'il n'y a pas de méthode sensée, essayez de définir ContextClassLoaderavant l'appel de l'API (et de le réinitialiser après):

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);
}
yonran
la source
5
Oui, c'est la réponse que je voudrais signaler à quiconque pose la question.
Marko Topolnik
6
Cette réponse se concentre sur l'utilisation du chargeur de classes pour charger des classes (pour les instancier par réflexion ou similaire), tandis qu'un autre but pour lequel il est utilisé (et, en fait, le seul but pour lequel je l'ai utilisé personnellement) est le chargement de ressources. Le même principe s'applique-t-il ou existe-t-il des situations dans lesquelles vous souhaitez récupérer des ressources via le chargeur de classe de contexte plutôt que le chargeur de classe d'appelant?
Egor Hans
90

Il y a un article sur javaworld.com qui explique la différence => Quel ClassLoader utiliser

(1)

Les chargeurs de classe de contexte de thread fournissent une porte dérobée autour du schéma de délégation de chargement de classe.

Prenons l'exemple de JNDI: ses tripes sont implémentées par des classes d'amorçage dans rt.jar (à partir de J2SE 1.3), mais ces classes JNDI de base peuvent charger des fournisseurs JNDI implémentés par des fournisseurs indépendants et potentiellement déployés dans le chemin d'accès aux classes de l'application. Ce scénario appelle un chargeur de classe parent (le primordial dans ce cas) pour charger une classe visible par l'un de ses chargeurs de classe enfant (le système, par exemple). La délégation J2SE normale ne fonctionne pas et la solution de contournement consiste à faire en sorte que les classes JNDI principales utilisent des chargeurs de contexte de threads, ce qui permet de "tunneler" efficacement 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 restera probablement avec Java pendant un certain temps. Prenez n'importe quelle API J2SE avec un chargement de ressources dynamique de toute nature et essayez de deviner quelle stratégie de chargement elle utilise. Voici un échantillon:

  • JNDI utilise des chargeurs de classe de contexte
  • Class.getResource () et Class.forName () utilisent le chargeur de classe actuel
  • JAXP utilise des chargeurs de classe de contexte (à partir de J2SE 1.4)
  • java.util.ResourceBundle utilise le chargeur de classe actuel de l'appelant
  • Les gestionnaires de protocole URL spécifiés via la propriété système java.protocol.handler.pkgs sont recherchés uniquement dans les chargeurs de démarrage et de classe système
  • L'API de sérialisation Java utilise le chargeur de classe actuel de l'appelant par défaut
Timour
la source
Comme il est suggéré que la solution de contournement consiste à faire en sorte que les classes JNDI de base utilisent des chargeurs de contexte de thread, je n'ai pas compris comment cela aide dans ce cas. . Alors, comment pouvons-nous les charger en utilisant le parent, même si nous définissons ce chargeur de classe parent dans le chargeur de classe de contexte du thread.
Sunny Gupta
6
@SAM, la solution de contournement suggérée est en fait tout à fait le contraire de ce que vous dites à la fin. Ce n'est pas le bootstrapchargeur de classe parent qui est défini en tant que chargeur de classe de contexte, mais le systemchargeur de classe de chemin de classe enfant avec lequel Threadest configuré. Les JNDIclasses s'assurent ensuite d'utiliser Thread.currentThread().getContextClassLoader()pour charger les classes d'implémentation JNDI disponibles sur le chemin de classe.
Ravi Thapliyal
"La délégation J2SE normale ne fonctionne pas", puis-je savoir pourquoi cela ne fonctionne pas? Parce que le Bootstrap ClassLoader peut uniquement charger la classe à partir de rt.jar et ne peut pas charger la classe à partir du -classpath de l'application? Droite?
YuFeng Shen
37

En plus de la réponse de @David Roussel, les classes peuvent être chargées par plusieurs chargeurs de classe.

Permet de comprendre le fonctionnement du chargeur de classe .

Du blog javin paul dans javarevisited:

entrez la description de l'image ici

entrez la description de l'image ici

ClassLoader suit trois principes.

Principe de délégation

Une classe est chargée en Java, lorsque cela est nécessaire. Supposons que vous ayez une classe spécifique à l'application appelée Abc.class, la première demande de chargement de cette classe arrivera à Application ClassLoader qui déléguera à son parent Extension ClassLoader qui déléguera davantage au chargeur de classe Primordial ou Bootstrap

  • Bootstrap ClassLoader est responsable du chargement des fichiers de classe JDK standard à partir de rt.jar et il est le parent de tous les chargeurs de classe en Java. Le chargeur de classe Bootstrap n'a pas de parents.

  • L'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 du formulaire de classe ou tout autre répertoire pointé par la propriété système java.ext.dirs

  • Chargeur de classe système ou d'application et il est chargé de charger des classes spécifiques à l'application à partir de la variable d'environnement CLASSPATH, de l'option de ligne de commande -classpath ou -cp, de l'attribut Class-Path du fichier Manifest dans JAR.

  • Le chargeur de classe d'application est un enfant d' Extension ClassLoader et son implémenté par sun.misc.Launcher$AppClassLoaderclasse.

REMARQUE: à l' exception du chargeur de classe Bootstrap , qui est implémenté en langage natif principalement en C, tous les chargeurs de classe Java sont implémentés à l'aide de java.lang.ClassLoader.

Principe de visibilité

Selon le principe de visibilité, le Child ClassLoader peut voir la classe chargée par le Parent ClassLoader mais l'inverse n'est pas vrai.

Principe d'unicité

Selon ce principe, une classe chargée par Parent ne devrait plus être chargée par Child ClassLoader

Ravindra babu
la source