动态类加载器

时间:2012-08-10 02:38:40

标签: java plugins classloader

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

复杂的是,某些插件将彼此依赖,以及核心应用程序。所以我无法在自己的URLClassLoader中加载每个插件/ jar。因此,我想将所有插件加载到1 URLClassLoader中。此外,某些插件可能由于各种原因而无法初始化。而且我只想在一天结束时知道一个ClassLoader,它知道成功加载的插件。原因很奇怪,并且与使用反射实例化类的一些遗留内容有关。如果插件未针对失败的插件jar中定义的类进行初始化,则需要失败。

如果没有这个要求,解决方案将是:

  1. 收集jar网址并根据它们构建一个ClassLoader
  2. 尝试从每个jar初始化插件类(在清单中的config中定义)
  3. 现在,这里的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);
        }
    }
    

    然后我的加载插件alogorithm将类似

    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 个答案:

答案 0 :(得分:4)

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

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

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

  • 对于每个插件,您可以创建一个单独的ClassLoader,只加载它需要的JAR。可能是最强大的解决方案。但是我看到了一个缺点:作为许多其他依赖项的插件将在不同的类加载器中重复加载。这取决于环境(插件数,JAR大小......)如果这可能是一个问题,它甚至可能是一个优势。
  • 你可以为所有插件设置一个大的ClassLoader,但是你可以按照它们的依赖顺序询问插件类。那些不依赖于任何东西的东西,那些依赖于那些东西的东西等。如果某些插件类无法加载/初始化,你可以立即丢弃所有依赖它的插件。

答案 1 :(得分:1)

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

你可以做一些像PetrPudlák所说的那样,但你应该考虑到你所拥有的解决方案之一可以创建循环依赖...