BOM
本章是"继承与聚合"章节的高级主题。BOM(Bill of Materials,物料清单)是 Maven 依赖管理的进阶武器,它解决了大型第三方生态(如 Spring Boot、Spring Cloud)的版本协调难题。理解 BOM 与普通 parent 的区别,是管理现代企业级项目依赖的必备技能。
核心机制
BOM 的核心特征可以概括为两点:
- only
<dependencyManagement>:BOM 的核心职责只有一个——管理版本,不携带插件、不携带构建配置 - centralize dependency versions:BOM 是"版本清单",不是"配置模板"
BOM 的本质
BOM 本质上是一个特殊的 POM 文件,它的特点是:
packaging为pom- 只包含
<dependencyManagement>,不包含<plugins>、<build>等其他配置 - 通过
<scope>import</scope>被引入到项目的<dependencyManagement>中
import scope 的语法
BOM 不能通过 <parent> 继承,只能通过 import 范围导入:
<dependencyManagement>
<dependencies>
<!-- 导入 Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
关键规则:
importscope 只能在<dependencyManagement>中使用- 必须同时声明
<type>pom</type>,因为导入的是一个 POM 文件,不是 JAR - 导入后,BOM 中管理的所有依赖版本自动在本项目中生效
BOM 与普通 parent 的区别
| 维度 | 普通 Parent POM | BOM |
|---|---|---|
| 引入方式 | <parent> 继承 | <scope>import</scope> 导入 |
| 内容范围 | 可包含插件、属性、构建配置等 | 只包含 <dependencyManagement> |
| 数量限制 | 一个子 POM 只能有一个 parent | 一个 POM 可以导入多个 BOM |
| 典型用途 | 公司内部统一构建标准 | 引入第三方生态的版本清单 |
| 版本控制 | 子 POM 继承 parent 的版本 | 子 POM 引用 BOM 中管理的版本 |
关键结论:Parent 是"公司制度",BOM 是"供应商产品目录"。一个项目只能遵循一套公司制度(一个 parent),但可以参考多份供应商目录(多个 BOM)。
Spring Boot Dependencies 示例
spring-boot-dependencies 是最著名的 BOM 之一。它内部管理了 200+ 个 Spring 生态相关依赖的版本:
<!-- spring-boot-dependencies-3.2.0.pom 内部片段 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
</dependency>
<!-- 还有 200+ 个依赖... -->
</dependencies>
</dependencyManagement>
当你的项目导入这个 BOM 后,引用其中任何依赖都无需写版本号:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本由 spring-boot-dependencies BOM 管理 -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<!-- 版本同样由 BOM 管理 -->
</dependency>
</dependencies>
生活类比:装修公司的材料清单
想象你要装修一套房子(构建一个 Spring Boot 项目):
- 普通 Parent POM = 装修公司的施工规范。规定了水电怎么走、墙面怎么刷、工期怎么排(插件配置、构建流程)。你只能选一家装修公司(一个 parent)。
- BOM = 建材供应商的产品目录。比如"宜家 2024 全屋套餐清单",里面列出了沙发、餐桌、灯具的型号和价格(依赖版本)。你可以同时参考宜家的清单和本地家具城的清单(多个 BOM),从中挑选合适的家具。
- import scope = 把供应商的产品目录放进你的购物车。你不需要买下整个宜家商场(继承整个 POM),只需要把目录复印一份,按上面的型号选购(引用版本)。
如果没有 BOM,你要逐个去查每个 Spring 依赖的兼容版本——就像没有产品目录时,你得亲自去工厂确认"这款沙发和这款茶几是否配套"。
图示
上图展示了 BOM 导入机制的核心逻辑:项目 POM 通过 <parent> 继承公司内部的构建规范(父 POM),同时通过 <scope>import</scope> 引入多个第三方 BOM 的版本清单。最终依赖列表中的版本由 BOM 统一提供,无需在每个依赖上硬编码。
完整示例
场景
飞翔科技的 employee-system 要引入 Spring Boot 3.2.0 和 Spring Cloud 2023.0.0。架构师白歌发现:Spring Boot 和 Spring Cloud 有严格的版本对应关系,手动管理容易出错。他决定用 BOM 来统一控制。
操作前:手动管理版本(易错)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.1.0</version> <!-- 需要手动确认与 Spring Boot 3.2.0 兼容 -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version> <!-- 需要手动确认与 Spring Boot 3.2.0 兼容 -->
</dependency>
</dependencies>
问题:
- 小崔升级 Spring Boot 到 3.3.0 时,不知道 Spring Cloud Gateway 应该升级到哪个版本
- 黄俪引入
spring-boot-starter-data-redis时,忘了写版本号,Maven 报错 - 李眉排查生产环境问题时,发现
jackson-databind版本与 Spring Boot 内置版本冲突
操作步骤:导入 BOM
第一步:在 feixiang-parent 中导入 BOM(推荐在父 POM 统一导入)
<dependencyManagement>
<dependencies>
<!-- Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
第二步:子项目按需引用,不写版本号
employee-system/pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
操作结果:BOM 与普通 parent 的区别
使用 BOM 后的版本控制:
| 依赖 | 版本来源 | 实际版本 |
|---|---|---|
spring-boot-starter-web | spring-boot-dependencies BOM | 3.2.0 |
spring-boot-starter-data-redis | spring-boot-dependencies BOM | 3.2.0 |
spring-cloud-starter-gateway | spring-cloud-dependencies BOM | 4.1.0 |
jackson-databind(传递依赖) | spring-boot-dependencies BOM | 2.15.3 |
变化分析:
- 小崔引入新的 Spring Boot Starter 时,无需查文档找版本号,直接写
groupId和artifactId即可 - 白歌升级 Spring Boot 时,只需修改
spring-boot-dependencies的导入版本,所有相关依赖自动对齐 - 李眉检查依赖树时,发现所有 Spring 生态的版本来自同一个 BOM,没有冲突
BOM 与普通 parent 的职责分离:
| 职责 | 由谁承担 | 具体内容 |
|---|---|---|
| 公司构建规范 | feixiang-parent(普通 parent) | Java 17、compiler 插件 3.11.0、UTF-8 |
| 第三方生态版本 | spring-boot-dependencies(BOM) | Spring Boot 3.2.0、Jackson 2.15.3 |
| 微服务生态版本 | spring-cloud-dependencies(BOM) | Gateway 4.1.0、OpenFeign 4.1.0 |
易错点与常见问题
误区一:import scope 用在 <dependencies> 中
错误配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
后果:Maven 报错 import scope 只能用于 dependencyManagement 中。因为 import 的作用是"导入版本清单",而不是"引入一个依赖"。BOM 本身不是项目的运行时依赖,构建产物中不应该包含 BOM。
纠正:import 必须写在 <dependencyManagement> 下,且通常放在父 POM 中统一管理。
误区二:把 BOM 当作 parent 继承
错误配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
</parent>
后果:虽然技术上可行(因为 spring-boot-dependencies 的 packaging 确实是 pom),但这样做会丢失你自己的 parent 继承链。你的项目无法继承公司内部的 feixiang-parent,导致构建规范(如 compiler 插件配置、仓库地址)全部丢失。
纠正:BOM 用 import,parent 用 <parent>。两者各司其职:
<parent>留给公司/团队内部的父 POMimport留给第三方 BOM
误区三:导入了 BOM,但某个依赖仍然写了版本号
配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.0</version> <!-- 覆盖了 BOM 的版本 -->
</dependency>
</dependencies>
后果:Maven 优先使用依赖上显式声明的版本(3.1.0),而不是 BOM 管理的版本(3.2.0)。这会导致版本不一致,破坏 BOM 的统一管理价值。
纠正:导入 BOM 后,被管理的依赖不要再写 <version>。如果确实需要覆盖某个依赖的版本,应在 <dependencyManagement> 中显式声明,而不是在 <dependencies> 中硬编码。
小结
BOM(Bill of Materials)是 Maven 管理复杂第三方生态版本的进阶工具。它通过 <scope>import</scope> 将版本清单导入项目的 <dependencyManagement>,实现了"一份清单、多处引用"的版本协调。BOM 与普通 parent 的核心区别在于:BOM 只管理依赖版本,不管理构建配置;一个项目可以导入多个 BOM,但只能继承一个 parent。在 Spring Boot、Spring Cloud 等现代 Java 生态中,BOM 已成为事实上的标准版本管理方式。
本章与全局的关系:本章结束了"继承与聚合"章节,也完成了 Maven 入门教程的核心内容。通过 parent 继承、聚合、properties 和 BOM 四个机制,你已经掌握了从单模块到多模块、从简单项目到企业级平台的 Maven 项目管理能力。