387 Stimmen

Wie kann man JAR-Dateien dynamisch zur Laufzeit laden?

Warum ist es so schwer, dies in Java zu tun? Wenn Sie irgendeine Art von Modulsystem haben wollen, müssen Sie in der Lage sein, JAR-Dateien dynamisch zu laden. Mir wurde gesagt, dass es einen Weg gibt, dies zu tun, indem man seine eigenen ClassLoader aber das ist eine Menge Arbeit für etwas, das (zumindest meiner Meinung nach) so einfach sein sollte wie der Aufruf einer Methode mit einer JAR-Datei als Argument.

Gibt es Vorschläge für einen einfachen Code, der dies ermöglicht?

19voto

Jonathan Nadeau Punkte 207

Hier ist eine Version, die nicht veraltet ist. Ich habe das Original geändert, um die veralteten Funktionen 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();
    }
}

18voto

fgb Punkte 18148

Mit Java 9 , die Antworten mit URLClassLoader geben nun eine Fehlermeldung wie:

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

Der Grund dafür ist, dass sich die verwendeten Klassenlader geändert haben. Stattdessen können Sie, um den System Class Loader zu ergänzen, die 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 legen Sie sie in eine JAR-Datei mit der Agentenklasse:

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

Führen Sie den Agenten aus:

Dabei wird die byte-buddy-agent Bibliothek, um den Agenten zu der 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());
    }
}

14voto

Anton Tananaev Punkte 2328

Hier ist ein schneller Workaround für Allains Methode, um sie mit neueren Versionen von Java kompatibel zu machen:

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
    Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
    Method method = classLoader.getClass()
            .getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
    method.setAccessible(true);
    method.invoke(classLoader, jarPath);
}

Beachten Sie, dass dies von der Kenntnis der internen Implementierung der spezifischen JVM abhängt, daher ist es nicht ideal und keine universelle Lösung. Aber es ist ein schneller und einfacher Workaround, wenn Sie wissen, dass Sie die 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, also müssen Sie das im Hinterkopf behalten.

9voto

Das Beste, was ich gefunden habe, ist org.apache.xbean.classloader.JarFileClassLoader die Teil des XBean Projekt.

Hier ist eine kurze Methode, die ich in der Vergangenheit verwendet habe, um einen Klassenlader aus allen Bibliotheksdateien in einem bestimmten Verzeichnis zu erstellen

public void initialize(String libDir) throws Exception {
    File dependencyDirectory = new File(libDir);
    File[] files = dependencyDirectory.listFiles();
    ArrayList<URL> urls = new ArrayList<URL>();
    for (int i = 0; i < files.length; i++) {
        if (files[i].getName().endsWith(".jar")) {
        urls.add(files[i].toURL());
        //urls.add(files[i].toURI().toURL());
        }
    }
    classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), 
        urls.toArray(new URL[urls.size()]), 
        GFClassLoader.class.getClassLoader());
}

Um dann den Classloader zu verwenden, tun Sie einfach:

classLoader.loadClass(name);

7voto

Sergio Santiago Punkte 801

Ich weiß, dass ich zu spät dran bin, aber ich benutze seit Jahren pf4j das ein Plug-in-Framework ist, und es funktioniert ziemlich gut.

PF4J ist ein Microframework und das Ziel ist es, den Kern einfach, aber erweiterbar zu halten.

Ein Beispiel für die Verwendung eines Plugins:

Definieren Sie einen Erweiterungspunkt in Ihrer Anwendung/Ihrem Plugin mithilfe der ExtensionPoint-Schnittstellenmarkierung:

public interface Greeting extends ExtensionPoint {

    String getGreeting();

}

Erstellen Sie eine Erweiterung mit @Extension Bemerkung:

@Extension
public class WelcomeGreeting implements Greeting {

    public String getGreeting() {
        return "Welcome";
    }

}

Dann können Sie das Plugin nach Belieben laden und entladen:

public static void main(String[] args) {

    // create the plugin manager
    PluginManager pluginManager = new JarPluginManager(); // or "new ZipPluginManager() / new DefaultPluginManager()"

    // start and load all plugins of application
    pluginManager.loadPlugins();
    pluginManager.startPlugins();

    // retrieve all extensions for "Greeting" extension point
    List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
    for (Greeting greeting : greetings) {
        System.out.println(">>> " + greeting.getGreeting());
    }

    // stop and unload all plugins
    pluginManager.stopPlugins();
    pluginManager.unloadPlugins();

}

Weitere Einzelheiten entnehmen Sie bitte der Dokumentation

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X