Подтвердить что ты не робот

Динамическая загрузка плагинов с помощью ServiceLoader

Я пытаюсь создать систему плагинов для своего приложения, и я хочу начать с чего-то простого. Каждый плагин должен быть упакован в файл .jar и реализован интерфейс SimplePlugin:

package plugintest;

public interface SimplePlugin {
    public String getName();
}

Теперь я создал реализацию SimplePlugin, упакованную в .jar и поместив ее в подкаталог plugin/main приложения:

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

Каталог plugins/добавляется как ожидалось (как можно проверить вызов 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());
    }
}

И снова итератор никогда не имеет "следующего" элемента.

Конечно, что-то мне не хватает, так как это первый раз, когда я "играю" с путями классов и загрузкой.

4b9b3361

Ответ 1

Проблема была очень простой. И глупо. В файлах плагинов .jar в каталоге META-INF отсутствовал файл /services/plugintest.SimplePlugin, поэтому ServiceLoader не смог идентифицировать банки как службы и загрузить класс.

Это почти все, второй (и более чистый) способ работает как шарм.

Я думал, что мне нужно поднять мой 200-й ответ... но это для моего вопроса = __ =

Ответ 2

Решение для вашей концепции приложения уже описано в документации Oracle (включая динамическую загрузку JAR)

Создание расширяемых приложений на платформе Java http://www.oracle.com/technetwork/articles/javase/extensible-137159.html

в нижней части статьи вы найдете ссылки на

  • исходный код примера
  • API Javadoc ServiceLoader

По моему мнению, лучше немного изменить пример Oracle, чем изобретать колесо, как сказал Омер Шлейфер.

Ответ 3

Начиная с Java 9 служба, обеспечивающая сканирование, будет намного проще и эффективнее. Больше нет необходимости в META-INF/services.

В объявлении модуля интерфейса объявите:

uses com.foo.spi.Service;

И в модуле провайдера:

provides com.foo.spi.Service with com.bar.ServiceImplementation