wake-up-neo.com

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

Ich weiß, dass dies eine dumme Frage für erfahrene Programmierer sein kann. Ich besitze jedoch eine Bibliothek (einen http-Client), die für einige der anderen in meinem Projekt verwendeten Frameworks/Jars erforderlich ist. Aber alle von ihnen erfordern verschiedene 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, wenn 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 die Klassen willkürlich? Wenn zum Beispiel eine Klasse aus Version-1.jar geladen wird, werden alle anderen Klassen, die aus demselben Klassenladeprogramm geladen werden, alle in dieselbe JAR geschrieben.

Wie gehen Sie mit diesem Problem um?

Gibt es einen Trick, um die Gläser irgendwie in die "required.jar" "einzubauen", so dass sie vom Classloader als "eine Einheit/ein Paket" angesehen oder irgendwie verknüpft werden?

112
jens

Classloader-bezogene Probleme sind eine recht komplexe Angelegenheit. Sie sollten auf jeden Fall einige Fakten beachten:

  • Klassenladeprogramme in einer Anwendung sind normalerweise mehr als ein einzelnes. Das Klassenladeprogramm bootstrap delegiert an das entsprechende. Wenn Sie eine neue Klasse instanziieren, wird das spezifischere Klassenladeprogramm aufgerufen. Wenn es keinen Verweis auf die Klasse findet, die Sie laden möchten, delegiert es an die übergeordnete Klasse usw., bis Sie zum Klassenladeprogramm bootstrap 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 möchten wissen, welche Klassen Sie laden, können Sie nur untersuchen, wie ein bestimmter Klassenladeprogramm versucht, einen Klassennamen aufzulösen.

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

Ich kann einen Weg finden, um zwei Klassen mit demselben Binärnamen zu laden, und dabei müssen sie (und alle ihre Abhängigkeiten) von zwei verschiedenen Klassenladeprogrammen 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 Classloadern immer eine knifflige Aufgabe. Ich würde eher vorschlagen, mehrere inkompatible Abhängigkeiten nach Möglichkeit zu vermeiden.

52
Luca Putzu

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.

20
Tarlog

Classloader lädt Klassen aus dem Jar, das sich zufällig zuerst im Klassenpfad befindet. Normalerweise unterscheiden sich inkompatible Bibliotheksversionen in den Paketen. In unwahrscheinlichen Fällen sind sie jedoch wirklich inkompatibel und können nicht durch eine einmalige Version von jarjar ersetzt werden.

6
Alex Gitelman

Klassenlader laden Klassen 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, dass Sie auf LinkageErrors stoßen, das besagt, dass doppelte Klassendefinitionen für Klassenladeprogramme gefunden wurden. In der Regel wird nicht versucht, zu bestimmen, welche Klasse zuerst geladen werden soll (wenn zwei oder mehr Klassen mit demselben Namen im Klassenpfad von vorhanden sind) der Lader). Manchmal lädt der Klassenladeprogramm die erste Klasse, die im Klassenpfad vorkommt, und ignoriert die doppelten Klassen. Dies hängt jedoch von der Implementierung des Ladeprogramms ab.

Die empfohlene Vorgehensweise zum Beheben derartiger Fehler besteht darin, für jede Gruppe von Bibliotheken, die in Konflikt stehende Abhängigkeiten aufweisen, ein separates Klassenladeprogramm zu verwenden. Auf diese Weise werden die abhängigen Klassen von demselben Klassenladeprogramm geladen, das keinen Zugriff auf die anderen Bibliotheken und Abhängigkeiten hat, wenn ein Klassenladeprogramm versucht, Klassen aus einer Bibliothek zu laden.

6
Vineet Reynolds

Sie können URLClassLoader für 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();
0
Pankaj Kalra