如何在运行时扩展模块路径

6

我喜欢服务。我也喜欢模块系统。不幸的是,在我使用Java 9之前,我养成了通过URLClassLoader在运行时从加载的jar获取服务提供程序的习惯,类似于这样(为了简洁起见,我将使用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及以后的版本中也能正常工作,但它是在classpath上加载jar包,这意味着它使用旧的META-INF\services方法查找服务提供者。我更愿意使用module-info方法,但这需要将jar包加载到模块路径上,但我找不到任何方法来做到这一点。所以我在这里,希望有人能告诉我如何实现这一点(如果有这样的情况的话)或者告诉我不可能。


1
@Anonymous 好的,您能否更新您的问题,说明您正在遵循哪个模块结构,哪个模块拥有服务,哪个模块正在使用,使用了哪些声明以及尝试访问服务的更新代码。 - Naman
2个回答

5

我能提供的最小工作示例是:

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 是一个模块化的 jar 包,声明提供了 MyService 的实现。

1
我本来想将术语更接近规范,如“uses”和“provides”,一起进行编辑,但是最后一行你假设myjar.jar是模块化的(带有module-info.java),这可能不是真的。看起来OP试图将其用作自动模块。 - Naman
这对我不起作用。它可以编译,但是 ServiceLoader<MyService> 实例中没有任何值。module-info.java 文件(提供程序和使用程序)应该长什么样子?另外 @nullpointer,所有的 jar 都是模块化的,没有自动模块参与。 - Anonymous
1
如果它们都是模块化的,那么生产者会提供类型名称,而消费者则会使用它。例如,您当前的模块应该有一个声明,如uses some.sample.package.MyService - Naman
@nullpointer 我已经在正确的位置使用了 usesprovides 指令,但是 ServiceLoader 仍然为空。 - Anonymous

0
原来我使用了错误的命令行选项(我没有意识到我不应该在模块化的JAR文件中使用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);

你应该使用resolveAndBind而不是resolve。如果你转换到这个方法,你会发现你不需要指定根模块。另一个要点是你也不需要URLClassLoader cl。相反,你可以将系统类加载器指定为父级。 - Alan Bateman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接