Dynamically loading plugin jars using ServiceLoader
classpath, jar, java, serviceloader
Solution
The problem was very simple. And stupid. In the plugin .jar files the `/services/plugintest.SimplePlugin` file was missing inside the `META-INF` directory, so the `ServiceLoader` couldn't identify the jars as services and load the class.
That's pretty much all, the second (and cleaner) way works like a charm.
Problem
I'm trying to create a plugin system for my application, and I want to start with something simple. Every plugin should be packed in a .jar file and implement the `SimplePlugin` interface: ``` package plugintest; public interface SimplePlugin { public String getName(); } ``` Now I've created an implementation of `SimplePlugin`, packed in a .jar and put it in the plugin/ subdirectory of the main application: ``` package plugintest; public class PluginTest implements SimplePlugin { public String getName() { return "I'm the plugin!"; } } ``` In the main application, I want to get an instance of `PluginTest`. I've tried two alternatives, both using `java.util.ServiceLoader`. 1. Dynamically extending the classpath This uses the known hack to use reflection on the system class loader to avoid encapsulation, in order to add `URL`s the the classpath. ``` package plugintest.system; import plugintest.SimplePlugin; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Iterator; import java.util.ServiceLoader; public class ManagePlugins { public static void main(String[] args) throws IOException { File loc = new File("plugins"); extendClasspath(loc); ServiceLoader<SimplePlugin> sl = ServiceLoader.load(SimplePlugin.class); Iterator<SimplePlugin> apit = sl.iterator(); while (apit.hasNext()) System.out.println(apit.next().getName()); } private static void extendClasspath(File dir) throws IOException { URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); URL urls[] = sysLoader.getURLs(), udir = dir.toURI().toURL(); String udirs = udir.toString(); for (int i = 0; i < urls.length; i++) if (urls[i].toString().equalsIgnoreCase(udirs)) return; Class<URLClassLoader> sysClass = URLClassLoader.class; try { Method method = sysClass.getDeclaredMethod("addURL", new Class[]{URL.class}); method.setAccessible(true); method.invoke(sysLoader, new Object[] {udir}); } catch (Throwable t) { t.printStackTrace(); } } } ``` The plugins/ directory is added as expected (as one can check calling `sysLoader.getURLs()`), but then the iterator given by the `ServiceLoader` object is empty. 2. Using URLClassLoader This uses another definition of `ServiceLoader.load` with a second argument of the class `ClassLoader`. ``` package plugintest.system; import plugintest.SimplePlugin; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Iterator; import java.util.ServiceLoader; public class ManagePlugins { public static void main(String[] args) throws IOException { File loc = new File("plugins"); File[] flist = loc.listFiles(new FileFilter() { public boolean accept(File file) {return file.getPath().toLowerCase().endsWith(".jar");} }); URL[] urls = new URL[flist.length]; for (int i = 0; i < flist.length; i++) urls[i] = flist[i].toURI().toURL(); URLClassLoader ucl = new URLClassLoader(urls); ServiceLoader<SimplePlugin> sl = ServiceLoader.load(SimplePlugin.class, ucl); Iterator<SimplePlugin> apit = sl.iterator(); while (apit.hasNext()) System.out.println(apit.next().getName()); } } ``` Once again, the iterator has never a "next" element. There's surely something I'm missing since it's the first time I'm "playing" with class paths and loading.