Как расширить путь к модулю во время выполнения

Мне нравятся услуги. Мне также нравится модульная система. К сожалению для меня, до того, как я использовал Java 9, я привык получать поставщиков услуг из jar-файлов, загружаемых во время выполнения через URLClassLoader, что-то вроде этого (для краткости я буду использовать Java 10 var):

var url = new File("myjar.jar").toURI().toURL();
var cl = new URLClassLoader(new URL[] {url}, getClass().getClassLoader());
var services = ServiceLoader.load(MyService.class, cl);
for (var service : services) {
  ...
}

Это прекрасно работает даже в Java 9 и более поздних версиях, но загружает банку в путь к классам, а это означает, что для поиска поставщиков услуг используется старый метод META-INF\services. Я бы предпочел использовать метод module-info, но для этого вместо этого требуется, чтобы jar загружался по пути модуля, но я не смог найти способ, которым я мог бы это сделать. Итак, я здесь, надеюсь, что кто-то из присутствующих здесь, кто лучше разбирается в модульной системе, скажет мне, как это можно сделать (или что нельзя, если это так).


person Anonymous    schedule 17.01.2019    source источник
comment
@Anonymous Хорошо, не могли бы вы обновить свой вопрос, указав структуру (модуля), которой вы следуете, с точки зрения того, какой модуль имеет службу, какой из них использует, какое объявление используется и обновленный код, пытающийся получить доступ к службам.   -  person Naman    schedule 19.01.2019


Ответы (2)


Самый маленький рабочий пример, который я смог собрать, это

var path = Path.of("myjar.jar");
var cl = new URLClassLoader(new URL[]{path.toUri().toURL()});
var mf = ModuleFinder.of(path);
var cfg = Configuration.resolve(mf, List.of(ModuleLayer.boot().configuration()), mf, Set.of());
var ml = ModuleLayer.defineModulesWithOneLoader(cfg, List.of(ModuleLayer.boot()), cl).layer();
var services = ServiceLoader.load(ml, MyService.class);
services.forEach(System.out::println);

Предполагая, что myjar.jar представляет собой модульную банку, объявляющую о предоставлении MyService реализаций.

person Holger    schedule 17.01.2019
comment
Я собирался внести изменения вместе с терминами, более близкими к спецификации, такими как uses и provides, но последняя строка, в которой вы предположили, что myjar.jar является модульным (с module-info.java), может быть неверна. Похоже, OP вместо этого пытается использовать это как автоматический модуль. - person Naman; 17.01.2019
comment
Это не работает для меня. Он компилируется нормально, но в экземпляре ServiceLoader<MyService> нет никаких значений. Как должны выглядеть файлы module-info.java (как поставщика, так и потребителя)? Также @nullpointer, все банки являются модульными, без участия автоматических модулей. - person Anonymous; 18.01.2019
comment
@Anonymous, если оба они модульные, то производитель provides имя типа, а потребитель uses его. например, ваш текущий модуль должен иметь такое объявление, что uses some.sample.package.MyService - person Naman; 18.01.2019
comment
@nullpointer У меня есть директивы uses и provides в нужных местах, но ServiceLoader все еще пусто. - person Anonymous; 19.01.2019

Оказывается, я использовал неправильные параметры командной строки (я не понимал, что не должен использовать java -jar с модульными банками). Используя правильные команды, ответ @Holger сработал, за исключением того, что набор, переданный Configuration.resolve, должен был содержать все имена загружаемых модулей, что было достаточно легко исправить:

var path = Path.of("myjar.jar");
var cl = new URLClassLoader(new URL[]{path.toUri().toURL()});
var mf = ModuleFinder.of(path);
var cfg = Configuration.resolve(mf, List.of(ModuleLayer.boot().configuration()), mf, mf.findAll().stream().map(module -> module.descriptor().name()).collect(Collectors.toSet()));
var ml = ModuleLayer.defineModulesWithOneLoader(cfg, List.of(ModuleLayer.boot()), cl).layer();
var services = ServiceLoader.load(ml, MyService.class);
services.forEach(System.out::println);
person Anonymous    schedule 19.01.2019
comment
Вы должны использовать resolveAndBind, а не resolve. Если вы перейдете к этому, то обнаружите, что вам не нужно указывать корневые модули. Другой момент заключается в том, что вам также не нужен URLClassLoader cl. Вместо этого вы можете указать загрузчик системного класса в качестве родителя. - person Alan Bateman; 27.01.2019