如何让JPMS ServiceLoader非递归加载或避免重复服务
模块层架构下ServiceLoader重复加载服务的问题
我正在尝试从文件夹动态加载模块,现有两个模块:
- Module A:拥有独立依赖
- Module B:拥有自身依赖,同时依赖Module A的部分功能
为避免多模块依赖同一依赖但版本不同引发冲突,我让各模块的依赖相互独立,模块层结构如下(每个框代表独立层):
ModuleLayers Tree
- allModules(顶层)
- moduleA
- moduleADeps
- boot(引导层)
- moduleB
- moduleBDeps
- moduleA
- moduleADeps
- boot(引导层)
问题现象
在该模块层配置下,运行以下ServiceLoader代码:
ServiceLoader.load(allModulesLayer, MyService.class);
得到的结果数组为:[ moduleA.ServiceImpl@..., moduleB.ServiceImpl@..., moduleA.ServiceImpl@... ]
可见ServiceLoader会从指定层递归遍历至引导层,此时存在两条遍历路径均会遇到moduleA:
allModules -> moduleA -> moduleADeps -> bootallModules -> moduleB -> moduleA -> moduleADeps -> boot
因此moduleA的服务实现被重复加载。
即使不创建allModulesLayer,直接在各模块层调用ServiceLoader也会出现类似问题:
ServiceLoader.load(layerA, MyService.class); // 返回结果:moduleA.ServiceImpl ServiceLoader.load(layerB, MyService.class); // 返回结果:moduleB.ServiceImpl, moduleA.ServiceImpl
需求目标
我希望实现以下两种效果之一:
- 让ServiceLoader仅非递归加载指定层的服务,这样我可以分别在每个
moduleX层运行ServiceLoader后合并结果; - 让ServiceLoader自动过滤重复的服务实现类(比如在使用
allModulesLayer的场景下,避免重复加载moduleA的服务)。
补充说明:layerB.modules()仅返回moduleB,说明moduleB层本身并不包含moduleA,但ServiceLoader仍能从其依赖路径中发现moduleA的服务。
模块加载代码
加载Module A的代码
// Module A的依赖位于"./moduleA/dependencies"目录 ModuleFinder finderADeps = ModuleFinder.of(Paths.get("moduleA", "dependencies")); // 获取该目录下的所有依赖模块名称 List<String> allADeps = finderADeps.findAll().stream() .map(ModuleReference::descriptor) .map(ModuleDescriptor::name) .toList(); // 解析所有依赖 Configuration configADeps = ModuleFinder.boot().configuration() .resolve(finderADeps, ModuleFinder.of(), allADeps); // 将依赖模块加载到"Module A 依赖层" ModuleLayer layerADeps = ModuleFinder.boot() .defineModulesWithOneLoader(configADeps, ClassLoader.getSystemClassLoader()); // 定义"Module A层"的配置,父配置为依赖层的配置 Configuration configA = Configuration.resolve(finder, List.of(configADeps), ModuleFinder.of(), Set.of("moduleA")); // 加载Module A到"Module A层",父层为依赖层 ModuleLayer layerA = ModuleLayer.defineModulesWithOneLoader(configA, List.of(layerADeps), ClassLoader.getSystemClassLoader()).layer();
加载Module B的代码
加载Module B的代码逻辑基本一致,但构建Module B层时会引入Module A的配置和层作为父项:
// 将configA加入父配置列表 Configuration configB = Configuration.resolveAndBind(finder, List.of(configA, configBDeps), ModuleFinder.of(), Set.of("moduleB")); // 将layerA加入父层列表 ModuleLayer layerB = ModuleLayer.defineModulesWithOneLoader(configB, List.of(layerA, layerBDeps), ClassLoader.getSystemClassLoader()).layer();
模块的module-info定义
moduleA的module-info.java
import ...; module moduleA { requires core.api; exports com.module_a; provides MyService with ServiceImpl; // 其他依赖定义 }
moduleB的module-info.java
import ...; module moduleB { requires core.api; exports com.module_b; requires static moduleA; provides MyService with ServiceImpl; // 其他依赖定义 }
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

