Carregando dinamicamente frascos de plug-in usando o ServiceLoader

Estou tentando criar um sistema de plugins para meu aplicativo e quero começar com algo simples. Cada plugin deve ser empacotado em um arquivo .jar e implementar oSimplePlugin interface:

package plugintest;

public interface SimplePlugin {
    public String getName();
}

Agora eu criei uma implementação deSimplePlugin, empacotado em um .jar e coloque-o no subdiretório plugin / da aplicação principal:

package plugintest;

public class PluginTest implements SimplePlugin {
    public String getName() {
        return "I'm the plugin!";
    }
}

No aplicativo principal, quero obter uma instância dePluginTest. Eu tentei duas alternativas, ambas usandojava.util.ServiceLoader.

1. Estendendo dinamicamente o caminho de classe

Isso usa o hack conhecido para usar a reflexão no carregador de classes do sistema para evitar o encapsulamento, a fim de adicionarURLs o caminho de classe.

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

Os plugins / diretório são adicionados como esperado (como se pode verificarsysLoader.getURLs()), mas depois o iterador dado peloServiceLoader objeto está vazio.

2. Usando o URLClassLoader

Isso usa outra definição deServiceLoader.load com um segundo argumento da classeClassLoader.

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

Mais uma vez, o iterador nunca tem um elemento "próximo".

Certamente há algo que estou perdendo, já que é a primeira vez que estou "jogando" com caminhos de classe e carregamento.

questionAnswers(3)

yourAnswerToTheQuestion