Wie lade ich JAR-Dateien dynamisch zur Laufzeit?

Lesezeit: 10 Minuten

Wie lade ich JAR Dateien dynamisch zur Laufzeit
Allain Lalonde

Warum ist es so schwierig, dies in Java zu tun? Wenn Sie irgendein 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, es selbst zu schreiben ClassLoaderaber das ist eine Menge Arbeit für etwas, das (zumindest meiner Meinung nach) 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?

  • Ich möchte dasselbe tun, aber das geladene JAR in einer Sandbox-Umgebung ausführen (offensichtlich aus Sicherheitsgründen). Zum Beispiel möchte ich den gesamten Netzwerk- und Dateisystemzugriff blockieren.

    – Jus12

    8. Februar 2012 um 18:51 Uhr

  • @francogrex Wenn es so einfach ist, warum hast du dann diesen Kommentar hinterlassen, anstatt die Frage zu beantworten?

    – Tyler Marshall

    25. Mai 2021 um 4:00 Uhr

  • @TylerMarshall Allain Lalonde hat unten gezeigt, wie und es ist nicht schwer, auch auf anderen Systemen ist es ziemlich einfach. Ich musste die Antworten nicht duplizieren, es ist eine schlechte Einstellung, eine gute ist genug, aber ich wollte dem OP hervorheben, dass das, was er als “hart” bezeichnet, nicht ist und seine Aussage falsch ist.

    Benutzer2587965

    25. Mai 2021 um 8:19 Uhr

1646715430 472 Wie lade ich JAR Dateien dynamisch zur Laufzeit
jodonnell

Der Grund, warum es schwierig ist, ist die Sicherheit. Classloader sollen unveränderlich sein; Sie sollten nicht in der Lage sein, zur Laufzeit willkürlich Klassen hinzuzufügen. Ich bin eigentlich sehr überrascht, dass das mit dem System-Classloader funktioniert. So erstellen Sie Ihren eigenen untergeordneten Classloader:

URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

Schmerzhaft, aber da ist es.

  • Das einzige Problem bei diesem Ansatz ist, dass Sie wissen müssen, welche Klassen sich in welchen Gläsern befinden. Im Gegensatz zum Laden eines Verzeichnisses von JAR-Dateien und dem anschließenden Instanziieren von Klassen. Ich verstehe es falsch?

    – Allain Lalonde

    13. September 2008 um 19:13 Uhr

  • Diese Methode funktioniert hervorragend, wenn sie in meiner IDE ausgeführt wird, aber wenn ich mein JAR erstelle, erhalte ich eine ClassNotFoundException, wenn ich Class.forName() aufrufe.

    – darrickc

    29. Juli 2009 um 16:50 Uhr

  • Bei diesem Ansatz müssen Sie sicherstellen, dass Sie diese Lademethode nicht mehr als einmal für jede Klasse aufrufen. Da Sie für jede Ladeoperation einen neuen Klassenlader erstellen, kann er nicht wissen, ob die Klasse bereits zuvor geladen wurde. Das kann schlimme Folgen haben. Zum Beispiel funktionieren Singletons nicht, weil die Klasse mehrfach geladen wurde und somit die statischen Felder mehrfach vorhanden sind.

    – Eduard Wirch

    27. Mai 2011 um 10:11 Uhr

  • Funktioniert. Sogar mit Abhängigkeiten zu anderen Klassen innerhalb des Glases. Die erste Zeile war unvollständig. ich benutzte URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader()); vorausgesetzt, dass die jar-Datei aufgerufen wird my.jar und befindet sich im selben Verzeichnis.

    – Kiefer

    4. Februar 2015 um 14:04 Uhr

  • Vergessen Sie nicht, URL url = file.toURI().toURL();

    – johnstosh

    17. Mai 2017 um 13:29 Uhr

Wie lade ich JAR Dateien dynamisch zur Laufzeit
Allain Lalonde

Die folgende Lösung ist hackisch, da sie Reflektion verwendet, um die Kapselung zu umgehen, aber sie funktioniert einwandfrei:

File file = ...
URL url = file.toURI().toURL();

URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);

  • Bei all den Aktivitäten zu dieser Antwort frage ich mich, wie viele Hacks wir in der Produktion in verschiedenen Systemen ausführen. Ich bin mir nicht sicher, ob ich die Antwort wissen will

    – Andrej Savu

    26. März 2015 um 22:27 Uhr

  • Funktioniert nicht so gut, wenn der Systemklassenlader etwas anderes als ein URLClassLoader ist …

    – Guss

    4. August 2016 um 18:15 Uhr

  • Java 9+ warnt davor URLClassLoader.class.getDeclaredMethod("addURL", URL.class) ist eine illegale Verwendung der Reflexion und wird in Zukunft scheitern.

    – Charlweed

    24. August 2018 um 17:44 Uhr

  • Irgendeine Idee, wie man diesen Code aktualisiert, damit er mit Java 9+ funktioniert?

    – FiReTiTi

    28. März 2019 um 18:19 Uhr

  • @FiReTiTi Ja!!

    – Mordechai

    15. Januar 2020 um 1:18 Uhr

