为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
当前回答
jodonnell提出的解决方案很好,但应该稍加改进。我用这篇文章成功地开发了我的应用程序。
分配当前线程
首先我们要加上
Thread.currentThread().setContextClassLoader(classLoader);
否则你将无法加载资源(如spring/context.xml)存储到jar。
不包括
你的罐子放入父类装入器中,否则你将无法理解谁装入了什么。
请参见使用URLClassLoader重新加载jar的问题
然而,OSGi框架仍然是最好的方法。
其他回答
我知道我来晚了,但我一直在使用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();
}
欲了解更多细节,请参阅文档
困难的原因在于安全。类加载器是不可变的;您不应该在运行时随意地向它添加类。我很惊讶它能与系统类加载器一起工作。下面是如何创建你自己的子类加载器:
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);
很痛苦,但事实就是这样。
我找到的最好的是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());
}
然后使用类加载器,只需执行:
classLoader.loadClass(name);
对于jar文件的动态上传,您可以使用我对URLClassLoader的修改。这种修改对于在应用程序操作期间更改jar文件没有问题,就像标准的URLClassloader一样。所有加载的jar文件都加载到RAM中,因此独立于原始文件。
内存中的jar和JDBC类装入器
虽然这里列出的大多数解决方案要么是hack(在JDK 9之前)难以配置(代理),要么就是不再工作(在JDK 9之后),但我发现没有人提到一个清晰的文档化方法,这真的很令人惊讶。
您可以创建一个自定义系统类装入器,然后您可以自由地做任何您想做的事情。不需要反射,所有类共享相同的类加载器。
当启动JVM时添加这个标志:
java -Djava.system.class.loader=com.example.MyCustomClassLoader
类加载器必须有一个接受类加载器的构造函数,必须将类加载器设置为父类加载器。构造函数将在JVM启动时调用,真正的系统类加载器将被传递,主类将由自定义加载器加载。
要添加jar,只需调用ClassLoader.getSystemClassLoader()并将其转换为您的类。
请查看此实现,以获得精心制作的类装入器。请注意,您可以将add()方法更改为public。