1. 从一个常见困惑说起:父模块和BOM到底哪里不一样
先问一个几乎所有Maven使用者都纠结过的问题:我在父POM的<dependencyManagement>里写了版本号,又在BOM(Bill of Materials)里写了版本号,效果看起来一样,那为什么还要搞出BOM这么个东西?
我自己的团队就踩过这个坑。早期所有多模块项目的依赖版本统一收口在父POM里,后来引入Spring Cloud、自研中间件、第三方SDK,父POM里堆积了上百个依赖声明,每次升级都战战兢兢——因为改任何一个依赖版本,全公司几十个下游服务全部受影响。更麻烦的是,跨团队协作时,别人根本不敢依赖我们的父POM,因为那意味着继承了我们所有的插件配置、仓库地址、甚至一些奇葩的<distributionManagement>。
后来才彻底搞明白:父模块继承(Parent POM Inheritance)和BOM严格来说是两种不同维度的东西。父模块是"血缘关系",BOM是"工具包"。这两者表面上看都能统一依赖版本,但解决的问题完全不同,适用场景也完全不同。
在展开讲之前,先给新手一个直觉类比:父模块就像家里的总闸,所有电器(子模块)都从这一路电走,总闸一拉全屋断电;BOM更像一张"标准件采购清单",每种螺丝用哪个型号写清楚,但清单本身不参与装配,哪个车间需要就按清单去领料。前者管"血缘",后者管"治标"。理解了这个区别,下面讲的内容你就能串起来了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. dependencyManagement的精确定位:父模块里写的只是"约束",不是"引入"
2.1 <dependencyManagement>的本质是约束而非依赖
很多人会把这样一段代码当作"引入依赖"来理解:
xml复制<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
</dependencyManagement>
注意:这段代码实际上根本没有引入任何依赖。它的含义是"如果本项目或者子模块想用jackson-databind,那么默认版本是2.15.2"。真正触发依赖引入的是你直接声明<dependency>的那一步。
这种约束机制带来的一个直观结果是:如果子模块自己显式声明了版本号,那么父POM里的版本约束会被覆盖。这个"就近声明优先"的规则,是Maven依赖管理的第一道法律,后面讲踩坑时还会用到。
2.2 父模块继承带来的"附加效应"
父模块继承在提供<dependencyManagement>的同时,还强制捆绑了几样东西:
<parent>标签本身决定了子模块的坐标继承关系- 插件管理(
<pluginManagement>)随父POM原样继承 - 全局属性(
<properties>)随父POM原样继承 - 仓库地址、镜像配置、分发管理也是继承的
所以,从设计意图看,父模块更偏向于"同一组织内、同一产品线的多模块项目"的构建公约。Maven官方文档里也明确说,父POM是实现"项目聚合与一致性"的手段,而不是跨项目共享依赖版本的唯一方式。
实际项目里我也见过很多团队把父POM当垃圾桶:什么配置都往里面塞,最后所有子模块都被迫背上巨重的"祖传配置"。更要命的是,如果某天父POM发布到内部仓库后改了一个插件版本,下游所有子模块即便代码一行没动,构建行为也可能发生变化。这种"隐性制动"是很多构建事故的源头,后面避坑部分我会单独说。
2.3 为什么"约束"和"继承"概念必须拆开
到了这里,一个新手最容易犯的错就是:把父POM里的<dependencyManagement>和"父模块继承"当成了同一件事。实际上<dependencyManagement>只是一个标签,它既可以出现在父POM里,也可以出现在任何POM里;而"
