动态类加载器

3
我有一个大型的桌面Java应用程序,我想允许其他开发人员为其开发插件。插件将是放置在指定目录中的JAR文件。它们在启动时不会在类路径上。我将在运行时加载和部署它们。
问题在于,一些插件将彼此依赖,以及核心应用程序。因此,我不能在自己的URLClassLoader中加载每个插件/JAR文件。因此,我想将所有插件加载到一个URLClassLoader中。此外,由于各种原因,一些插件可能无法初始化。而且我只需要在最后拥有一个ClassLoader,它知道成功加载的插件。原因相当奇怪,与使用反射实例化类的一些旧系统有关。如果插件jar文件内定义的类未能初始化,则这将失败。
如果没有这个要求,解决方案将是:
1. 收集jar URL并基于它们构建ClassLoader 2. 尝试从每个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);
    }
}

然后,我创建了另一个自定义的类加载器,它可以理解多个子类加载器:

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,因此希望在为时已晚之前避免陷入深渊。

谢谢!

2个回答

4
我看到的问题是,如果您事先不知道哪个插件依赖于哪个插件,那么很难做出任何合理的事情,调试问题,隔离无功能或表现不佳的插件等。
因此,我建议另一种选择:在每个插件的清单中添加另一个字段,该字段将说明它依赖于哪些其他插件。可能只是它需要运行的其他插件JAR的列表。(核心应用程序类始终可用)。我相信这将使设计更加稳健,并简化许多事情。
然后,您可以从不同的设计中进行选择,例如:
- 为每个插件创建一个单独的ClassLoader,仅加载它所需的JAR。这可能是最稳健的解决方案。但我看到一个缺点:作为许多其他插件的依赖项的插件将在不同的类加载器中重复加载。这取决于情况(插件计数,JAR大小等),这可能是一个问题也可能是一个优势。 - 您可以像您建议的那样拥有一个大的ClassLoader来处理所有插件,但您可以按其依赖关系顺序请求插件类。首先是不依赖任何东西的插件,然后是那些依赖于这些插件的插件等。如果某个插件类无法加载/初始化,则可以立即丢弃所有依赖于它的插件。

1

你是否正在寻找类似于OSGi方法的东西?

你可以像Petr Pudlák所说的那样做,但是你应该考虑到你拥有的解决方案之一可能会创建循环依赖关系...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接