Unterschied zwischen dem Kontextklassenlader des Threads und dem normalen Klassenlader

242

Was ist der Unterschied zwischen dem Kontextklassenlader eines Threads und einem normalen Klassenlader?

Das heißt, wenn Thread.currentThread().getContextClassLoader()und getClass().getClassLoader()verschiedene Klassenlade Objekte zurück, die man verwendet wird?

Abrakadabra
quelle

Antworten:

151

Jede Klasse verwendet einen eigenen Klassenladeprogramm, um andere Klassen zu laden. Also , wenn ClassA.classReferenzen ClassB.classdann ClassBmuss sein auf dem Classpath des Classloader von ClassAoder seinen Eltern.

Der Thread-Kontext-Klassenlader ist der aktuelle Klassenlader für den aktuellen Thread. Ein Objekt kann aus einer Klasse in erstellt ClassLoaderCund dann an einen Thread übergeben werden, dessen Eigentümer es ist ClassLoaderD. In diesem Fall muss das Objekt Thread.currentThread().getContextClassLoader()direkt verwendet werden, wenn Ressourcen geladen werden sollen, die auf seinem eigenen Klassenladeprogramm nicht verfügbar sind.

David Roussel
quelle
1
Warum ClassBmuss sich das auf dem Klassenpfad des ClassALaders (oder ClassAder Eltern des Laders) befinden? Kann der ClassALoader nicht überschrieben werden loadClass(), sodass er erfolgreich geladen werden kann, ClassBauch wenn er ClassBsich nicht in seinem Klassenpfad befindet?
Pacerier
8
In der Tat haben nicht alle Klassenlader einen Klassenpfad. Als geschrieben wurde "ClassB muss sich im Klassenpfad des Classloaders von ClassA befinden", meinte ich "ClassB muss vom Classloader von ClassA geladen werden können". In 90% der Fälle meinen sie dasselbe. Wenn Sie jedoch keinen URL-basierten Klassenladeprogramm verwenden, ist nur der zweite Fall wahr.
David Roussel
Was bedeutet es zu sagen, dass " ClassA.classReferenzen ClassB.class"?
Jameshfisher
1
Wenn ClassA eine Importanweisung für ClassB hat oder wenn es in ClassA eine Methode gibt, die eine lokale Variable vom Typ ClassB hat. Dadurch wird ClassB geladen, sofern es noch nicht geladen ist.
David Roussel
Ich denke, mein Problem hängt mit diesem Thema zusammen. Was denkst du über meine Sollution? Ich weiß, dass es kein gutes Muster ist, aber ich habe keine andere Idee, wie ich es beheben kann: stackoverflow.com/questions/29238493/…
Marcin Erbel
109

Dies beantwortet nicht die ursprüngliche Frage, aber da die Frage für jede ContextClassLoaderAbfrage einen hohen Rang hat und verknüpft ist , halte ich es für wichtig, die zugehörige Frage zu beantworten, wann der Kontextklassenlader verwendet werden sollte. Kurze Antwort: Verwenden Sie niemals den Kontextklassenlader ! Stellen Sie es jedoch auf ein, getClass().getClassLoader()wenn Sie eine Methode aufrufen müssen, bei der ein ClassLoaderParameter fehlt .

Wenn Code aus einer Klasse zum Laden einer anderen Klasse auffordert, ist der richtige Klassenlader derselbe Klassenlader wie die aufrufende Klasse (dh getClass().getClassLoader()). Auf diese Weise funktionieren die Dinge in 99,9% der Fälle, da die JVM dies selbst tut, wenn Sie zum ersten Mal eine Instanz einer neuen Klasse erstellen, eine statische Methode aufrufen oder auf ein statisches Feld zugreifen.

Wenn Sie eine Klasse mithilfe von Reflection erstellen möchten (z. B. beim Deserialisieren oder Laden einer konfigurierbaren benannten Klasse), sollte die Bibliothek, die die Reflektion ausführt, die Anwendung immer fragen, welchen Klassenlader sie verwenden soll, indem sie den ClassLoaderals Parameter von der Anwendung empfängt . Die Anwendung (die alle Klassen kennt, die erstellt werden müssen) sollte sie bestehen getClass().getClassLoader().

