Java, Classpath, Classloading => Versioni multiple dello stesso jar / progetto


118

So che questa potrebbe essere una domanda stupida per programmatori esperti. Ma ho una libreria (un client http) richiesta da alcuni degli altri framework / jar utilizzati nel mio progetto. Ma tutti richiedono diverse versioni principali come:

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

Il classloader è abbastanza intelligente da separarli in qualche modo? Probabilmente no? Come lo gestisce il Classloader, nel caso in cui una classe è la stessa in tutti e tre i jar. Quale è caricato e perché?

Il Classloader preleva solo esattamente un vaso o mescola le classi in modo arbitrario? Quindi, ad esempio, se una classe viene caricata dalla versione 1.jar, tutte le altre classi caricate dallo stesso classloader andranno tutte nello stesso jar?

Come gestisci questo problema?

C'è qualche trucco per "incorporare" in qualche modo i barattoli in "required.jar" in modo che siano visti come "un'unità / pacchetto" da Classloader, o in qualche modo collegati?

Risposte:


57

I problemi relativi al Classloader sono una questione piuttosto complessa. In ogni caso dovresti tenere a mente alcuni fatti:

  • I classloader in un'applicazione sono generalmente più di uno. Il programma di caricamento classi bootstrap delega al file. Quando si crea un'istanza di una nuova classe, viene richiamato il programma di caricamento classi più specifico. Se non trova un riferimento alla classe che si sta tentando di caricare, delega al suo genitore e così via, finché non si arriva al caricatore di classi bootstrap. Se nessuno di loro trova un riferimento alla classe che stai tentando di caricare, ottieni un'eccezione ClassNotFoundException.

  • Se si dispone di due classi con lo stesso nome binario, ricercabili dallo stesso programma di caricamento classi, e si desidera sapere quale di esse si sta caricando, è possibile controllare solo il modo in cui il programma di caricamento classi specifico tenta di risolvere un nome di classe.

  • Secondo la specifica del linguaggio Java, non esiste un vincolo di unicità per un nome binario di classe, ma per quanto posso vedere, dovrebbe essere univoco per ogni caricatore di classe.

Riesco a trovare un modo per caricare due classi con lo stesso nome binario, e ciò implica farle caricare (e tutte le loro dipendenze) da due diversi classloader che sovrascrivono il comportamento predefinito. Un esempio approssimativo:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

Ho sempre trovato la personalizzazione del classloader un compito complicato. Preferisco suggerire di evitare più dipendenze incompatibili, se possibile.


13
Il programma di caricamento classi bootstrap delega al file. Quando si crea un'istanza di una nuova classe, viene richiamato il programma di caricamento classi più specifico. Se non trova un riferimento alla classe che stai tentando di caricare, delega al suo genitore Per favore, abbi pazienza, ma dipende dalla politica del classloader che è per impostazione predefinita Parent First. In altre parole, la classe figlia chiederà prima al suo genitore di caricare la classe e caricherà solo se l'intera gerarchia non è riuscita a caricarla, no ??
deckingraj

5
No, in genere un classloader delega al suo genitore prima di cercare la classe stessa. Vedi la classe javadoc per Classloader.
Joe Kearney

1
Penso che Tomcat lo faccia nel modo descritto qui, ma la delega "convenzionale" è chiedere prima al genitore
rogerdpack

@deckingraj: dopo un po 'di googling ho trovato questo da Oracle docs: "Nella progettazione della delega, un caricatore di classi delega il caricamento delle classi al suo genitore prima di tentare di caricare una classe. [...] Se il caricatore di classi genitore non può caricare una classe, il class loader tenta di caricare la classe stessa. In effetti, un class loader è responsabile del caricamento solo delle classi non disponibili per il genitore ". Investigherò ulteriormente. Se questo emergerà come implementazione predefinita aggiornerò la risposta di conseguenza. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Luca Putzu

20

Ogni carico di classe seleziona esattamente una classe. Di solito il primo trovato.

OSGi mira a risolvere il problema di più versioni dello stesso jar. Equinox e Apache Felix sono le comuni implementazioni open source per OSGi.


6

Classloader caricherà prima le classi dal jar che si trovava nel classpath. Normalmente, le versioni incompatibili della libreria avranno differenze nei pacchetti, ma in casi improbabili sono davvero incompatibili e non possono essere sostituite con una: prova jarjar.


6

I classloader caricano la classe su richiesta. Ciò significa che la classe richiesta per prima dall'applicazione e le relative librerie verrebbero caricate prima delle altre classi; la richiesta di caricare le classi dipendenti viene generalmente emessa durante il processo di caricamento e collegamento di una classe dipendente.

È probabile che si incontrino messaggi di posta LinkageErrorche affermano che sono state rilevate definizioni di classi duplicate per i classloader che in genere non tentano di determinare quale classe deve essere caricata per prima (se ci sono due o più classi con lo stesso nome presenti nel classpath del caricatore). A volte, il classloader caricherà la prima classe che si verifica nel classpath e ignorerà le classi duplicate, ma questo dipende dall'implementazione del loader.

La pratica consigliata per risolvere questo tipo di errori consiste nell'utilizzare un classloader separato per ogni set di librerie che hanno dipendenze in conflitto. In questo modo, se un classloader tenta di caricare classi da una libreria, le classi dipendenti verrebbero caricate dallo stesso classloader che non ha accesso alle altre librerie e dipendenze.


1

Puoi usare URLClassLoaderfor require per caricare le classi da una versione diff-2 di jars:

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();
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.