Dynamically loading plugin jars using ServiceLoader

MaxArt picture MaxArt · Apr 19, 2013 · Viewed 27.9k times · Source

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 URLs 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.

Answer

MaxArt picture MaxArt · Apr 19, 2013

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.