为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。

对实现这一功能的简单代码有什么建议吗?


当前回答

另一个使用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);
      }
   }
}

修改MANIFEST。曼氏金融

添加对代理的引用:

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

我实际上使用Netbeans,所以这篇文章有助于如何更改manifest.mf

运行

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

 java -jar <your jar>

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

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

在运行时添加新的Jar

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

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

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

其他回答

以防将来有人搜索这个,这种方法对我来说适用于OpenJDK 13.0.2。

我有许多需要在运行时动态实例化的类,每个类都可能具有不同的类路径。

在这段代码中,我已经有了一个名为pack的对象,它保存了一些关于我试图加载的类的元数据。getObjectFile()方法返回类的类文件的位置。getObjectRootPath()方法返回bin/目录的路径,其中包含我试图实例化的类的类文件。getLibPath()方法返回一个目录的路径,该目录包含构成该类所在模块类路径的jar文件。

File object = new File(pack.getObjectFile()).getAbsoluteFile();
Object packObject;
try {
    URLClassLoader classloader;

    List<URL> classpath = new ArrayList<>();
    classpath.add(new File(pack.getObjectRootPath()).toURI().toURL());
    for (File jar : FileUtils.listFiles(new File(pack.getLibPath()), new String[] {"jar"}, true)) {
        classpath.add(jar.toURI().toURL());
    }
    classloader = new URLClassLoader(classpath.toArray(new URL[] {}));

    Class<?> clazz = classloader.loadClass(object.getName());
    packObject = clazz.getDeclaredConstructor().newInstance();

} catch (Exception e) {
    e.printStackTrace();
    throw e;
}
return packObject;

我以前使用Maven依赖项:org.xeustechnologies:jcl-core:2.8来做这件事,但在JDK 1.8之后,它有时会冻结,并且永远不会返回在Reference::waitForReferencePendingList()中卡住“等待引用”。

我还保留了类加载器的映射,以便在我试图实例化的类与我已经实例化的类在同一个模块中时可以重用它们,我建议这样做。

困难的原因在于安全。类加载器是不可变的;您不应该在运行时随意地向它添加类。我很惊讶它能与系统类加载器一起工作。下面是如何创建你自己的子类加载器:

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

很痛苦,但事实就是这样。

jodonnell提出的解决方案很好,但应该稍加改进。我用这篇文章成功地开发了我的应用程序。

分配当前线程

首先我们要加上

Thread.currentThread().setContextClassLoader(classLoader);

否则你将无法加载资源(如spring/context.xml)存储到jar。

不包括

你的罐子放入父类装入器中,否则你将无法理解谁装入了什么。

请参见使用URLClassLoader重新加载jar的问题

然而,OSGi框架仍然是最好的方法。

对于jar文件的动态上传,您可以使用我对URLClassLoader的修改。这种修改对于在应用程序操作期间更改jar文件没有问题,就像标准的URLClassloader一样。所有加载的jar文件都加载到RAM中,因此独立于原始文件。

内存中的jar和JDBC类装入器

这可能是一个延迟响应,我可以这样做(fastutil-8.2.2.jar的一个简单示例)使用jhplot。来自DataMelt的Web类(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

根据文档,这个文件将在“lib/user”中下载,然后动态加载,因此您可以立即在同一个程序中使用这个jar文件中的类。