Frage

Warum ist es so schwer, dies in Java zu tun? Wenn Sie jede Art von Modul-System haben wollen, müssen Sie in der Lage sein, Gläser dynamisch zu laden. Mir wurde gesagt, es gibt einen Weg, es zu tun, indem Sie Ihre eigenen ClassLoader zu schreiben, aber das ist eine Menge Arbeit für etwas, das so einfach (zumindest in meinem Kopf) sein sollte, eine Methode mit einer JAR-Datei als Argument als Aufruf.

Alle Vorschläge zur einfachen Code, der tut das?

War es hilfreich?

Lösung

Der Grund ist es schwer, ist die Sicherheit. Classloader sollen unveränderlich sein; Sie sollten in der Lage, nicht notgedrungen Klassen zur Laufzeit hinzufügen. Ich bin eigentlich sehr überrascht, dass mit dem System Classloader funktioniert. Hier ist, wie Sie es tun Ihr eigenes Kind Classloader zu machen:

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.

Andere Tipps

Die folgende Lösung ist hackish, da es Reflexion verwendet Verkapselung zu umgehen, aber es 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);

Sie sollten einen Blick auf OSGi , z.B. in der Eclipse-Plattform implementiert. Es tut genau das. Sie können installieren, deinstallieren, starten und so genannte Bündel stoppen, die JAR-Dateien effektiv sind. Aber es hat ein wenig mehr, als es bietet zum Beispiel Dienste, die in JAR-Dateien dynamisch entdeckt werden zur Laufzeit kann.

Oder finden Sie in der Spezifikation für den Java-Modul-System .

Wie über den JCL Class Loader Rahmen ? 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");

Hier ist eine Version, die nicht veraltet. Ich änderte die ursprüngliche 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();
    }
}

Mit Java 9 , die Antworten mit URLClassLoader jetzt einen Fehler ausgeben wie:

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

Das ist, weil die Klassenlader verwendet geändert haben. Stattdessen auf das System Class Loader hinzufügen möchten, können Sie verwenden, um die Instrumentation 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));
    }
}

Hinzufügen META-INF / MANIFEST.MF und steckt es in einer JAR-Datei mit der Agenten-Klasse:

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

Führen Sie die Agenten:

Dies nutzt den Byte-Buddy-Agenten Bibliothek die Agenten zum Laufe 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());
    }
}

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

Hier ist eine kurze Methode, die ich in der Vergangenheit verwendet habe, einen Klassenlader aus allen lib Dateien 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());
}

Dann die Klassenlader zu bedienen, einfach zu tun:

classLoader.loadClass(name);

Wenn Sie auf Android arbeiten, der folgende Code funktioniert:

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

Die Lösung von jodonnell vorgeschlagen ist gut, aber sollte ein wenig verbessert werden. Ich habe diesen Beitrag zu meiner Anwendung mit Erfolg zu entwickeln.

Weisen Sie den aktuellen Thread

Zum einen müssen wir hinzufügen

Thread.currentThread().setContextClassLoader(classLoader);

oder Sie werden nicht in der Lage Ressource (wie Frühling / context.xml) in den Topf gespeichert zu laden.

Sie sind nicht

Ihre Gläser in der übergeordneten Klasse Loader oder Sie werden nicht in der Lage zu verstehen, wer was geladen ist.

siehe auch Problem ein Glas Nachladen mit URLClassLoader

Allerdings OSGi-Framework bleibt der beste Weg.

Eine andere Version der hackish Lösung von Allain, die auch auf JDK arbeitet 11:

File file = ...
URL url = file.toURI().toURL();
URLClassLoader sysLoader = new URLClassLoader(new URL[0]);

Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});

Auf JDK 11 es gibt einige deprecation Warnungen, sondern dient als vorübergehende Lösung diejenigen, die auf JDK Allain Lösung verwenden 11.

