ServiceLoader с UrlClassLoader не находит службу

Я пытаюсь загрузить плагины из определенного файла jar, используя java ServiceLoader с UrlClassLoader, но я просто не могу заставить его найти мои классы плагинов. Сборка обоих модулей работает, но всякий раз, когда я запускаю приведенный ниже код, я получаю java.util.NoSuchElementException, хотя путь к файлу правильный, и я не вижу, где я напортачил.

У меня есть модули в моем проекте IntelliJ: Test (приложение, загружающее плагин, а также предоставляющее ServiceProvider) и PluginTest (загружаемый плагин).

Это скриншот структуры тестового модуля:

А вот скриншот структуры модуля PluginTest:

Вот поставщик плагинов ServiceProvider:

package net.lbflabs;

public abstract class PluginProvider {

    public abstract String getSecretMessage(int message);
}

Это плагин, который расширяет этот класс:

package net.lbflabs.plugins;

import net.lbflabs.PluginProvider;

public class PluginClass extends PluginProvider {
    @Override
    public String getSecretMessage(int message) {
        return "Here is my secret.";
    }
}

Вот основной класс, загружающий плагин:

package net.lbflabs;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ServiceLoader;

public class Main {

    private static String path = "C:/Users/jacke/IdeaProjects/Tests/out/artifacts/PluginTest_jar/PluginTest.jar";

    public static void main(String[] args) throws MalformedURLException {
        System.out.println("Initializing... \nPreparing to load JAR from Path " + path + "...");
        File file = new File(path);
        System.out.println(file.exists()); //Prints true, so file does exist
        URLClassLoader c = new URLClassLoader((new URL[]{file.getAbsoluteFile().toURI().toURL()}));
        ServiceLoader<PluginProvider> loader = ServiceLoader.load(PluginProvider.class, c);
        PluginProvider p = loader.iterator().next(); // Throws the java.util.NoSuchElementException
        System.out.println("Secret message in plugin: " + p.getSecretMessage(1));
    }
}

Вот вывод при запуске модуля Tests:

"C:\Program Files\Java\jdk-14.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3\lib\idea_rt.jar=50330:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\jacke\IdeaProjects\Tests\out\production\Tests net.lbflabs.Main
Initializing... 
Preparing to load JAR from Path C:/Users/jacke/IdeaProjects/Tests/out/artifacts/PluginTest_jar/PluginTest.jar...
true
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.ServiceLoader$2.next(ServiceLoader.java:1310)
    at java.base/java.util.ServiceLoader$2.next(ServiceLoader.java:1298)
    at java.base/java.util.ServiceLoader$3.next(ServiceLoader.java:1396)
    at net.lbflabs.Main.main(Main.java:19)

Process finished with exit code 1

Я считаю, что сервисный файл в модуле PluginTest назван правильно (net.lbflabs.PluginProvider) и содержит допустимое имя класса (net.lbflabs.plugins.PluginClass). Если бы это было неправильно, IntelliJ, вероятно, нашел бы проблему, поскольку, когда я добавляю опечатку, я получаю предупреждения.

Если кому-то это нужно, я могу предоставить весь проект в формате .rar (скажите, пожалуйста, как вы хотите, чтобы я им поделился, например, я создаю ссылку на onedrive)

PS: я уже задавал этот вопрос здесь, но, поскольку я работал над более крупным проектом и не решался поделиться в нем всем кодом (и поскольку единственный ответ, который я получил, не сработал, вероятно, из-за недостаточной информации от я), я хотел опубликовать проблему с минимально работоспособным примером, который выдает то же исключение, надеюсь, это нормально.

Загрузчик классов не содержит родительский класс, а только класс реализации, поэтому он не может найти родительский класс. Поэтому я подозреваю, что из-за неполной иерархии классов он не может загрузить этот класс. Поэтому вы должны использовать конструктор, который берет загрузчик родительского класса и устанавливает его в текущий. Чтобы можно было определить правильную иерархию классов.

M. Deinum 21.12.2020 10:48

Не выдаст ли тогда другое исключение? У меня были исключения для загрузчика классов, такие как NoClassDefFoundError, в аналогичных случаях. Возможно, ты все еще прав. Как бы я решил проблему? Не могли бы вы опубликовать свое исправление в качестве ответа, чтобы я потенциально мог его принять?

Jakob Tinhofer 21.12.2020 10:52

Не было бы первым, когда одно исключение скрыто другим и, глядя на LazyClassPathLookupIterator (внутренний класс), оно фактически ловит ClassNotFoundException и просто возвращает null. Что приводит к исключению, которое вы получаете.

M. Deinum 21.12.2020 10:57
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
1 262
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я заметил одну вещь: вы создаете загрузчик классов, который содержит только 1 класс. При этом он не сможет создать правильную иерархию классов и, скорее всего, потерпит неудачу. Вместо этого передайте текущий Classloader в качестве родителя вашему URLClassLoader.

ClassLoade parent = PluginProvider.class.getClassLoader();
URL[] urls = new URL[] { file.getAbsoluteFile().toURI().toURL()};
URLClassLoader c = new URLClassLoader(urls, parent);

Теперь должна быть возможность построить правильную иерархию.

Другое дело, что в структуре вашего проекта ваш каталог META-INF не находится в каталоге src, содержащем исходники для проекта. Это означает, что Intellij оставит их вне банки. Таким образом, вы в основном добавляете банку без соответствующих файлов. Он содержит только класс, а не META-INF/services/net.lbflabs.PluginProvider.

Спасибо. Я добавил загрузчик класса PluginProvider в urlclassloader, но он все равно почему-то не работает.

Jakob Tinhofer 21.12.2020 10:59

Но вы по-прежнему получаете то же исключение или другое? Или попробуйте вместо этого ClassLoader.getSystemClassLoader.

M. Deinum 21.12.2020 11:00

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

M. Deinum 21.12.2020 11:15

Странный. Я пробовал с загрузчиком системных классов, все то же исключение. Может быть, это моя ошибка IntelliJ noob. Я мог бы поделиться проектом, хотя знаю, что это потребует от вас немного больше усилий.

Jakob Tinhofer 21.12.2020 11:18

Единственная разница в том, что я создал проект maven вместо простого проекта Java. Действительно ли банка содержит каталог META-INF с каталогом и файлом сервисов? META-INF должен быть в src, иначе он не будет экспортирован в банку. Ваш jar не содержит файл и, следовательно, не плагин.

M. Deinum 21.12.2020 11:29

Ох, хорошо. Итак, я мог бы использовать maven, и не было бы реальной разницы в структуре проекта?

Jakob Tinhofer 21.12.2020 11:54

Преобразование проекта в maven устранило все проблемы, спасибо. Вы хотите отредактировать свой ответ с решением в комментариях, чтобы я мог его принять?

Jakob Tinhofer 21.12.2020 12:29

Что ж, я подозреваю, что настоящая проблема в том, что META-INF в вашем исходном сообщении не было под src, что означает отсутствие всей структуры и файла загрузчика службы. При переходе на maven вы, вероятно, вместо этого поместите META-INF в src/main/resources.

M. Deinum 21.12.2020 13:50

Я хотел бы принять ответ, но ваш ответ не решил проблему, а комментарий. Не могли бы вы отредактировать его?

Jakob Tinhofer 21.12.2020 13:54

Просто редактировал, пока вы комментировали :).

M. Deinum 21.12.2020 13:56

О, хорошо, извини: D Еще раз спасибо за вашу помощь

Jakob Tinhofer 21.12.2020 13:59

Другие вопросы по теме