Динамическая загрузка jar-файлов плагинов с помощью 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. Динамическое расширение пути к классам

Здесь используется известный прием для использования отражения в загрузчике системного класса, чтобы избежать инкапсуляции, чтобы добавить URLs в путь к классам.

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

Опять же, итератор никогда не имеет «следующего» элемента.

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


person MaxArt    schedule 19.04.2013    source источник
comment
почему бы не использовать простую библиотеку с поддержкой отражения? например, общая конфигурация apache с поддержкой bean-компонентов: commons.apache.org / собственно / commons-configuration / userguide /   -  person omer schleifer    schedule 19.04.2013
comment
@omerschleifer Потому что, как я уже сказал, я впервые играю с такими вещами и хочу узнать, как они работают. Во-вторых, ваш совет приветствуется, но у библиотек есть общая проблема, заключающаяся в том, что они могут делать больше, чем вы хотите, поэтому часто все оказывается сложнее, чем необходимо. Я не знаю, так ли это, но я хочу обратиться к внешней библиотеке, только если это мой последний шанс.   -  person MaxArt    schedule 19.04.2013
comment
Если это в образовательных целях, получайте удовольствие. но что касается сложности, рефлексию намного проще выполнить и освоить, используя простую библиотеку, подобную той, которую я вам прислал, чем изобретать колесо самостоятельно. В конце концов, отражение - это хорошо изученная область. удачи   -  person omer schleifer    schedule 19.04.2013
comment
Я хотел использовать этот prinzip для загрузки плагина-jar из любого места на жестком диске. Но по какой-то причине я получаю ClassNotFoudException на iterator.next. У вас есть идеи, как использовать этот принцип с внешними банками?   -  person Sebastian Röher    schedule 07.03.2017


Ответы (3)


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

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

person MaxArt    schedule 19.04.2013

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

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

uses com.foo.spi.Service;

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

provides com.foo.spi.Service with com.bar.ServiceImplementation
person Mordechai    schedule 21.07.2017
comment
Таким образом, это решает проблему поиска плагинов в путях, указанных программой - как? - person foo; 27.03.2021
comment
ServiceLoader не совсем подходит для этого, jar уже должен быть в пути к классам или модулю. - person Mordechai; 30.03.2021
comment
Однако вы можете добавлять банки динамически. Ознакомьтесь с stackoverflow.com/questions/60764/ - person Mordechai; 30.03.2021
comment
Кроме того, конструктор ServiceLoader имеет перегрузки, которые принимают ClassLoader или ModuleLayer, поэтому вы все равно можете выполнять с ним некоторую динамику. - person Mordechai; 31.03.2021

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

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

внизу статьи вы найдете ссылки на

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

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

person ANTARA    schedule 28.01.2015