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ügenURL
s 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.