Maven 多模块项目中,为什么子模块独立构建时依赖版本错误?
结论
优先检查两次构建中同一个子模块实际生效的 parent POM、BOM 和版本覆盖项是否一致。子模块独立构建时仍会继承父 POM 的 dependencyManagement,切换目录不会自动使 BOM 管理失效。
没有完整的 pom.xml,暂时无法确定唯一原因。可以先检查独立构建是否读取了仓库中的旧 parent 或自定义 BOM,再比较两次构建启用的 profile、属性和依赖版本声明。
示例中的 3.5.10 是 Spring Boot 版本,spring-context 对应的是 6.2.15。不要将所有 Spring 依赖的版本都设为 3.5.10。
先比较同一个子模块的 effective POM
根目录执行 dependency:tree 可能输出多个模块的依赖树。先确认两段日志都属于 my-project-unique,避免拿父项目或其他模块的结果作比较。
在父项目根目录执行:
mvn -pl my-project-unique help:effective-pom -Dverbose -Doutput=root-effective.xml
mvn -pl my-project-unique dependency:tree "-Dincludes=org.springframework.boot:*,org.springframework:*"
然后进入子模块目录执行:
mvn help:effective-pom -Dverbose -Doutput=child-effective.xml
mvn dependency:tree "-Dincludes=org.springframework.boot:*,org.springframework:*"
找到生成的两个 XML,比较其中属于 my-project-unique 的内容,重点查看:
dependencyManagement中spring-boot-starter和spring-boot的最终版本。dependencies中是否直接声明了2.3.9.RELEASE。- 版本属性是否被 profile 或命令行参数覆盖。
- 相关条目旁的来源注释是否指向不同 POM。
help:effective-pom 会包含激活 profile 的效果。-Dverbose 可添加配置来源注释,要求 Maven Help Plugin 3.2.0 或更高版本,不能仅凭 Maven 3.8.x 判断插件是否支持该选项。Maven Help Plugin 文档
如果 effective POM 已经出现旧版本,先追踪该配置从哪里进入模型;如果管理版本正确,但实际解析结果仍是旧版本,继续检查直接依赖的显式版本和依赖树中的冲突信息。
检查 parent 与 BOM 的解析来源
parent 的 relativePath 只负责查找父 POM
对于题目中的目录结构,子模块可以通过下面的配置指向父 POM,原有父项目坐标保持不变:
<relativePath>../pom.xml</relativePath>
这行配置应位于 <parent> 内。Maven 默认也会查找 ../pom.xml,但只有坐标与 <parent> 声明匹配时才会使用它。对于 Maven 3,写成 <relativePath/> 会跳过该文件系统路径,转而从仓库解析 parent。Maven POM Reference
即使子模块继承了预期的 parent 坐标,也要确认读取的是不是当前工作区中的父 POM。
找到父 POM,并不代表找到了旁边的 BOM 源码
my-project-dependencies 是通过 dependencyManagement 导入的 POM artifact。parent.relativePath 不会将这个导入指向旁边的源码目录。
检查父 POM 中导入 BOM 的 groupId、artifactId 和 version,再核对独立构建实际解析到的 BOM。即使坐标相同,本地仓库中也可能保留着以前安装的内容。
如果 BOM 完全无法解析,通常会报模型解析错误。现在能构建却出现旧版本,应追踪旧版本配置的来源,不能直接认定 Maven 找不到新 BOM 时会自动回退到旧版本。
如果确认仓库中的 POM 过旧,先执行 install
题目使用的是 mvn clean package。package 不会执行后面的 install 阶段,因此不能据此认为当前 parent 和自定义 BOM 已经更新到本地仓库。执行 install 会将项目产物安装到本地仓库,供其他构建使用。Maven 构建生命周期说明
如果根目录构建确实可用,可以先在根目录执行:
mvn clean install
成功后,再进入 my-project-unique 执行:
mvn clean package
mvn dependency:tree "-Dincludes=org.springframework.boot:*,org.springframework:*"
若版本恢复一致,说明更新仓库中的项目产物解决了当前差异,但仍应通过 effective POM 的来源确认究竟是哪份 POM 发生了变化。其他开发机或 CI 若也需要独立构建,应能从共享仓库获取对应版本的 parent 和 BOM。
如果安装后仍然使用旧版本,继续检查两次命令的 -P、-D、-s 参数、激活的 profile、本地仓库位置,以及显式版本覆盖。不要把删除整个本地仓库作为第一步。
检查父项目导入自身 BOM 子模块的结构
题目中“父 POM 导入 BOM 子模块”的关系需要核对。Maven 官方文档提醒,应避免导入当前 POM 的子模块 POM;如果被导入的 BOM 又继承这个导入它的父 POM,还会形成无法解析的循环关系。Maven BOM 使用说明
原始材料没有给出 my-project-dependencies 的 <parent>,所以不能断言已经存在循环。若核对后确实存在,应调整结构,例如:
- 让 BOM 独立发布,且不继承导入它的业务父 POM。
- 将聚合 POM 与负责业务继承的 parent POM 分开。
- 如果不需要自定义 BOM,直接在公共父 POM 中导入
spring-boot-dependencies。
最后一种方式的配置片段如下,适用于确认可以移除这层自定义 BOM 的情况:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.5.10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子模块随后可以不写受管理依赖的版本:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
若自定义 BOM 还管理了其他依赖,调整时需要保留那些管理项。
日常只构建一个模块时,可以保留根目录入口
如果目的是减少构建范围,可以在根目录执行:
mvn -pl my-project-unique -am clean package
-pl 选择目标模块,-am 同时构建 reactor 中它需要的项目。不过,dependencyManagement 中的声明不会建立 reactor 排序关系,不能依赖 -am 自动解决 BOM 的首次解析或循环引用问题。Maven 多模块构建指南
修复后,用同一子模块的 effective POM 和依赖树复核两种构建入口,确认 Spring Boot 均解析为预期的 3.5.10,并继续由 BOM 管理配套 Spring 依赖。
备注:内容仅供参考。
