Динамическая загрузка плагинов с помощью ServiceLoader
Я пытаюсь создать систему плагинов для своего приложения и хочу начать с чего-то простого. Каждый плагин должен быть упакован в файл.jar и реализовывать SimplePlugin
интерфейс:
package plugintest;
public interface SimplePlugin {
public String getName();
}
Теперь я создал реализацию SimplePlugin
, упакованный в.jar и поместив его в подкаталог plugin/ основного приложения:
package plugintest;
public class PluginTest implements SimplePlugin {
public String getName() {
return "I'm the plugin!";
}
}
В основном приложении я хочу получить экземпляр PluginTest
, Я пробовал две альтернативы, обе с использованием java.util.ServiceLoader
,
1. Динамическое расширение пути к классам
Это использует известный хак для использования отражения в системном загрузчике классов, чтобы избежать инкапсуляции, чтобы добавить URL
Это путь к классам.
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();
}
}
}
Каталог плагинов / добавляется, как и ожидалось (как можно проверить вызов sysLoader.getURLs()
), но затем итератор, заданный ServiceLoader
Объект пуст.
2. Использование URLClassLoader
Это использует другое определение ServiceLoader.load
со вторым аргументом класса 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());
}
}
Еще раз, итератор никогда не имеет "следующего" элемента.
Я наверняка что-то упускаю, так как впервые "играю" с путями классов и загрузкой.
3 ответа
Проблема была очень простой. И глупый. В плагине.jar файлы /services/plugintest.SimplePlugin
файл отсутствовал внутри META-INF
каталог, поэтому ServiceLoader
не смог идентифицировать банки как сервисы и загрузить класс.
Вот и все, второй (и более чистый) способ работает как шарм.
Начиная с Java 9 сервис, обеспечивающий сканирование, станет намного проще и эффективнее. Больше не нужно META-INF/services
,
В объявлении модуля интерфейса объявляем:
uses com.foo.spi.Service;
И в модуле провайдера:
provides com.foo.spi.Service with com.bar.ServiceImplementation
Решение для вашей концепции приложения уже описано в документации Oracle (включая динамическую загрузку JAR-файлов).
Создание расширяемых приложений с платформой Java http://www.oracle.com/technetwork/articles/javase/extensible-137159.html
в нижней части статьи вы найдете ссылки на
- исходный код примера
- Javadoc ServiceLoader API
На мой взгляд, лучше немного изменить пример Oracle, чем изобретать велосипед, как сказал Омер Шляйфер.