Differenza tra il caricatore di classi di contesto del thread e il caricatore di classi normale


242

Qual è la differenza tra un caricatore di classi di contesto di un thread e un caricatore di classi normale?

Cioè, se Thread.currentThread().getContextClassLoader()e getClass().getClassLoader()restituisce diversi oggetti caricatore di classe, quale verrà utilizzato?

Risposte:


151

Ogni classe utilizzerà il proprio classloader per caricare altre classi. Quindi, se i ClassA.classriferimenti devono ClassB.classquindi ClassBessere sul percorso di classe del classloader ClassAo dei suoi genitori.

Il classloader di contesto del thread è il classloader corrente per il thread corrente. Un oggetto può essere creato da una classe in ClassLoaderCe quindi passato a un thread di proprietà di ClassLoaderD. In questo caso, l'oggetto deve utilizzare Thread.currentThread().getContextClassLoader()direttamente se desidera caricare risorse non disponibili sul proprio classloader.


1
Perché dici che ClassBdeve essere sul percorso di classe del ClassAcaricatore (o ClassAdei genitori del caricatore)? Non è possibile ClassAeseguire l'override del caricatore loadClass(), in modo che possa essere caricato correttamente ClassBanche quando ClassBnon si trova sul suo percorso di classe?
Pacerier,

8
In effetti, non tutti i classloader hanno un percorso di classe. Quando ho scritto "ClassB deve essere sul percorso di classe del classloader di ClassA", intendevo "ClassB deve essere caricabile dal classloader di ClassA". Il 90% delle volte ha lo stesso significato. Ma se non si utilizza un classloader basato su URL, è vero solo il secondo caso.
David Roussel,

Che cosa significa dire che " ClassA.classriferimenti ClassB.class"?
jameshfisher,

1
Quando ClassA ha un'istruzione import per ClassB o se esiste un metodo in ClassA che ha una variabile locale di tipo ClassB. Ciò attiverà il caricamento di ClassB, se non è già caricato.
David Roussel,

Penso che il mio problema sia collegato a questo argomento. Cosa ne pensi del mio sollution? So che non è un buon modello, ma non ho altre idee su come risolverlo: stackoverflow.com/questions/29238493/…
Marcin Erbel

109

Questo non risponde alla domanda originale, ma poiché la domanda è classificata e collegata per qualsiasi ContextClassLoaderquery, penso che sia importante rispondere alla domanda correlata su quando utilizzare il caricatore di classi di contesto. Risposta breve: non usare mai il caricatore di classi di contesto ! Ma impostalo su getClass().getClassLoader()quando devi chiamare un metodo in cui manca un ClassLoaderparametro.

Quando il codice di una classe chiede di caricare un'altra classe, il caricatore di classi corretto da utilizzare è lo stesso caricatore di classi della classe chiamante (ovvero, getClass().getClassLoader()). Questo è il modo in cui le cose funzionano il 99,9% delle volte perché è ciò che la JVM fa da sola la prima volta che costruisci un'istanza di una nuova classe, invoca un metodo statico o accedi a un campo statico.

Quando si desidera creare una classe utilizzando la riflessione (come quando si deserializza o si carica una classe denominata configurabile), la libreria che esegue la riflessione dovrebbe sempre chiedere all'applicazione quale caricatore di classe utilizzare, ricevendo ClassLoadercome parametro dall'applicazione. L'applicazione (che conosce tutte le classi che devono essere costruite) dovrebbe passarla getClass().getClassLoader().

Qualsiasi altro modo per ottenere un caricatore di classi non è corretto. Se una libreria utilizza hack come Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader()o sun.reflect.Reflection.getCallerClass()è un bug causato da un difetto nell'API. Fondamentalmente, Thread.getContextClassLoader()esiste solo perché chiunque abbia progettato l' ObjectInputStreamAPI ha dimenticato di accettarlo ClassLoadercome parametro e questo errore ha perseguitato la comunità Java fino ad oggi.

Detto questo, molte molte classi JDK usano uno dei pochi hack per indovinare un caricatore di classi da usare. Alcuni usano il ContextClassLoader(che fallisce quando si eseguono app diverse su un pool di thread condiviso o quando si lascia il ContextClassLoader null), altri camminano nello stack (che fallisce quando il chiamante diretto della classe è esso stesso una libreria), altri usano il caricatore di classi di sistema (il che va bene, purché sia ​​documentato l'uso solo delle classi nel CLASSPATH) o caricatore di classi bootstrap, e alcuni usano una combinazione imprevedibile delle tecniche di cui sopra (il che rende le cose più confuse). Ciò ha provocato molto pianto e digrignamento dei denti.

Quando si utilizza un'API di questo tipo, provare innanzitutto a trovare un sovraccarico del metodo che accetta il programma di caricamento classi come parametro . Se non esiste un metodo ragionevole, prova a impostare ContextClassLoaderprima della chiamata API (e reimpostarla in seguito):

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);
}

5
Sì, questa è la risposta che vorrei segnalare a chiunque ponga la domanda.
Marko Topolnik,

