309 votes

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

Quelle est la différence entre le chargeur de classe contextuel d'un thread et un chargeur de classe normal ?

En d'autres termes, si Thread.currentThread().getContextClassLoader() y getClass().getClassLoader() renvoient des objets de chargement de classe différents, lequel sera utilisé ?

200voto

yonran Points 6952

Cela ne répond pas à la question initiale, mais étant donné que la question est très bien classée et liée à n'importe quelle autre question, il est possible d'y répondre. ContextClassLoader Je pense qu'il est important de répondre à la question connexe de savoir quand le chargeur de classes contextuelles doit être utilisé. Réponse courte : ne jamais utiliser le chargeur de classe contextuel ! Mais réglez-le sur getClass().getClassLoader() lorsque vous devez appeler une méthode à laquelle il manque un ClassLoader paramètre.

Lorsque le code d'une classe demande à 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 dans 99,9 % des cas, car c'est ce que fait la JVM elle-même la première fois que vous construisez une instance d'une nouvelle classe, que vous invoquez une méthode statique ou que vous accédez à un champ statique.

Lorsque vous souhaitez créer une classe en utilisant 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 le chargeur de classe à utiliser, en recevant le fichier ClassLoader en tant que paramètre de l'application. L'application (qui connaît toutes les classes qui doivent être construites) doit lui passer getClass().getClassLoader() .

Toute autre façon d'obtenir un chargeur de classe est incorrecte. Si une bibliothèque utilise des astuces telles que Thread.getContextClassLoader() , sun.misc.VM.latestUserDefinedLoader() ou sun.reflect.Reflection.getCallerClass() il s'agit d'un bogue causé par une déficience de l'API. En fait, il s'agit d'un bogue causé par une déficience de l'API, Thread.getContextClassLoader() n'existe que parce que celui qui a conçu le ObjectInputStream L'API a oublié d'accepter le ClassLoader en tant que paramètre, et cette erreur a hanté la communauté Java jusqu'à ce jour.

Cela dit, de nombreuses classes du JDK utilisent l'une de ces quelques astuces pour deviner le chargeur de classe à utiliser. Certaines utilisent la méthode ContextClassLoader (qui échoue lorsque vous exécutez différentes applications sur un pool de threads partagés, ou lorsque vous laissez la fonction ContextClassLoader null ), d'autres utilisent la pile (ce qui échoue lorsque l'appelant direct de la classe est lui-même une bibliothèque), d'autres encore utilisent le chargeur de classe du système (ce qui est très bien, tant qu'il est documenté pour n'utiliser que des classes dans le répertoire 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). Il en résulte beaucoup de pleurs et de grincements de dents.

Lors de l'utilisation d'une telle API, d'abord, essayer 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 le paramètre ContextClassLoader avant l'appel à l'API (et 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);
}

175voto

David Roussel Points 2019

Chaque classe utilisera son propre classloader pour charger d'autres classes. Ainsi, si ClassA.class les références ClassB.class puis ClassB doit se trouver sur le classpath du classloader de ClassA ou ses parents.

Le classloader du contexte du thread est le classloader actuel du thread en cours. 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 souhaite charger des ressources qui ne sont pas disponibles dans son propre classloader.

1 votes

