Dynamisches Laden von Plugin-Jars mit ServiceLoader

Ich versuche ein Pluginsystem für meine Anwendung zu erstellen und möchte mit etwas Einfachem beginnen. Jedes Plugin sollte in eine .jar-Datei gepackt sein und das implementierenSimplePlugin Schnittstelle:

package plugintest;

public interface SimplePlugin {
    public String getName();
}

Jetzt habe ich eine Implementierung von erstelltSimplePlugin, in eine .jar gepackt und in das Plugin / Unterverzeichnis der Hauptanwendung gestellt:

package plugintest;

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

In der Hauptanwendung möchte ich eine Instanz vonPluginTest. Ich habe zwei Alternativen ausprobiert, beide mitjava.util.ServiceLoader.

1. Erweitern Sie den Klassenpfad dynamisch

Hierbei wird der bekannte Hack verwendet, um Reflektionen auf dem Systemklassen-Loader zu verwenden, um eine Kapselung zu vermeiden und diese hinzuzufügenURLs der Klassenpfad.

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

Das Plugins / Verzeichnis wird wie erwartet hinzugefügt (da man den Aufruf überprüfen kann)sysLoader.getURLs()), aber dann der Iterator durch die gegebeneServiceLoader Objekt ist leer.

2. Verwenden von URLClassLoader

Dies verwendet eine andere Definition vonServiceLoader.load mit einem zweiten Argument der KlasseClassLoader.

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

Wieder hat der Iterator nie ein "nächstes" Element.

Ich vermisse mit Sicherheit etwas, da ich zum ersten Mal mit Klassenpfaden "spiele" und lade.

Antworten auf die Frage(3)

Ihre Antwort auf die Frage