I have a great Java desktop application and I want other developers to develop plugins. Plugins will be placed in the specified directory. They will not be on the way to the class at startup. I will download and deploy them at runtime.
The complication is that some plugins will have dependencies on each other, as well as the main application. Therefore, I cannot load each plugin / jar into my own URLClassLoader. Therefore, I want to load all the plugins into 1 URLClassLoader. In addition, some plugins may not be initialized for various reasons. And I only want ClassLoader at the end of the day, who knows about successfully loaded plugins. The reasons are rather strange and are related to some legacies that use reflection to create classes. This should fail if the plugin is not initialized for classes defined inside the plugin flag that failed.
Without this requirement, the solution would be:
- Build jar urls and create ClassLoader based on them
- Try to initialize the plugin class from each jar (defined in config in the manifest)
ClassLoader will now be transferred to the legacy system to use for reflection. Nevertheless, I understand that he will still be able to create instances of classes from plugins whose plugin could not be initialized (since the bank will still be in the URL [] of the Loader class). Therefore, this violates my claim above.
The only solution I came across was to create a custom URLClassLoader as follows (just 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, which essentially knows about a few child classes of 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 download plugin algorithm 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 transfer the multiURLClassLoader instance to the legacy system, and it can only find the classes (via reflection) whose plugin has been successfully loaded.
I did some basic testing and it seems to work the way I would like. But I would really like someone to think about whether this seems like a good idea or not? I've never played with ClassLoaders like this before, and I want to avoid penetrating too deeply too soon before it gets too late.
Thanks!