Warum ist das in Java so schwierig? Wenn Sie ein Modulsystem haben möchten, müssen Sie in der Lage sein, JAR-Dateien dynamisch zu laden. Mir wurde gesagt, dass es eine Möglichkeit gibt, dies zu tun, indem Sie Ihre eigenen schreiben ClassLoader
, aber das ist eine Menge Arbeit für etwas, das (zumindest in meinen Augen) so einfach sein sollte wie das Aufrufen einer Methode mit einer JAR-Datei als Argument.
Irgendwelche Vorschläge für einfachen Code, der dies tut?
java
jar
classloader
Allain Lalonde
quelle
quelle
Antworten:
Der Grund, warum es schwierig ist, ist die Sicherheit. Klassenlader sollen unveränderlich sein; Sie sollten nicht in der Lage sein, zur Laufzeit wohl oder übel Klassen hinzuzufügen. Ich bin eigentlich sehr überrascht, dass das mit dem System Classloader funktioniert. So machen Sie Ihren eigenen Klassenlader:
Schmerzhaft, aber da ist es.
quelle
URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());
angenommen, dass die JAR-Datei aufgerufen wirdmy.jar
und sich im selben Verzeichnis befindet.Die folgende Lösung ist hackisch, da sie Reflexion verwendet, um die Kapselung zu umgehen, aber sie funktioniert einwandfrei:
quelle
URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
Verwendung von Reflektion illegal ist und in Zukunft fehlschlagen wird.Sie sollten sich OSGi ansehen , z. B. in der Eclipse-Plattform implementiert . Es macht genau das. Sie können sogenannte Bundles installieren, deinstallieren, starten und stoppen, bei denen es sich effektiv um JAR-Dateien handelt. Aber es macht ein bisschen mehr, da es z. B. Dienste bietet, die zur Laufzeit dynamisch in JAR-Dateien erkannt werden können.
Oder lesen Sie die Spezifikation für das Java Module System .
quelle
Wie wäre es mit dem JCL Class Loader Framework ? Ich muss zugeben, ich habe es nicht benutzt, aber es sieht vielversprechend aus.
Anwendungsbeispiel:
quelle
Hier ist eine Version, die nicht veraltet ist. Ich habe das Original geändert, um die veraltete Funktionalität zu entfernen.
quelle
Während die meisten hier aufgeführten Lösungen entweder schwer zu konfigurierende Hacks (vor JDK 9) sind (Agenten) oder einfach nicht mehr funktionieren (nach JDK 9), finde ich es wirklich schockierend, dass niemand eine klar dokumentierte Methode erwähnt hat .
Sie können einen benutzerdefinierten Systemklassenlader erstellen und dann tun, was Sie möchten. Keine Reflexion erforderlich und alle Klassen verwenden denselben Klassenladeprogramm.
Fügen Sie beim Starten der JVM dieses Flag hinzu:
Der Klassenladeprogramm muss über einen Konstruktor verfügen, der einen Klassenladeprogramm akzeptiert, das als übergeordnetes Element festgelegt werden muss. Der Konstruktor wird beim JVM-Start aufgerufen und der echte Systemklassenlader wird übergeben, die Hauptklasse wird vom benutzerdefinierten Lader geladen.
Um Gläser hinzuzufügen, rufen
ClassLoader.getSystemClassLoader()
Sie einfach an und werfen Sie es in Ihre Klasse.Schauen Sie sich diese Implementierung für einen sorgfältig ausgearbeiteten Classloader an. Bitte beachten Sie, dass Sie die
add()
Methode in public ändern können .quelle
Bei Java 9 geben die Antworten mit
URLClassLoader
jetzt einen Fehler wie:Dies liegt daran, dass sich die verwendeten Klassenlader geändert haben. Um den Systemklassenlader zu erweitern, können Sie stattdessen die Instrumentation- API über einen Agenten verwenden.
Erstellen Sie eine Agentenklasse:
Fügen Sie META-INF / MANIFEST.MF hinzu und fügen Sie es in eine JAR-Datei mit der Agentenklasse ein:
Führen Sie den Agenten aus:
Dies verwendet die Byte-Buddy-Agent- Bibliothek, um den Agenten zur laufenden JVM hinzuzufügen:
quelle
Das Beste, was ich gefunden habe, ist org.apache.xbean.classloader.JarFileClassLoader, das Teil des XBean- Projekts ist.
Hier ist eine kurze Methode, die ich in der Vergangenheit verwendet habe, um einen Klassenlader aus allen lib-Dateien in einem bestimmten Verzeichnis zu erstellen
Um den Klassenlader zu verwenden, gehen Sie einfach wie folgt vor:
quelle
Wenn Sie mit Android arbeiten, funktioniert der folgende Code:
quelle
Hier ist eine schnelle Problemumgehung für Allains Methode, um sie mit neueren Java-Versionen kompatibel zu machen:
Beachten Sie, dass es auf dem Wissen über die interne Implementierung einer bestimmten JVM beruht. Daher ist es nicht ideal und keine universelle Lösung. Es ist jedoch eine schnelle und einfache Problemumgehung, wenn Sie wissen, dass Sie Standard-OpenJDK oder Oracle JVM verwenden werden. Es könnte auch irgendwann in der Zukunft kaputt gehen, wenn eine neue JVM-Version veröffentlicht wird. Sie müssen dies also berücksichtigen.
quelle
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
Die von Jodonnell vorgeschlagene Lösung ist gut, sollte aber etwas verbessert werden. Ich habe diesen Beitrag verwendet, um meine Anwendung mit Erfolg zu entwickeln.
Weisen Sie den aktuellen Thread zu
Zuerst müssen wir hinzufügen
oder Sie können keine in das Glas gespeicherte Ressource (z. B. spring / context.xml) laden.
Nicht einschließen
Ihre Gläser in den übergeordneten Klassenlader oder Sie werden nicht verstehen können, wer was lädt.
Siehe auch Problem beim erneuten Laden eines JAR mit URLClassLoader
Das OSGi-Framework bleibt jedoch der beste Weg.
quelle
Eine andere Version der hackischen Lösung von Allain, die auch auf JDK 11 funktioniert:
In JDK 11 werden einige Verwerfungswarnungen ausgegeben, sie dienen jedoch als vorübergehende Lösung für diejenigen, die die Allain-Lösung in JDK 11 verwenden.
quelle
Eine weitere funktionierende Lösung mit Instrumentation, die für mich funktioniert. Es hat den Vorteil, dass die Suche nach Klassenladeprogrammen geändert wird, wodurch Probleme bei der Sichtbarkeit von Klassen für abhängige Klassen vermieden werden:
Erstellen Sie eine Agentenklasse
In diesem Beispiel muss es sich in demselben JAR befinden, das von der Befehlszeile aufgerufen wird:
Ändern Sie die MANIFEST.MF
Hinzufügen des Verweises zum Agenten:
Ich benutze tatsächlich Netbeans, daher hilft dieser Beitrag beim Ändern der manifest.mf
Laufen
Das
Launcher-Agent-Class
wird nur von JDK 9+ unterstützt und ist für das Laden des Agenten verantwortlich, ohne ihn explizit in der Befehlszeile zu definieren:Die Funktionsweise von JDK 6+ definiert das
-javaagent
Argument:Hinzufügen eines neuen Glases zur Laufzeit
Sie können dann mit dem folgenden Befehl nach Bedarf jar hinzufügen:
Ich habe in der Dokumentation keine Probleme damit gefunden.
quelle
Falls jemand in Zukunft danach sucht, funktioniert dies für mich mit OpenJDK 13.0.2.
Ich habe viele Klassen, die ich zur Laufzeit dynamisch instanziieren muss, jede möglicherweise mit einem anderen Klassenpfad.
In diesem Code habe ich bereits ein Objekt namens pack, das einige Metadaten zu der Klasse enthält, die ich laden möchte. Die Methode getObjectFile () gibt den Speicherort der Klassendatei für die Klasse zurück. Die Methode getObjectRootPath () gibt den Pfad zum Verzeichnis bin / zurück, das die Klassendateien enthält, die die Klasse enthalten, die ich instanziieren möchte. Die Methode getLibPath () gibt den Pfad zu einem Verzeichnis zurück, das die JAR-Dateien enthält, die den Klassenpfad für das Modul bilden, zu dem die Klasse gehört.
Ich habe die Maven-Abhängigkeit: org.xeustechnologies: jcl-core: 2.8 verwendet, um dies zuvor zu tun, aber nachdem ich JDK 1.8 überschritten habe, ist sie manchmal eingefroren und hat nie wieder "auf Referenzen gewartet" bei Reference :: waitForReferencePendingList ().
Ich behalte auch eine Karte mit Klassenladeprogrammen, damit diese wiederverwendet werden können, wenn sich die Klasse, die ich instanziieren möchte, im selben Modul befindet wie eine Klasse, die ich bereits instanziiert habe, was ich empfehlen würde.
quelle
Bitte schauen Sie sich dieses Projekt an, das ich gestartet habe: proxy-object lib
Diese Bibliothek lädt JAR aus dem Dateisystem oder einem anderen Speicherort. Es wird ein Klassenladeprogramm für das JAR reserviert, um sicherzustellen, dass keine Bibliothekskonflikte vorliegen. Benutzer können jedes Objekt aus dem geladenen JAR erstellen und eine beliebige Methode darauf aufrufen. Diese Bibliothek wurde entwickelt, um in Java 8 kompilierte Jars aus der Codebasis zu laden, die Java 7 unterstützt.
So erstellen Sie ein Objekt:
ObjectBuilder unterstützt Factory-Methoden, ruft statische Funktionen auf und ruft Schnittstellenimplementierungen zurück. Ich werde weitere Beispiele auf der Readme-Seite veröffentlichen.
quelle
Dies kann eine späte Antwort sein. Ich kann dies wie folgt tun (ein einfaches Beispiel für fastutil-8.2.2.jar), indem ich die Klasse jhplot.Web von DataMelt ( http://jwork.org/dmelt ) verwende.
Gemäß der Dokumentation wird diese Datei in "lib / user" heruntergeladen und dann dynamisch geladen, sodass Sie sofort Klassen aus dieser JAR-Datei im selben Programm verwenden können.
quelle
Ich musste zur Laufzeit eine JAR-Datei für Java 8 und Java 9+ laden (die obigen Kommentare funktionieren nicht für beide Versionen). Hier ist die Methode, um dies zu tun (mit Spring Boot 1.5.2, wenn es sich darauf bezieht).
quelle
Ich persönlich finde, dass java.util.ServiceLoader den Job ziemlich gut macht. Ein Beispiel finden Sie hier .
quelle