我应该如何在运行时动态加载Jars?

All*_*nde 290 java jar classloader

为什么在Java中这么做呢?如果您想拥有任何类型的模块系统,您需要能够动态加载jar.我被告知有一种方法可以通过编写自己的方式来完成它ClassLoader,但这对于应该(至少在我看来)像调用一个以jar文件作为参数的方法一样容易的事情来做很多工作.

这样做的简单代码的任何建议?

jod*_*ell 236

安全的原因很难.类加载器意味着不可变; 你不应该在运行时不断添加类.我真的很惊讶它与系统类加载器一起工作.以下是制作自己的子类加载器的方法:

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);
Run Code Online (Sandbox Code Playgroud)

痛苦,但它确实存在.

  • 使用这种方法,您需要确保不会为每个类多次调用此加载方法.由于您正在为每个加载操作创建一个新的类加载器,因此无法知道该类是否先前已加载过.这可能会产生不良后果.例如,由于类被加载了几次而无法工作的单例,因此静态字段存在多次. (25认同)
  • 这种方法的唯一问题是你需要知道什么类在什么罐子里.而不是只加载jar目录然后实例化类.我误会了吗? (14认同)
  • 在我的IDE中运行时,此方法很有效,但是当我构建JAR时,在调用Class.forName()时会出现ClassNotFoundException. (10认同)
  • 作品.即使依赖于jar中的其他类.第一行不完整.我使用`URLClassLoader child = new URLClassLoader(new URL [] {new URL("file://./my.jar")},Main.class.getClassLoader());`假设jar文件名为`my .jar`和位于同一目录中. (7认同)
  • 不要忘记URL url = file.toURI().toURL(); (3认同)
  • @Allain 和其他人,你可以像这样加载一个 jars 目录: <code>ClassLoader loader = URLClassLoader.newInstance( new URL[] { yourURL }, getClass().getClassLoader() );</code> (2认同)

All*_*nde 136

以下解决方案是hackish,因为它使用反射来绕过封装,但它完美无缺:

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);
Run Code Online (Sandbox Code Playgroud)

  • 关于这个响应的所有活动让我想知道我们在不同系统中的生产中运行了多少黑客.我不确定我是否想知道答案 (37认同)
  • Java 9+警告说`URLClassLoader.class.getDeclaredMethod("addURL",URL.class)`是非法使用反射,并且将来会失败. (6认同)
  • 如果系统类加载器碰巧不是URLClassLoader,那么效果不好...... (5认同)
  • @FiReTiTi [是](/sf/answers/4182075621/)!! (3认同)
  • 狂野海勒姆定律被发现! (2认同)

Mar*_*nke 48

您应该看看OSGi,例如在Eclipse Platform中实现.它正是如此.您可以安装,卸载,启动和停止所谓的捆绑包,这些捆绑包实际上是JAR文件.但它做得更多,因为它提供了可以在运行时在JAR文件中动态发现的服务.

或者查看Java模块系统的规范.


Chr*_*ris 40

如何在JCL类加载器的框架?我不得不承认,我没有使用它,但看起来很有希望.

用法示例:

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");
Run Code Online (Sandbox Code Playgroud)

  • 它也有错误并缺少一些重要的实现,即findResources(...).准备好度过美好的夜晚,调查为什么某些事情不起作用=) (9认同)
  • @ErdinEray,这是一个很好的问题,我也问自己,因为我们“被迫”切换到 OpenJDK。我仍然在 Java 项目上工作,而且我没有任何证据表明 Open JDK 这些天会让你失败(虽然我当时遇到了问题)。我想我会撤回我的索赔,直到我遇到其他事情。 (2认同)

Mor*_*hai 35

虽然这里列出的大多数解决方案要么是难以配置的黑客(JDK 9 之前)(代理),要么不再起作用(JDK 9 之后),但我发现没有人提到明确记录的方法真的很令人惊讶。

你可以创建一个自定义的系统类加载器,然后你就可以随心所欲地做任何你想做的事。不需要反射,所有类共享同一个类加载器。

启动 JVM 时添加此标志:

java -Djava.system.class.loader=com.example.MyCustomClassLoader
Run Code Online (Sandbox Code Playgroud)

类加载器必须有一个接受类加载器的构造函数,该类加载器必须设置为其父类。构造函数将在 JVM 启动时被调用并传递真正的系统类加载器,主类将由自定义加载器加载。

要添加罐子,只需调用ClassLoader.getSystemClassLoader()并将其投射到您的班级即可。

查看此实现以获取精心制作的类加载器。请注意,您可以将add()方法更改为公开。

  • 谢谢 - 这真的很有帮助!Web 上的所有其他参考都使用 JDK 8 或更早版本的方法 - 这有多个问题。 (2认同)
  • 正如答案中提到的,原始系统类加载器成为自定义加载器的父加载器,这意味着通过原始类加载器加载的类看不到自定义加载器定义的类,这一点很重要,因为类路径是仍然通过原始加载器处理,即自定义加载器的 getURLs() 不返回类路径,并且当自定义加载器遵循标准委托模型时,对主类的请求将转发到旧的标准加载器将以与没有自定义加载程序相同的方式加载它。 (2认同)