Sollte man sich anschauen OSGizB implementiert in der Eclipse-Plattform. Genau das tut es. Sie können sogenannte Bundles installieren, deinstallieren, starten und stoppen, die effektiv JAR-Dateien sind. Aber es leistet noch ein wenig mehr, da es zB Dienste anbietet, die in JAR-Dateien zur Laufzeit dynamisch entdeckt werden können.

Oder sehen Sie sich die Spezifikation für die an Java-Modulsystem.

1646715431 256 Wie lade ich JAR Dateien dynamisch zur Laufzeit
Chris

Was ist mit JCL-Klassenlader-Framework? Ich muss zugeben, ich habe es nicht benutzt, aber es sieht vielversprechend aus.

Anwendungsbeispiel:

JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file  
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder  
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)

JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class  
Object obj = factory.create(jcl, "mypackage.MyClass");

1646715431 756 Wie lade ich JAR Dateien dynamisch zur Laufzeit
Mordechai

Während die meisten hier aufgeführten Lösungen entweder Hacks (vor JDK 9) sind, die schwer zu konfigurieren sind (Agenten) oder einfach nicht mehr funktionieren (nach JDK 9), finde ich es wirklich überraschend, dass niemand a klar dokumentierte Methode.

Sie können einen benutzerdefinierten Systemklassenlader erstellen und dann tun, was Sie wollen. Keine Reflektion erforderlich und alle Klassen teilen sich denselben Classloader.

Fügen Sie beim Starten der JVM dieses Flag hinzu:

java -Djava.system.class.loader=com.example.MyCustomClassLoader

Der Klassenlader muss einen Konstruktor haben, der einen Klassenlader akzeptiert, der als sein Elternteil festgelegt werden muss. Der Konstruktor wird beim JVM-Start aufgerufen und der Classloader des echten Systems wird übergeben, die Hauptklasse wird vom benutzerdefinierten Loader geladen.

Um Gläser hinzuzufügen, rufen Sie einfach an ClassLoader.getSystemClassLoader() und übertragen Sie es in Ihre Klasse.

Kasse diese Umsetzung für einen sorgfältig gefertigten Classloader. Bitte beachten Sie, dass Sie die ändern können add() Methode öffentlich.

  • Danke – das ist wirklich hilfreich! Alle anderen Referenzen im Web verwenden Methoden für JDK 8 oder früher – was mehrere Probleme hat.

    – Vishal Biyani

    9. Juni 2020 um 17:04 Uhr

  • -Djava.system.class.loader=com.example.MyCustomClassLoader scheint nicht für die Kompilierzeit-Abhängigkeits-Jars im Anwendungs-JAR-Manifest wiedergegeben zu werden. Siehe: stackoverflow.com/questions/68380968/…

    – Valsaraj Viswanathan

    17. September 2021 um 12:03 Uhr

  • Können die JAR-Dateien nach dem Start mit dem Custom Classloader genauso geladen werden wie in Allain answer? Erhalten Sie immer noch “jdk.internal.loader.ClassLoaders$AppClassLoader is in module java.base of loader ‘bootstrap’; org.myapp.CustomClassLoader is in unnamed module of loader ‘app'”

    – Benutzer2209562

    22. November 2021 um 15:26 Uhr

  • Ah nein. Ändern Sie einfach Ihren Klassenlader, um die geschützte addUrl verfügbar zu machen, und Sie brauchen keine Reflektion. Es ist eigentlich mein letzter Satz

    – Mordechai

    23. November 2021 um 5:20 Uhr

  • Wie in der Antwort erwähnt, wird der Klassenlader des ursprünglichen Systems zum übergeordneten Ladeprogramm des benutzerdefinierten Ladeprogramms, was bedeutet, dass Klassen, die durch das ursprüngliche Klassenladeprogramm geladen werden, keine vom benutzerdefinierten Ladeprogramm definierten Klassen sehen, was ebenso wichtig ist wie der Klassenpfad noch durch den ursprünglichen Lader gehandhabt, dh getURLs() des benutzerdefinierten Ladeprogramms gibt den Klassenpfad nicht zurück, und wenn das benutzerdefinierte Ladeprogramm dem Standarddelegierungsmodell folgt, wird die Anforderung für die Hauptklasse an das alte Standardladeprogramm weitergeleitet, das sie auf die gleiche Weise wie ohne das benutzerdefinierte Ladeprogramm lädt.

    – Holger

    24. November 2021 um 11:48 Uhr

1646715431 720 Wie lade ich JAR Dateien dynamisch zur Laufzeit
Jason S

Hier ist eine Version, die nicht veraltet ist. Ich habe das Original geändert, um die veraltete Funktionalität zu entfernen.