Jede andere Möglichkeit, einen Klassenlader zu erhalten, ist falsch. Wenn eine Bibliothek verwendet Hacks wie Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader()oder sun.reflect.Reflection.getCallerClass()es ist ein durch einen Mangel in der API verursacht Fehler. Grundsätzlich Thread.getContextClassLoader()existiert nur, weil derjenige, der die ObjectInputStreamAPI entworfen hat, vergessen hat, den ClassLoaderals Parameter zu akzeptieren , und dieser Fehler die Java-Community bis heute verfolgt hat.

Das heißt, viele, viele JDK-Klassen verwenden einen der wenigen Hacks, um einen zu verwendenden Klassenlader zu erraten. Einige verwenden das ContextClassLoader(was fehlschlägt, wenn Sie verschiedene Apps in einem gemeinsam genutzten Thread-Pool ausführen oder wenn Sie das verlassen ContextClassLoader null), andere gehen den Stapel entlang (was fehlschlägt, wenn der direkte Aufrufer der Klasse selbst eine Bibliothek ist), andere verwenden den Systemklassenlader (was in Ordnung ist, solange dokumentiert ist, dass nur Klassen im CLASSPATH) oder Bootstrap-Klassenladeprogramm verwendet werden, und einige verwenden eine unvorhersehbare Kombination der oben genannten Techniken (was die Dinge nur verwirrender macht). Dies hat zu viel Weinen und Zähneknirschen geführt.

Versuchen Sie bei Verwendung einer solchen API zunächst, eine Überladung der Methode zu finden, die den Klassenlader als Parameter akzeptiert . Wenn es keine sinnvolle Methode gibt, versuchen Sie, die ContextClassLoadervor dem API-Aufruf festzulegen (und danach zurückzusetzen):

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
quelle
5
Ja, dies ist die Antwort, auf die ich jeden hinweisen möchte, der die Frage stellt.
Marko Topolnik
6
Diese Antwort konzentriert sich auf die Verwendung des Klassenladeprogramms zum Laden von Klassen (um sie durch Reflexion oder ähnliches zu instanziieren), während ein anderer Zweck, für den er verwendet wird (und tatsächlich der einzige Zweck, für den ich ihn jemals persönlich verwendet habe), das Laden von Ressourcen ist. Gilt das gleiche Prinzip oder gibt es Situationen, in denen Sie Ressourcen über den Kontextklassenlader und nicht über den Aufruferklassenlader abrufen möchten?
Egor Hans
90

Auf javaworld.com gibt es einen Artikel, der den Unterschied erklärt => Welchen ClassLoader sollten Sie verwenden?

(1)

Thread-Kontext-Klassenladeprogramme bieten eine Hintertür um das Delegierungsschema für das Laden von Klassen.

Nehmen wir zum Beispiel JNDI: Die Eingeweide werden von Bootstrap-Klassen in rt.jar implementiert (beginnend mit J2SE 1.3), aber diese JNDI-Kernklassen können JNDI-Anbieter laden, die von unabhängigen Anbietern implementiert und möglicherweise im Klassenpfad der Anwendung bereitgestellt werden. In diesem Szenario muss ein übergeordneter Klassenladeprogramm (in diesem Fall das ursprüngliche) eine Klasse laden, die für einen seiner untergeordneten Klassenladeprogramme (z. B. das System) sichtbar ist. Die normale J2SE-Delegierung funktioniert nicht, und die Problemumgehung besteht darin, die JNDI-Kernklassen dazu zu bringen, Thread-Kontextlader zu verwenden, wodurch die Classloader-Hierarchie effektiv entgegen der richtigen Delegierung "getunnelt" wird.

(2) aus derselben Quelle:

Diese Verwirrung wird wahrscheinlich einige Zeit bei Java bleiben. Nehmen Sie eine J2SE-API mit dynamischem Laden von Ressourcen jeglicher Art und versuchen Sie zu erraten, welche Ladestrategie verwendet wird. Hier ist eine Auswahl:

  • JNDI verwendet Kontextklassenlader
  • Class.getResource () und Class.forName () verwenden den aktuellen Klassenladeprogramm
  • JAXP verwendet Kontextklassenlader (ab J2SE 1.4)
  • java.util.ResourceBundle verwendet den aktuellen Klassenladeprogramm des Aufrufers
  • Über die Systemeigenschaft java.protocol.handler.pkgs angegebene URL-Protokollhandler werden nur in den Bootstrap- und Systemklassenladern nachgeschlagen
  • Die Java Serialization API verwendet standardmäßig den aktuellen Klassenladeprogramm des Aufrufers
