为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
当前回答
困难的原因在于安全。类加载器是不可变的;您不应该在运行时随意地向它添加类。我很惊讶它能与系统类加载器一起工作。下面是如何创建你自己的子类加载器:
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);
很痛苦,但事实就是这样。
其他回答
如果你在Android上工作,下面的代码可以工作:
String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");
我个人认为java.util.ServiceLoader做得很好。你可以在这里得到一个例子。
下面是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);
}
请注意,它依赖于特定JVM内部实现的知识,因此它不是理想的,也不是通用的解决方案。但是,如果您知道您将使用标准的OpenJDK或Oracle JVM,这是一种快速而简单的解决方法。在将来发布新的JVM版本时,它也可能会崩溃,所以您需要记住这一点。
你应该看一看OSGi,比如在Eclipse平台中实现的。它就是这么做的。您可以安装、卸载、启动和停止所谓的包,这些包实际上是JAR文件。但是它做的更多,例如,它提供了可以在运行时在JAR文件中动态发现的服务。
或参阅Java模块系统的规范。
另一个使用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>));
我在文档中没有发现任何问题。