Dynamiczne ładowanie słoików wtyczek za pomocą ServiceLoadera
Próbuję stworzyć system wtyczek dla mojej aplikacji i chcę zacząć od czegoś prostego. Każda wtyczka powinna być zapakowana w plik .jar i zaimplementowaćSimplePlugin
berło:
package plugintest;
public interface SimplePlugin {
public String getName();
}
Teraz stworzyłem implementacjęSimplePlugin
, zapakowane w .jar i umieść je w podkatalogu plugin / głównej aplikacji:
package plugintest;
public class PluginTest implements SimplePlugin {
public String getName() {
return "I'm the plugin!";
}
}
W głównej aplikacji chcę uzyskać wystąpieniePluginTest
. Wypróbowałem dwie alternatywy, obie używającejava.util.ServiceLoader
.
1. Dynamiczne rozszerzanie ścieżki klasy
Używa znanego hacku, aby użyć refleksji nad programem ładującym klasy systemu, aby uniknąć enkapsulacji, aby dodaćURL
s Ścieżka klasy.
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();
}
}
}
Katalog wtyczek / katalogów jest dodawany zgodnie z oczekiwaniami (ponieważ można sprawdzić wywołanie)sysLoader.getURLs()
), ale potem iterator podany przezServiceLoader
obiekt jest pusty.
2. Korzystanie z URLClassLoader
To używa innej definicjiServiceLoader.load
z drugim argumentem klasyClassLoader
.
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());
}
}
Po raz kolejny iterator nigdy nie ma elementu „następnego”.
Z pewnością brakuje mi czegoś, ponieważ po raz pierwszy „bawię się” ścieżkami klas i ładowaniem.