Timour
quelle
Da vorgeschlagen wird, dass die Problemumgehung darin besteht, dass die JNDI-Kernklassen Thread-Kontextlader verwenden, habe ich nicht verstanden, wie dies in diesem Fall hilft. Wir möchten die Herstellerklassen der Implementierung mit dem übergeordneten Klassenladeprogramm laden, sie sind jedoch für das übergeordnete Klassenladeprogramm nicht sichtbar . Wie können wir sie also mit parent überladen, selbst wenn wir diesen übergeordneten Klassenlader im Kontextklassenlader des Threads festlegen?
Sunny Gupta
6
@SAM, die vorgeschlagene Problemumgehung ist eigentlich das Gegenteil von dem, was Sie am Ende sagen. Es ist nicht der übergeordnete bootstrapKlassenlader, der als Kontextklassenlader festgelegt wird, sondern der systemuntergeordnete Klassenpfadklassenlader, mit dem Threadder eingerichtet wird. Die JNDIKlassen stellen dann sicher, dass sie Thread.currentThread().getContextClassLoader()zum Laden der im Klassenpfad verfügbaren JNDI-Implementierungsklassen verwendet werden.
Ravi Thapliyal
"Normale J2SE-Delegierung funktioniert nicht", kann ich wissen, warum es nicht funktioniert? Weil der Bootstrap ClassLoader nur Klassen aus rt.jar laden kann und die Klasse nicht aus dem Klassenpfad der Anwendung laden kann? Richtig?
YuFeng Shen
37

Zusätzlich zur Antwort von @David Roussel können Klassen von mehreren Klassenladeprogrammen geladen werden.

Lassen Sie uns verstehen, wie Class Loader funktioniert.

Von javin paul blog in javarevisited:

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

ClassLoader folgt drei Prinzipien.

Delegationsprinzip

Eine Klasse wird bei Bedarf in Java geladen. Angenommen, Sie haben eine anwendungsspezifische Klasse namens Abc.class. Die erste Anforderung zum Laden dieser Klasse geht an Application ClassLoader, der an den übergeordneten Extension ClassLoader delegiert, der weiter an Primordial oder Bootstrap Class Loader delegiert

  • Bootstrap ClassLoader ist für das Laden von Standard-JDK-Klassendateien aus rt.jar verantwortlich und übergeordnet zu allen Klassenladeprogrammen in Java. Der Bootstrap-Klassenlader hat keine Eltern.

  • Die Erweiterung ClassLoader delegiert die Klassenladeanforderung an das übergeordnete Bootstrap und lädt, falls dies nicht erfolgreich ist, die Klasse aus dem Verzeichnis jre / lib / ext oder einem anderen Verzeichnis, auf das die Systemeigenschaft java.ext.dirs verweist

  • Der System- oder Anwendungsklassenlader ist für das Laden anwendungsspezifischer Klassen aus der Umgebungsvariablen CLASSPATH, der Befehlszeilenoption -classpath oder -cp sowie dem Klassenpfadattribut der Manifestdatei in JAR verantwortlich.

  • Application Class Loader ist ein untergeordnetes Element von Extension ClassLoader und wird von der sun.misc.Launcher$AppClassLoaderKlasse implementiert .

ANMERKUNG: Mit Ausnahme des Bootstrap-Klassenladeprogramms , das hauptsächlich in C in der Muttersprache implementiert ist, werden alle Java-Klassenladeprogramme mit implementiert java.lang.ClassLoader.

Sichtbarkeitsprinzip

Gemäß dem Sichtbarkeitsprinzip kann Child ClassLoader die von Parent ClassLoader geladene Klasse sehen, aber umgekehrt ist dies nicht der Fall.

Prinzip der Einzigartigkeit

Nach diesem Prinzip sollte eine von Parent geladene Klasse nicht erneut von Child ClassLoader geladen werden

Ravindra Babu
quelle