为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
当前回答
我知道我来晚了,但我一直在使用pf4j,这是一个插件框架,它工作得非常好。
PF4J是一个微框架,其目标是保持核心简单但可扩展。
一个插件使用的例子:
在你的应用/插件中使用ExtensionPoint接口标记定义一个扩展点:
public interface Greeting extends ExtensionPoint {
String getGreeting();
}
使用@Extension注释创建一个扩展:
@Extension
public class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}
然后你可以加载和卸载插件,如你所愿:
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();
}
欲了解更多细节,请参阅文档
其他回答
虽然这里列出的大多数解决方案要么是hack(在JDK 9之前)难以配置(代理),要么就是不再工作(在JDK 9之后),但我发现没有人提到一个清晰的文档化方法,这真的很令人惊讶。
您可以创建一个自定义系统类装入器,然后您可以自由地做任何您想做的事情。不需要反射,所有类共享相同的类加载器。
当启动JVM时添加这个标志:
java -Djava.system.class.loader=com.example.MyCustomClassLoader
类加载器必须有一个接受类加载器的构造函数,必须将类加载器设置为父类加载器。构造函数将在JVM启动时调用,真正的系统类加载器将被传递,主类将由自定义加载器加载。
要添加jar,只需调用ClassLoader.getSystemClassLoader()并将其转换为您的类。
请查看此实现,以获得精心制作的类装入器。请注意,您可以将add()方法更改为public。
如果你在Android上工作,下面的代码可以工作:
String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");
另一个使用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>));
我在文档中没有发现任何问题。
在Java 9中,URLClassLoader的答案现在会给出如下错误:
java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader
这是因为所使用的类装入器发生了变化。相反,要添加到系统类装入器,您可以通过代理使用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));
}
}
添加meta - inf / MANIFEST。MF并将其与代理类一起放在JAR文件中:
Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent
运行代理:
使用bytes -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());
}
}
下面是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版本时,它也可能会崩溃,所以您需要记住这一点。