Java, Classpath, Classloading => Mehrere Versionen desselben JARs / Projekts

117

Ich weiß, dass dies für erfahrene Programmierer eine dumme Frage sein kann. Ich habe jedoch eine Bibliothek (einen http-Client), die einige der anderen in meinem Projekt verwendeten Frameworks / Jars benötigen. Aber alle erfordern unterschiedliche Hauptversionen wie:

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

Ist der Klassenlader intelligent genug, um sie irgendwie zu trennen? Höchst wahrscheinlich nicht? Wie geht der Classloader damit um, falls eine Klasse in allen drei Gläsern gleich ist? Welches ist geladen und warum?

Nimmt der Classloader nur genau ein Glas auf oder mischt er Klassen willkürlich? Wenn zum Beispiel eine Klasse aus Version-1.jar geladen wird, werden alle anderen Klassen, die von demselben Klassenladeprogramm geladen werden, alle in dasselbe JAR verschoben?

Wie gehen Sie mit diesem Problem um?

Gibt es einen Trick, um die Gläser irgendwie in die "required.jar" zu "integrieren", so dass sie von der als "eine Einheit / Packung" angesehen Classloaderoder irgendwie verknüpft werden?

jens
quelle

Antworten:

56

Probleme im Zusammenhang mit Klassenladern sind eine recht komplexe Angelegenheit. Sie sollten auf jeden Fall einige Fakten beachten:

  • Klassenlader in einer Anwendung sind normalerweise mehr als eine einzelne. Der Bootstrap-Klassenladeprogramm delegiert an die entsprechenden. Wenn Sie eine neue Klasse instanziieren, wird der spezifischere Klassenladeprogramm aufgerufen. Wenn kein Verweis auf die Klasse gefunden wird, die Sie laden möchten, wird er an die übergeordnete Klasse usw. delegiert, bis Sie zum Bootstrap-Klassenladeprogramm gelangen. Wenn keiner von ihnen einen Verweis auf die Klasse findet, die Sie laden möchten, erhalten Sie eine ClassNotFoundException.

  • Wenn Sie zwei Klassen mit demselben Binärnamen haben, die von demselben Klassenladeprogramm durchsucht werden können, und Sie wissen möchten, welche von ihnen Sie laden, können Sie nur überprüfen, wie ein bestimmter Klassenladeprogramm versucht, einen Klassennamen aufzulösen.

  • Gemäß der Java-Sprachspezifikation gibt es keine Eindeutigkeitsbeschränkung für einen Klassenbinärnamen, aber soweit ich sehen kann, sollte sie für jeden Klassenlader eindeutig sein.

Ich kann einen Weg finden, zwei Klassen mit demselben Binärnamen zu laden, und sie müssen von zwei verschiedenen Klassenladern (und allen ihren Abhängigkeiten) geladen werden, die das Standardverhalten überschreiben. Ein grobes Beispiel:

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

Ich fand die Anpassung von Klassenladern immer eine schwierige Aufgabe. Ich würde eher vorschlagen, wenn möglich mehrere inkompatible Abhängigkeiten zu vermeiden.

Luca Putzu
quelle
13
Der Bootstrap-Klassenladeprogramm delegiert an die entsprechenden. Wenn Sie eine neue Klasse instanziieren, wird der spezifischere Klassenladeprogramm aufgerufen. Wenn es keinen Verweis auf die Klasse findet, die Sie laden möchten, delegiert es an die übergeordnete Klasse. Bitte nehmen Sie Kontakt mit mir auf, dies hängt jedoch von der Classloader-Richtlinie ab, die standardmäßig Parent First ist. Mit anderen Worten, die untergeordnete Klasse fordert zuerst ihre übergeordnete Klasse auf, die Klasse zu laden, und lädt nur, wenn die gesamte Hierarchie sie nicht laden konnte, nein?
Deckingraj
5
Nein - normalerweise delegiert ein Klassenladeprogramm an sein übergeordnetes Element, bevor er nach der Klasse selbst sucht. Siehe die Klasse javadoc für Classloader.
Joe Kearney
1
Ich denke, Tomcat macht es auf die hier beschriebene Weise, aber "konventionelle" Delegation ist es, die Eltern zuerst zu fragen
Rogerdpack
@deckingraj: Nach einigem googeln fand ich dies aus Oracle-Dokumenten: "Im Delegierungsdesign delegiert ein Klassenladeprogramm das Laden von Klassen an sein übergeordnetes Element, bevor er versucht, eine Klasse selbst zu laden. [...] Wenn der übergeordnete Klassenladeprogramm keine Klasse laden kann, Der Klassenlader versucht, die Klasse selbst zu laden. Tatsächlich ist ein Klassenlader dafür verantwortlich, nur die Klassen zu laden, die dem übergeordneten Element nicht zur Verfügung stehen. " Ich werde weiter untersuchen. Wenn dies als Standardimplementierung herauskommt, werde ich die Antwort entsprechend aktualisieren. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Luca Putzu
20

Jede Klassenladung wählt genau eine Klasse aus. Normalerweise wird der erste gefunden.

OSGi zielt darauf ab, das Problem mehrerer Versionen desselben Glases zu lösen. Equinox und Apache Felix sind die gängigen Open-Source-Implementierungen für OSGi.

Tarlog
quelle
6

Classloader lädt Klassen aus dem JAR, das sich zuerst im Klassenpfad befand. Normalerweise unterscheiden sich inkompatible Versionen der Bibliothek in den Paketen. In unwahrscheinlichen Fällen sind sie jedoch wirklich inkompatibel und können nicht durch ein einziges ersetzt werden - versuchen Sie es mit jarjar.

Alex Gitelman
quelle
6

Klassenlader laden Klasse auf Anfrage. Dies bedeutet, dass die Klasse, die zuerst von Ihrer Anwendung und den zugehörigen Bibliotheken benötigt wird, vor anderen Klassen geladen wird. Die Anforderung zum Laden der abhängigen Klassen wird normalerweise während des Lade- und Verknüpfungsprozesses einer abhängigen Klasse ausgegeben.

Es ist wahrscheinlich LinkageError, dass Sie feststellen, dass für Klassenlader doppelte Klassendefinitionen aufgetreten sind. In der Regel wird nicht versucht, zu bestimmen, welche Klasse zuerst geladen werden soll (wenn im Klassenpfad des Loaders zwei oder mehr Klassen mit demselben Namen vorhanden sind). Manchmal lädt der Klassenladeprogramm die erste im Klassenpfad vorkommende Klasse und ignoriert die doppelten Klassen. Dies hängt jedoch von der Implementierung des Ladeprogramms ab.

Die empfohlene Vorgehensweise zur Behebung solcher Fehler besteht darin, für jeden Satz von Bibliotheken mit widersprüchlichen Abhängigkeiten einen separaten Klassenladeprogramm zu verwenden. Wenn ein Klassenladeprogramm versucht, Klassen aus einer Bibliothek zu laden, werden die abhängigen Klassen auf dieselbe Weise von demselben Klassenladeprogramm geladen, das keinen Zugriff auf die anderen Bibliotheken und Abhängigkeiten hat.

Vineet Reynolds
quelle
1

Sie können die URLClassLoaderfor require verwenden, um die Klassen aus einer diff-2-Version von Jars zu laden:

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();
Pankaj Kalra
quelle