小智 21

这是一个未弃用的版本.我修改了原始文件以删除已弃用的功能.

/**************************************************************************************************
 * 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 嗯.从技术上讲,原始内容是CC许可的,但如果您在此处发布受版权保护的内容,则不会删除内容受版权保护的事实.如果我发布米老鼠的图片,它不会使其获得CC许可.所以我正在添加版权声明. (40认同)
  • 我讨厌破坏一个旧线程,但我想指出stackoverflow上的所有内容都是CC许可的.您的版权声明实际上无效.http://stackoverflow.com/faq#editing (17认同)
  • `addURL()` 在 Java 9 中不起作用,因为自 Java 9 以来 `ClassLoader.getSystemClassLoader()` 不再是 `URLClassLoader`。 (5认同)

fgb*_*fgb 14

使用Java 9,URLClassLoader现在的答案会产生如下错误:

java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader
Run Code Online (Sandbox Code Playgroud)

这是因为使用的类加载器已经改变.相反,要添加到系统类加载器,您可以通过代理使用Instrumentation API.

创建代理类:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

添加META-INF/MANIFEST.MF并将其放在具有代理类的JAR文件中:

Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent
Run Code Online (Sandbox Code Playgroud)

运行代理:

这使用byte-buddy-agent库将代理添加到正在运行的JVM:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 9

我发现的最好的是org.apache.xbean.classloader.JarFileClassLoader,它是XBean项目的一部分.

这是我过去使用的一个简短方法,用于从特定目录中的所有lib文件创建类加载器

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());
}
Run Code Online (Sandbox Code Playgroud)

然后使用类加载器,只需:

classLoader.loadClass(name);
Run Code Online (Sandbox Code Playgroud)


Can*_*ner 6

如果您正在使用Android,则以下代码有效:

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


czd*_*ski 6

另一个使用 Instrumentation 的工作解决方案对我有用。它的优点是修改类加载器搜索,避免依赖类的类可见性问题:

创建代理类

对于此示例,它必须位于命令行调用的同一个 jar 上:

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);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

修改MANIFEST.MF

添加对代理的引用:

Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent
Run Code Online (Sandbox Code Playgroud)

我实际上使用 Netbeans,因此这篇文章有助于了解如何更改 manifest.mf

跑步

仅在 JDK 9+ 上受Launcher-Agent-Class支持,并负责加载代理,而无需在命令行上显式定义它:

 java -jar <your jar>
Run Code Online (Sandbox Code Playgroud)

在 JDK 6+ 上工作的方式是定义参数-javaagent

java -javaagent:<your jar> -jar <your jar>
Run Code Online (Sandbox Code Playgroud)

在运行时添加新的 Jar

然后,您可以根据需要使用以下命令添加 jar:

Agent.appendJarFile(new JarFile(<your file>));
Run Code Online (Sandbox Code Playgroud)

我在文档中使用它没有发现任何问题。


czd*_*ski 5

来自 Allain 的 hackish 解决方案的另一个版本,也适用于 JDK 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});
Run Code Online (Sandbox Code Playgroud)

在 JDK 11 上,它给出了一些弃用警告,但作为在 JDK 11 上使用 Allin 解决方案的人的临时解决方案。

  • 这对 Java 11 不起作用,显示“java.lang.ClassNotFoundException:package01.WorkerImpl”。 (2认同)

Ant*_*aev 5

这是Allain方法使其与Java的较新版本兼容的快速解决方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,它依赖于特定JVM内部实现的知识,因此它不是理想的,也不是通用的解决方案。但是,如果您知道将要使用标准的OpenJDK或Oracle JVM,则这是一个快速简便的解决方法。在将来发布新的JVM版本时,它有时也可能会中断,因此您需要牢记这一点。

  • 使用 Java 11.0.2,我得到:`线程“main”中的异常 java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) 可访问:模块 java .base 不会“打开 jdk.internal.loader”到未命名的模块 @18ef96` (3认同)
  • 添加到虚拟机选项: -add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED (3认同)

Ser*_*ago 5

我知道我来晚了,但我一直在使用pf4j,它是一个插件框架,而且效果很好。

PF4J 是一个微框架,目标是保持核心简单但可扩展。

插件使用示例:

使用 ExtensionPoint 接口标记在应用程序/插件中定义扩展点:

public interface Greeting extends ExtensionPoint {

    String getGreeting();

}
Run Code Online (Sandbox Code Playgroud)

使用注释创建扩展@Extension

@Extension
public class WelcomeGreeting implements Greeting {

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

}
Run Code Online (Sandbox Code Playgroud)

然后您可以根据需要加载和卸载插件:

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();

}
Run Code Online (Sandbox Code Playgroud)

更多详细信息请参阅文档