Pourquoi dites-vous que ClassB doit se trouver sur le classpath de ClassA (ou ClassA (parents du chargeur) ? N'est-il pas possible que ClassA pour remplacer le chargeur de loadClass() de telle sorte qu'il puisse charger avec succès ClassB même lorsque ClassB n'est pas sur son chemin d'accès (classpath) ?

11 votes

En effet, tous les classloaders n'ont pas de classpath. Lorsque j'ai écrit "ClassB doit être sur le classpath du classloader de ClassA", je voulais dire "ClassB doit être chargeable par le classloader de ClassA". Dans 90 % des cas, ces deux expressions ont la même signification. Mais si vous n'utilisez pas un classloader basé sur une URL, alors seul le deuxième cas est vrai.

0 votes

Qu'est-ce que cela signifie de dire que " ClassA.class les références ClassB.class " ?

97voto

mtim Points 1212

Un article sur infoworld.com explique la différence => Quel ClassLoader utiliser ?

(1)

Les chargeurs de classe de contexte de threads fournissent un une porte dérobée pour contourner le schéma de de classloading.

Prenons l'exemple de JNDI : ses éléments essentiels sont implémentés par des classes d'amorçage dans rt.jar (à partir de J2SE 1.3), mais ces classes JNDI de base peuvent charger des fichiers JNDI. ces classes JNDI de base peuvent charger des fichiers JNDI mis en œuvre par des fournisseurs indépendants et indépendants et potentiellement déployés dans dans le -classpath de l'application. Ce Ce scénario nécessite un classloader classloader (celui qui est primordial dans primordial) de charger une classe visible par l'un de ses l'un de ses classloaders enfants (celui du système, par exemple). Normalement, J2SE ne fonctionne pas, et la délégation solution consiste à faire en sorte que les classes JNDI utilisent des chargeurs de contexte de threads, ce qui a pour effet de créer un "tunnel" à travers la hiérarchie des chargeurs de classe dans le dans la direction opposée à celle de la délégation.

(2) de la même source :

Cette confusion restera probablement dans les mémoires. Java pendant un certain temps. Prenez n'importe quelle API J2SE J2SE avec un chargement dynamique de ressources et essayez de deviner quelle de chargement qu'elle utilise. En voici un échantillon :

  • JNDI utilise des chargeurs de classe contextuels
  • Class.getResource() et Class.forName() utilisent le classloader actuel
  • JAXP utilise des chargeurs de classe contextuels (à partir de J2SE 1.4)
  • java.util.ResourceBundle utilise le classloader 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 classloaders bootstrap et système.
  • L'API de sérialisation Java utilise par défaut le classloader actuel de l'appelant.

0 votes

Comme il est suggéré que la solution consiste à faire en sorte que les classes JNDI de base utilisent des chargeurs de contexte de thread, je n'ai pas compris en quoi cela pouvait être utile dans ce cas. Nous voulons charger les classes de fournisseurs d'implémentation à l'aide du classloader parent, mais elles ne sont pas visibles par le classloader parent.

6 votes

@SAM, la solution suggérée est en fait tout à fait opposée à ce que vous dites à la fin. Ce n'est pas le parent bootstrap étant défini comme le chargeur de classe du contexte, mais le chargeur de classe de l'enfant system que le chargeur de classe Thread est mis en place. Les JNDI s'assurent alors de l'utilisation de Thread.currentThread().getContextClassLoader() pour charger les classes d'implémentation JNDI disponibles sur le chemin d'accès (classpath).

0 votes

"La délégation normale de J2SE ne fonctionne pas", puis-je savoir pourquoi elle ne fonctionne pas ? Parce que le Bootstrap ClassLoader ne peut charger que les classes de rt.jar et ne peut pas charger les classes du -classpath de l'application ? C'est ça ?

45voto

Ravindra babu Points 5571

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

Comprenons comment chargeur de classe œuvre.

Du blog de javin paul dans javarevisited :

enter image description here

enter image description here

ClassLoader suit trois principes.

Principe de délégation

Une classe est chargée en Java lorsqu'elle est nécessaire. Supposons que vous ayez une classe spécifique à une application appelée Abc.class, la première demande de chargement de cette classe viendra à Application ClassLoader qui déléguera à son parent Extension ClassLoader qui délègue à son tour à Primordial ou Bootstrap class loader.

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

  • Extension ClassLoader délègue la demande de chargement de classe à son parent, Bootstrap, et en cas d'échec, charge la classe à partir du répertoire jre/lib/ext ou de tout autre répertoire indiqué par la propriété système java.ext.dirs.

  • Chargeur de classe du système ou de l'application et il est chargé de charger les 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 à l'intérieur du JAR.

  • Chargeur de classe d'application est un enfant de Extension ClassLoader et sa mise en œuvre par sun.misc.Launcher$AppClassLoader classe.

NOTE : Sauf Chargeur de classe Bootstrap qui est mis en œuvre en langage natif, principalement en C, tous les chargeurs de classe Java sont mis en œuvre à l'aide de java.lang.ClassLoader .

Principe de visibilité

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

Principe d'unicité

Selon ce principe, une classe chargée par le parent ne doit pas être chargée à nouveau par le ClassLoader de l'enfant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X