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

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


当前回答

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

其他回答

虽然这里列出的大多数解决方案要么是hack(在JDK 9之前)难以配置(代理),要么就是不再工作(在JDK 9之后),但我发现没有人提到一个清晰的文档化方法,这真的很令人惊讶。

您可以创建一个自定义系统类装入器,然后您可以自由地做任何您想做的事情。不需要反射,所有类共享相同的类加载器。

当启动JVM时添加这个标志:

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

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

要添加jar,只需调用ClassLoader.getSystemClassLoader()并将其转换为您的类。

请查看此实现,以获得精心制作的类装入器。请注意,您可以将add()方法更改为public。

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

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

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

分配当前线程

首先我们要加上

Thread.currentThread().setContextClassLoader(classLoader);

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

不包括

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

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

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

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

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

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

你应该看一看OSGi,比如在Eclipse平台中实现的。它就是这么做的。您可以安装、卸载、启动和停止所谓的包,这些包实际上是JAR文件。但是它做的更多,例如,它提供了可以在运行时在JAR文件中动态发现的服务。

或参阅Java模块系统的规范。