Eine weitere Arbeitslösung mit Instrumentation, die für mich funktioniert. Es hat den Vorteil, dass der Class Loader Suche Modifizieren Probleme auf Klasse Sichtbarkeit für abhängige Klassen zu vermeiden:

Erstellen eines Agenten-Klasse

Für dieses Beispiel hat es auf dem gleichen Glas von der Kommandozeile aufgerufen werden:

package agent;

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

public class Agent {
   public static Instrumentation instrumentation;

   public static void premain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void agentmain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void appendJarFile(JarFile file) throws IOException {
      if (instrumentation != null) {
         instrumentation.appendToSystemClassLoaderSearch(file);
      }
   }
}

Ändern Sie den MANIFEST.MF

Das Hinzufügen des Verweises auf die Agenten:

Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent

ich Netbeans tatsächlich nutzen, so dieser Beitrag hilft, wie die manifest.mf ändern

Ausführen

Die Launcher-Agent-Class wird nur auf JDK unterstützt 9+ und ist verantwortlich für die Agenten laden, ohne es auf der Kommandozeile explizit zu definieren:

 java -jar <your jar>

Die Art und Weise, die auf JDK 6+ arbeitet, ist die Definition des -javaagent Arguments:

java -javaagent:<your jar> -jar <your jar>

Hinzufügen neue Jar bei Runtime

Sie können dann jar als notwendig, den folgenden Befehl:

Agent.appendJarFile(new JarFile(<your file>));

Ich finde keine Probleme mit diesem auf Dokumentation.

Hier ist eine schnelle Abhilfe für Verfahren Allain, um es 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 es auf der Kenntnis der internen Umsetzung spezifischer JVM angewiesen ist, so ist es nicht ideal und es ist nicht eine universelle Lösung. Aber es ist eine schnelle und einfache Abhilfe, wenn Sie wissen, dass Sie Standard OpenJDK oder Oracle JVM verwenden werden. Es könnte auch irgendwann in Zukunft brechen, wenn neue JVM-Version freigegeben wird, so müssen Sie, dass im Auge behalten.

Dies kann eine späte Antwort, ich es, da dies tun kann (ein einfaches Beispiel für fastutil-8.2.2.jar) mit jhplot.Web Klasse von DataMelt ( http://jwork.org/dmelt )

import jhplot.Web;
Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library

Nach der Dokumentation, wird diese Datei herunterladen inside „lib / user“ und dann dynamisch geladen, so können Sie sofort beginnen Klassen aus dieser JAR-Datei im selben Programm.

Sie bitte einen Blick auf dieses Projekt übernehmen, die ich begonnen: Proxy-Objekt lib

Dieses lib wird jar aus dem Dateisystem oder einem beliebigen anderen Ort laden. Es wird ein Klassenlader für das Glas widmen, um sicherzustellen, gibt es keine Bibliothek Konflikte. Die Benutzer ein beliebiges Objekt aus der geladenen jar erstellen können und auf sie jede Methode aufrufen. Diese lib wurde entwickelt, Gläser in Java 8 von der Code-Basis erstellt zu laden, unterstützt Java 7.

Um ein Objekt zu erstellen:

    File libDir = new File("path/to/jar");

    ProxyCallerInterface caller = ObjectBuilder.builder()
            .setClassName("net.proxy.lib.test.LibClass")
            .setArtifact(DirArtifact.builder()
                    .withClazz(ObjectBuilderTest.class)
                    .withVersionInfo(newVersionInfo(libDir))
                    .build())
            .build();
    String version = caller.call("getLibVersion").asString();

ObjectBuilder unterstützt Factory-Methoden, statische Funktionen aufrufen und Interface-Implementierungen zurückrufen. i werden weitere Beispiele auf der Readme-Seite veröffentlichen.

Ich persönlich finde, dass java.util.ServiceLoader macht den Job ziemlich gut. Sie können ein Beispiel rel="nofollow"> hier.

scroll top