【问题标题】:Dynamic ClassLoader动态类加载器
【发布时间】:2012-08-07 08:18:05
【问题描述】:

我有一个大型桌面 Java 应用程序,我希望允许其他开发人员为其开发插件。插件将是放置在指定目录中的 jar。它们在启动时不会在类路径上。我将在运行时加载和部署它们。

复杂之处在于一些插件会相互依赖,以及核心应用程序。所以我不能在它自己的 URLClassLoader 中加载每个插件/jar。因此,我想将所有插件加载到 1 个 URLClassLoader 中。此外,某些插件可能由于各种原因无法初始化。而且我只想要一个知道成功加载插件的类加载器。原因很奇怪,并且与一些使用反射来实例化类的遗留问题有关。如果插件没有为失败的插件 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);
    }
}

然后我制作了另一个自定义 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,我想避免在为时已晚之前让自己陷入太深。

谢谢!

【问题讨论】:

    标签: java plugins classloader


    【解决方案1】:

    我看到的问题是,如果您事先不知道哪个插件依赖于哪个插件,那么很难做任何合理的事情,调试问题,隔离无功能或行为不良的插件等。

    因此我建议另一种选择:在每个插件的清单中添加另一个字段,这将说明它依赖于哪些其他插件。也许只是它需要运行的其他插件 JAR 的列表。 (核心应用程序类将始终可用。)我相信这将使设计更加健壮并简化许多事情。

    然后,您可以从不同的设计中进行选择,例如:

    • 对于每个插件,您可以创建一个单独的 ClassLoader,它只会加载它需要的 JAR。可能是最强大的解决方案。但我看到了一个缺点:充当许多其他依赖项的插件将在不同的类加载器中重复加载。这取决于具体情况(插件数量、JAR 大小等),如果这可能是一个问题,它甚至可能是一个优势。
    • 按照您的建议,您可以为所有插件使用一个大的 ClassLoader,但您可以按照插件类的依赖顺序向它请求插件类。首先不依赖任何东西的那些,然后依赖于那些第一个等等的那些。如果某些插件类无法加载/初始化,您可以立即丢弃所有依赖它的插件。

    【讨论】:

      【解决方案2】:

      您是否正在寻找类似OSGi 的方法?

      您可以像 Petr Pudlák 所说的那样做一些事情,但是您应该考虑这样一个事实,即您拥有的解决方案之一可以创建循环依赖项......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-04
        • 2014-09-27
        • 2021-02-08
        • 2012-10-10
        • 2013-05-05
        相关资源
        最近更新 更多