user1300560
user1300560

Reputation: 275

Dynamic ClassLoader

I have a large desktop Java application and I want to allow other developers to develop plugins for. Plugins will be jars placed in a specified dir. They will not be on the classpath at startup. I will load and deploy them at runtime.

The complication is that some plugins will have dependencies on each other, as well as the core application. So I cannot load each plugin/jar in its own URLClassLoader. Therefore I want to load all plugins into 1 URLClassLoader. Furthermore, some plugins may fail to initialise for various reasons. And I only want a ClassLoader at the end of day that knows about the successfully loaded plugins. The reasons are quite bizarre and relate to some legacy stuff that is using reflection to instantiate classes. This needs to fail if the plugin doesn't initialise for classes defined inside the plugin jar that failed.

Without this requirement, the solution would be:

  1. Collect the jar URLs and build a ClassLoader based on them
  2. Try to initialise a plugin class from each jar (defined in config in the manifest)

Now the ClassLoader here would be passed to the legacy system for it to use for its reflection stuff. However, it's my understanding that it will still be able to instantiate classes from plugin jars whose plugin failed to initialise (since the jar will still in the URL[] of the ClassLoader). Hence this breaks my requirement above.

The only solution I have come up with so far is to create a custom URLClassLoader as follows (simply to allow access to 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);
    }
}

And then I made another custom ClassLoader that essentially knows about multiple child ClassLoaders:

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);
    }
}

Then my loading plugin alogorithm will be something like

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);
    }
}

Then I can pass the multiURLClassLoader instance onto the legacy system and it will only be able to find classes (via reflection) whose plugin successfully loaded.

I've done some basic testing and it seems to work as I'd like so far. But I would very much like someones opinion on whether this seems like a good idea or not? I have never played this much with ClassLoaders before and I am wanting to avoid getting myself in too deep before its too late.

Thanks!

Upvotes: 3

Views: 1748

Answers (2)

SithEngineer
SithEngineer

Reputation: 51

Are you looking for something like the OSGi approach?

You could do something like Petr Pudlák has said, however you should take in account the fact that one of the solutions you have can create cyclic dependencies...

Upvotes: 1

Petr
Petr

Reputation: 63359

The problem I see is that if you don't know in advance which plugin depends on which, it's very hard to do anything reasonable, to debug problems, to isolate non-functional or bad-behaving plugins, etc.

Therefore I'd suggest another option: Add another field into each plugin's manifest, which will say on what other plugins it depends. Perhaps just a list of other plugin JARs it needs to function. (The core application classes would be always available.) I believe this would make the design much more robust and simplify many things.

Then, you could choose from different designs, for example:

  • For each plugin you could create a separate ClassLoader that would load just the JARs it needs. Probably the most robust solution. But I see a drawback: plugins that act as dependencies for many other ones will be loaded repeatedly in different class-loaders. It depends on circumstances (plugin count, JARs size, ...) if this could be a problem or not, it could even be an advantage.
  • You could have one big ClassLoader for all plugins, as you suggest, but you could ask it for plugin classes in the order of their dependencies. The ones that don't depend on anything first, then the ones that depend on those first ones etc. If some plugin class fails to load/initialize, you could immediately discard all plugins that depend on it.

Upvotes: 4

Related Questions