6
Questa risposta si concentra sull'uso del caricatore di classi per caricare le classi (per istanziarle attraverso la riflessione o simili), mentre un altro scopo è utilizzato (e, in effetti, l'unico scopo per cui l'ho mai usato personalmente) è il caricamento delle risorse. Si applica lo stesso principio o ci sono situazioni in cui si desidera recuperare risorse tramite il caricatore di classi di contesto anziché il caricatore di classi di chiamanti?
Egor Hans,

90

C'è un articolo su javaworld.com che spiega la differenza => Quale ClassLoader dovresti usare

(1)

I classloader di contesto di thread forniscono una backdoor attorno allo schema di delega del caricamento di classe.

Prendiamo ad esempio JNDI: le sue viscere sono implementate dalle classi bootstrap in rt.jar (a partire da J2SE 1.3), ma queste classi JNDI di base possono caricare i provider JNDI implementati da fornitori indipendenti e potenzialmente distribuiti nel percorso di classe dell'applicazione. Questo scenario richiede un classloader genitore (in questo caso quello primordiale) per caricare una classe visibile a uno dei suoi caricatori di classi figlio (quello di sistema, ad esempio). La normale delega J2SE non funziona e la soluzione consiste nel fare in modo che le classi JNDI di base utilizzino i caricatori di contesto di thread, in tal modo "tunneling" efficacemente attraverso la gerarchia del caricatore di classi nella direzione opposta alla delega corretta.

(2) dalla stessa fonte:

Questa confusione probabilmente rimarrà con Java per qualche tempo. Prendi qualsiasi API J2SE con caricamento dinamico delle risorse di qualsiasi tipo e prova a indovinare quale strategia di caricamento utilizza. Ecco un campionamento:

  • JNDI utilizza classloader di contesto
  • Class.getResource () e Class.forName () utilizzano il classloader corrente
  • JAXP utilizza classloader di contesto (a partire da J2SE 1.4)
  • java.util.ResourceBundle utilizza il classloader corrente del chiamante
  • I gestori del protocollo URL specificati tramite la proprietà di sistema java.protocol.handler.pkgs vengono cercati solo nel bootstrap e nei classloader di sistema
  • L'API di serializzazione Java utilizza il classloader corrente del chiamante per impostazione predefinita

Poiché si suggerisce che la soluzione alternativa consiste nel fare in modo che le classi JNDI principali utilizzino i caricatori di contesto di thread, in questo caso non ho capito come sia d'aiuto. Vogliamo caricare le classi dei fornitori di implementazione utilizzando il classloader principale ma non sono visibili al classloader principale . Quindi, come possiamo caricarli usando parent, anche se impostiamo questo classloader genitore nel contesto classloader del thread.
Sunny Gupta,

6
@SAM, la soluzione suggerita è in realtà esattamente l'opposto di quello che stai dicendo alla fine. Non è il bootstrapcaricatore di classi padre impostato come caricatore di classi di contesto, ma il systemcaricatore di classi percorso di classe figlio con cui Threadviene impostato. Le JNDIclassi si assicurano quindi di utilizzare Thread.currentThread().getContextClassLoader()per caricare le classi di implementazione JNDI disponibili sul percorso di classe.
Ravi Thapliyal,

"La normale delega J2SE non funziona", posso sapere perché non funziona? Perché BootStrap ClassLoader può caricare solo la classe da rt.jar e non può caricare la classe dal percorso -classe dell'applicazione? Destra?
YuFeng Shen,

37

Aggiungendo alla risposta @David Roussel, le classi possono essere caricate da caricatori di più classi.

Consente di capire come funziona il programma di caricamento classi .

Dal blog di javin paul in javarevisited:

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

ClassLoader segue tre principi.

Principio di delega

Una classe viene caricata in Java, quando necessario. Supponiamo di avere una classe specifica dell'applicazione chiamata Abc.class, la prima richiesta di caricamento di questa classe verrà inviata a Application ClassLoader che delegherà al suo ClassLoader di estensione padre che delega ulteriormente al caricatore di classi Primordial o Bootstrap

  • Bootstrap ClassLoader è responsabile del caricamento dei file di classe JDK standard da rt.jar ed è padre di tutti i caricatori di classi in Java. Il caricatore di classi Bootstrap non ha genitori.

  • Estensione ClassLoader delega la richiesta di caricamento della classe al suo genitore, Bootstrap e, in caso di esito negativo, carica la classe dalla directory jre / lib / ext o da qualsiasi altra directory indicata dalla proprietà di sistema java.ext.dirs

  • Caricatore di classi di sistema o dell'applicazione ed è responsabile del caricamento di classi specifiche dell'applicazione dalla variabile di ambiente CLASSPATH, dall'opzione della riga di comando -classpath o -cp, attributo Class-Path del file manifest all'interno di JAR.

  • Il caricatore di classi di applicazioni è figlio di Extension ClassLoader ed è implementato per sun.misc.Launcher$AppClassLoaderclasse.

NOTA: Tranne il caricatore di classi Bootstrap , implementato in linguaggio nativo principalmente in C, tutti i caricatori di classi Java vengono implementati utilizzando java.lang.ClassLoader.

Principio di visibilità

Secondo il principio di visibilità, Child ClassLoader può vedere la classe caricata da Parent ClassLoader ma viceversa non è vera.

Principio di unicità

Secondo questo principio, una classe caricata da Parent non dovrebbe essere nuovamente caricata da Child ClassLoader

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.