/**************************************************************************************************
 * Copyright (c) 2004, Federal University of So Carlos                                           *
 *                                                                                                *
 * All rights reserved.                                                                           *
 *                                                                                                *
 * Redistribution and use in source and binary forms, with or without modification, are permitted *
 * provided that the following conditions are met:                                                *
 *                                                                                                *
 *     * Redistributions of source code must retain the above copyright notice, this list of      *
 *       conditions and the following disclaimer.                                                 *
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of   *
 *     * conditions and the following disclaimer in the documentation and/or other materials      *
 *     * provided with the distribution.                                                          *
 *     * Neither the name of the Federal University of So Carlos nor the names of its            *
 *     * contributors may be used to endorse or promote products derived from this software       *
 *     * without specific prior written permission.                                               *
 *                                                                                                *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS                            *
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT                              *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR                          *
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR                  *
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,                          *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                            *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR                             *
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF                         *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING                           *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS                             *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
 **************************************************************************************************/
/*
 * Created on Oct 6, 2004
 */
package tools;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Useful class for dynamically changing the classpath, adding classes during runtime. 
 */
public class ClasspathHacker {
    /**
     * Parameters of the method to add an URL to the System classes. 
     */
    private static final Class<?>[] parameters = new Class[]{URL.class};

    /**
     * Adds a file to the classpath.
     * @param s a String pointing to the file
     * @throws IOException
     */
    public static void addFile(String s) throws IOException {
        File f = new File(s);
        addFile(f);
    }

    /**
     * Adds a file to the classpath
     * @param f the file to be added
     * @throws IOException
     */
    public static void addFile(File f) throws IOException {
        addURL(f.toURI().toURL());
    }

    /**
     * Adds the content pointed by the URL to the classpath.
     * @param u the URL pointing to the content to be added
     * @throws IOException
     */
    public static void addURL(URL u) throws IOException {
        URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
        Class<?> sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL",parameters);
            method.setAccessible(true);
            method.invoke(sysloader,new Object[]{ u }); 
        } catch (Throwable t) {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }        
    }

    public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        addFile("C:\\dynamicloading.jar");
        Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
        DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
        instance.test();
    }
}

  • Danke – das ist wirklich hilfreich! Alle anderen Referenzen im Web verwenden Methoden für JDK 8 oder früher – was mehrere Probleme hat.

    – Vishal Biyani

    9. Juni 2020 um 17:04 Uhr

  • -Djava.system.class.loader=com.example.MyCustomClassLoader scheint nicht für die Kompilierzeit-Abhängigkeits-Jars im Anwendungs-JAR-Manifest wiedergegeben zu werden. Siehe: stackoverflow.com/questions/68380968/…

    – Valsaraj Viswanathan

    17. September 2021 um 12:03 Uhr

  • Können die JAR-Dateien nach dem Start mit dem Custom Classloader genauso geladen werden wie in Allain answer? Erhalten Sie immer noch “jdk.internal.loader.ClassLoaders$AppClassLoader is in module java.base of loader ‘bootstrap’; org.myapp.CustomClassLoader is in unnamed module of loader ‘app'”

    – Benutzer2209562

    22. November 2021 um 15:26 Uhr

  • Ah nein. Ändern Sie einfach Ihren Klassenlader, um die geschützte addUrl verfügbar zu machen, und Sie brauchen keine Reflektion. Es ist eigentlich mein letzter Satz

    – Mordechai

    23. November 2021 um 5:20 Uhr

  • Wie in der Antwort erwähnt, wird der Klassenlader des ursprünglichen Systems zum übergeordneten Ladeprogramm des benutzerdefinierten Ladeprogramms, was bedeutet, dass Klassen, die durch das ursprüngliche Klassenladeprogramm geladen werden, keine vom benutzerdefinierten Ladeprogramm definierten Klassen sehen, was ebenso wichtig ist wie der Klassenpfad noch durch den ursprünglichen Lader gehandhabt, dh getURLs() des benutzerdefinierten Ladeprogramms gibt den Klassenpfad nicht zurück, und wenn das benutzerdefinierte Ladeprogramm dem Standarddelegierungsmodell folgt, wird die Anforderung für die Hauptklasse an das alte Standardladeprogramm weitergeleitet, das sie auf die gleiche Weise wie ohne das benutzerdefinierte Ladeprogramm lädt.

    – Holger

    24. November 2021 um 11:48 Uhr

Mit Java 9die Antworten mit URLClassLoader geben Sie jetzt einen Fehler wie:

java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader

Dies liegt daran, dass sich die verwendeten Klassenlader geändert haben. Um den Systemklassenlader hinzuzufügen, können Sie stattdessen die verwenden Instrumentierung API über einen Agenten.

Erstellen Sie eine Agentenklasse:

package ClassPathAgent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class ClassPathAgent {
    public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
        instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
    }
}

Fügen Sie META-INF/MANIFEST.MF hinzu und fügen Sie es in eine JAR-Datei mit der Agentenklasse ein:

Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent

Führen Sie den Agenten aus:

Dies nutzt die byte-buddy-agent Bibliothek, um den Agenten zur laufenden JVM hinzuzufügen:

import java.io.File;

import net.bytebuddy.agent.ByteBuddyAgent;

public class ClassPathUtil {
    private static File AGENT_JAR = new File("/path/to/agent.jar");

    public static void addJarToClassPath(File jarFile) {
        ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
    }
}

972160cookie-checkWie lade ich JAR-Dateien dynamisch zur Laufzeit?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy