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

如何通过Maven Surefire插件在测试时添加JPMS模块及解析失败原因

JPMS模块测试中引入临时服务提供者的问题及解决

场景说明

我使用Maven和maven-surefire-plugin(3.0.0版本)开发Java模块系统(JPMS)项目:

  • fancythingprovider模块:通过provides ThingProvider with FancyThingProvider声明服务提供者
  • thinguser模块:通过uses ThingProvider声明服务依赖,其中ThingUser类负责通过ServiceLoader加载服务

基础测试已正常运行:JUnit测试类使用ThingUser可以成功找到FancyThingProvider。

现在需要仅在测试阶段引入额外的simplethingprovider模块,让ThingUser同时发现FancyThingProvider和SimpleThingProvider,但不想在thinguser的主module-info.java中添加requires simplethingprovider(因为主模块本身不依赖该模块)。

尝试过的无效方案

  1. 在maven-surefire-plugin的argLine中添加--add-modules simplethingprovider:失败,因为simplethingprovider不在Surefire运行时的模块路径上,也未被其他模块引用,无法加入模块图。
  2. 意识到simplethingprovider处于测试类路径(属于未命名模块),添加--add-reads thinguser=ALL-UNNAMED:仍未生效。
  3. 为simplethingprovider补充了传统SPI配置文件/META-INF/services/ThingProvider(内容为SimpleThingProvider),因为类路径下的模块module-info.java会失效,但--add-reads依旧没有效果。

为什么--add-reads方案不生效?

--add-reads thinguser=ALL-UNNAMED仅能让thinguser模块读取未命名模块中的类,但服务发现的核心逻辑并不受此影响:

  • 当模块中的代码调用ServiceLoader.load(ThingProvider.class)时,默认只会扫描模块路径中已解析的模块(通过provides声明的服务),以及调用者所在模块的类资源。
  • 未命名模块中的SPI配置不会被模块上下文的ServiceLoader自动扫描,因为JPMS的服务发现逻辑在模块和未命名模块之间是隔离的——模块的ServiceLoader不会主动去未命名模块中查找服务,哪怕配置了--add-reads。
  • 加上Surefire的测试机制:测试类会被--patch-module合并到主模块中,测试代码的运行上下文依然是thinguser模块,而非未命名模块,所以服务加载逻辑还是局限在模块路径范围内。

可行解决方案

方案1:将simplethingprovider放到模块路径

修改maven-surefire-plugin配置,确保simplethingprovider被加入模块路径,同时用--add-modules将其引入模块图:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0</version>
  <configuration>
    <!-- 开启模块路径模式,让测试依赖进入模块路径 -->
    <useModulePath>true</useModulePath>
    <!-- 强制引入simplethingprovider模块 -->
    <argLine>--add-modules simplethingprovider</argLine>
  </configuration>
</plugin>

开启useModulePath=true后,Surefire会自动将测试依赖(包括simplethingprovider)放到模块路径,此时--add-modules可以成功将其加入模块图,simplethingprovider的module-info.java中的provides声明会被正常识别,无需额外SPI文件。

方案2:测试代码中手动加载未命名模块的服务

如果必须将simplethingprovider留在类路径,可以在测试代码中显式从系统类加载器(未命名模块的类加载器)加载服务并合并:

// 加载模块上下文的服务
ServiceLoader<ThingProvider> moduleProviders = ServiceLoader.load(ThingProvider.class);
// 加载未命名模块的服务
ClassLoader unnamedClassLoader = ClassLoader.getSystemClassLoader();
ServiceLoader<ThingProvider> unnamedProviders = ServiceLoader.load(ThingProvider.class, unnamedClassLoader);

// 合并两个来源的服务
Set<ThingProvider> allProviders = new HashSet<>();
moduleProviders.forEach(allProviders::add);
unnamedProviders.forEach(allProviders::add);

// 使用allProviders进行测试断言

方案3:创建测试专属模块描述符

在src/test/java下创建独立的module-info.java,专门用于测试场景:

module thinguser.test {
  requires thinguser;
  requires simplethingprovider;
  requires org.junit.jupiter.api;
  
  // 导出测试类给JUnit平台
  exports com.example.thinguser.test to org.junit.platform.commons;
}

修改Surefire配置,让测试使用这个测试模块运行:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0</version>
  <configuration>
    <modulepath>${project.testModulePath}</modulepath>
    <mainClass>org.junit.platform.console.ConsoleLauncher</mainClass>
    <arguments>
      <argument>--scan-classpath</argument>
    </arguments>
  </configuration>
</plugin>

这种方式完全隔离了主模块和测试模块的依赖,是JPMS测试的标准实践之一。

内容的提问来源于stack exchange,提问作者Laird Nelson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 19:23:15