Я пытаюсь загрузить плагины из определенного файла 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: я уже задавал этот вопрос здесь, но, поскольку я работал над более крупным проектом и не решался поделиться в нем всем кодом (и поскольку единственный ответ, который я получил, не сработал, вероятно, из-за недостаточной информации от я), я хотел опубликовать проблему с минимально работоспособным примером, который выдает то же исключение, надеюсь, это нормально.
Не выдаст ли тогда другое исключение? У меня были исключения для загрузчика классов, такие как NoClassDefFoundError, в аналогичных случаях. Возможно, ты все еще прав. Как бы я решил проблему? Не могли бы вы опубликовать свое исправление в качестве ответа, чтобы я потенциально мог его принять?
Не было бы первым, когда одно исключение скрыто другим и, глядя на LazyClassPathLookupIterator
(внутренний класс), оно фактически ловит ClassNotFoundException
и просто возвращает null
. Что приводит к исключению, которое вы получаете.
Я заметил одну вещь: вы создаете загрузчик классов, который содержит только 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, но он все равно почему-то не работает.
Но вы по-прежнему получаете то же исключение или другое? Или попробуйте вместо этого ClassLoader.getSystemClassLoader
.
Странно, я не могу это воспроизвести. Точный образец с системой или загрузчиком классов от PluginProvider действительно дает ожидаемый результат.
Странный. Я пробовал с загрузчиком системных классов, все то же исключение. Может быть, это моя ошибка IntelliJ noob. Я мог бы поделиться проектом, хотя знаю, что это потребует от вас немного больше усилий.
Единственная разница в том, что я создал проект maven вместо простого проекта Java. Действительно ли банка содержит каталог META-INF с каталогом и файлом сервисов? META-INF
должен быть в src
, иначе он не будет экспортирован в банку. Ваш jar не содержит файл и, следовательно, не плагин.
Ох, хорошо. Итак, я мог бы использовать maven, и не было бы реальной разницы в структуре проекта?
Преобразование проекта в maven устранило все проблемы, спасибо. Вы хотите отредактировать свой ответ с решением в комментариях, чтобы я мог его принять?
Что ж, я подозреваю, что настоящая проблема в том, что META-INF
в вашем исходном сообщении не было под src
, что означает отсутствие всей структуры и файла загрузчика службы. При переходе на maven вы, вероятно, вместо этого поместите META-INF
в src/main/resources
.
Я хотел бы принять ответ, но ваш ответ не решил проблему, а комментарий. Не могли бы вы отредактировать его?
Просто редактировал, пока вы комментировали :).
О, хорошо, извини: D Еще раз спасибо за вашу помощь
Загрузчик классов не содержит родительский класс, а только класс реализации, поэтому он не может найти родительский класс. Поэтому я подозреваю, что из-за неполной иерархии классов он не может загрузить этот класс. Поэтому вы должны использовать конструктор, который берет загрузчик родительского класса и устанавливает его в текущий. Чтобы можно было определить правильную иерархию классов.