我有一个大型桌面 Java 应用程序,我想允许其他开发人员为其开发插件。插件将是放置在指定目录中的 jar。它们在启动时不会在类路径上。我将在运行时加载和部署它们。
复杂的是,一些插件将相互依赖,以及核心应用程序。所以我不能在它自己的 URLClassLoader 中加载每个插件/jar。因此,我想将所有插件加载到 1 个 URLClassLoader 中。此外,某些插件可能由于各种原因无法初始化。而且我只想要一个知道成功加载插件的类加载器。原因很奇怪,并且与一些使用反射来实例化类的遗留问题有关。如果插件没有为失败的插件 jar 中定义的类初始化,这需要失败。
如果没有这个要求,解决方案将是:
- 收集 jar URL 并基于它们构建一个 ClassLoader
- 尝试从每个 jar 中初始化一个插件类(在清单中的配置中定义)
现在,这里的 ClassLoader 将被传递给遗留系统以用于其反射内容。但是,据我了解,它仍然能够从插件未能初始化的插件 jar 中实例化类(因为 jar 仍将位于 ClassLoader 的 URL[] 中)。因此,这打破了我上面的要求。
到目前为止,我想出的唯一解决方案是创建一个自定义 URLClassLoader,如下所示(仅允许访问 findClass()):
public class CustomURLClassLoader extends URLClassLoader {
public CustomURLClassLoader(final URL[] urls, final ClassLoader parent) {
super(urls, parent);
}
@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
然后我制作了另一个自定义的 ClassLoader,它基本上知道多个子 ClassLoader:
public class MultiURLClassLoader extends ClassLoader {
private Set<CustomURLClassLoader> loaders = new HashSet<CustomURLClassLoader>();
public MultiURLClassLoader(final ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
Iterator<CustomURLClassLoader> loadersIter = loaders.iterator();
boolean first = true;
while (first || loadersIter.hasNext()) {
try {
if (first) {
return super.findClass(name);
} else {
return loadersIter.next().findClass(name);
}
} catch (ClassNotFoundException e) {
first = false;
}
}
throw new ClassNotFoundException(name);
}
public void addClassLoader(final CustomURLClassLoader classLoader) {
loaders.add(classLoader);
}
public void removeClassLoader(final CustomURLClassLoader classLoader) {
loaders.remove(classLoader);
}
}
然后我的加载插件算法将类似于
MultiURLClassLoader multiURLClassLoader = new MultiURLClassLoader(ClassLoader.getSystemClassLoader());
for (File pluginJar : new File("plugindir").listFiles()) {
CustomURLClassLoader classLoader = null;
try {
URL pluginURL = pluginJar.toURI().toURL();
final URL[] pluginJarUrl = new URL[] { pluginURL };
classLoader = new CustomURLClassLoader(pluginJarUrl, multiURLClassLoader);
multiURLClassLoader.addClassLoader(classLoader);
Class<?> clazz = Class.forName("some.PluginClass", false, multiURLClassLoader);
Constructor<?> ctor = clazz.getConstructor();
SomePluginInterface plugin = (SomePluginInterface)ctor1.newInstance();
plugin.initialise();
} catch (SomePluginInitialiseException e) {
multiURLClassLoader.removeClassLoader(classLoader);
}
}
然后我可以将 multiURLClassLoader 实例传递给遗留系统,它只能找到插件成功加载的类(通过反射)。
我已经做了一些基本的测试,到目前为止它似乎可以正常工作。但我非常希望有人对这是否是个好主意提出意见?我以前从未在 ClassLoaders 上玩过这么多,我想避免在为时已晚之前让自己陷入太深。
谢谢!