You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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 -> boot
  • allModules -> moduleB -> moduleA -> moduleADeps -> boot
    因此moduleA的服务实现被重复加载。

即使不创建allModulesLayer,直接在各模块层调用ServiceLoader也会出现类似问题:

ServiceLoader.load(layerA, MyService.class); // 返回结果:moduleA.ServiceImpl
ServiceLoader.load(layerB, MyService.class); // 返回结果:moduleB.ServiceImpl, moduleA.ServiceImpl

需求目标

我希望实现以下两种效果之一:

  1. 让ServiceLoader仅非递归加载指定层的服务,这样我可以分别在每个moduleX层运行ServiceLoader后合并结果;
  2. 让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 20:24:50