一句话定位:
spring-boot-dependenciesBOM(Bill of Materials)是 Spring Boot 版本管理的中央锁,通过 Maven 的<dependencyManagement>机制统一锁定所有 Starter 及其传递依赖的版本号,让开发者无需手写version即可避免依赖冲突。
定义与作用
BOM(Bill of Materials,物料清单) 是 Maven 的一种依赖管理技术。spring-boot-dependencies 是 Spring Boot 官方提供的 BOM,它内部声明了数百个依赖的 <version>,覆盖 Spring Boot 生态中所有常用的库和框架。
当项目的 pom.xml 继承 spring-boot-starter-parent(或导入 spring-boot-dependencies)后,引入任何被 BOM 管理的依赖时,无需再写 <version>,Maven 会自动从 BOM 中获取对应的版本号。
它解决的痛点:
| 传统 Maven 项目 | 使用 BOM 后 |
|---|---|
手动指定 spring-webmvc、jackson、tomcat、hikaricp 等版本,版本冲突频繁 | 引入 spring-boot-starter-parent,所有版本自动锁定 |
升级 Spring 版本时,逐个修改 pom.xml 中的 10+ 个版本号 | 只改父 POM 的 <version>,全部依赖同步升级 |
| 新成员引入依赖时不知道该用哪个版本,经常引入不兼容版本 | 直接省略 <version>,BOM 保证兼容性 |
依赖树中出现两个不同版本的同一个库(如 jackson 2.12 和 2.13),导致 NoSuchMethodError | BOM 统一管理,传递依赖自动对齐到同一版本 |
适用位置与常用属性
BOM 管理不是注解,而是 Maven 构建机制。它通过以下两种方式融入项目:
方式一:继承父 POM(推荐,本教程采用)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
方式二:导入 BOM(公司有自定义父 POM 时使用)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.18</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
父 POM 提供的额外能力
| 能力 | 说明 | 是否可通过方式二获得 |
|---|---|---|
| 统一依赖版本管理 | spring-boot-dependencies BOM | ✅ 可以 |
spring-boot-maven-plugin 默认绑定 | 打包 Fat JAR 无需额外配置插件 | ❌ 需要手动配置 |
| 默认编码 UTF-8 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ❌ 需要手动配置 |
| 默认 Java 版本 | 与 Spring Boot 版本对应的默认 JDK | ❌ 需要手动配置 |
| 资源过滤 | application.yml 中的 ${...} 占位符替换 | ❌ 需要手动配置 |
| 依赖插件版本管理 | maven-compiler-plugin、maven-surefire-plugin 等版本锁定 | ❌ 需要手动配置 |
核心原理
BOM 版本锁定机制
机制解读:
- 父 POM 继承:
spring-boot-starter-parent是spring-boot-dependencies的父 POM,它将 BOM 引入到项目中。 <dependencyManagement>锁定:BOM 在<dependencyManagement>中声明了每个依赖的version和scope,这不会直接把依赖加入项目,而是定义了一个版本规则。<dependencies>生效:当小崔在<dependencies>中引入spring-boot-starter-web时,Maven 发现没有<version>,于是去<dependencyManagement>查找,找到 BOM 中锁定的2.7.18,自动填充。- 传递依赖对齐:Starter 的传递依赖(如
spring-webmvc依赖spring-core)也会从 BOM 中获取版本,确保整个依赖树版本一致。
版本管理层次图
优先级:开发者在 pom.xml 中显式写的 <version> 优先级最高,会覆盖 BOM 的锁定;BOM 的锁定优先级次之;如果两者都没有,Maven 会尝试从中央仓库获取最新版本(通常不可预期,生产环境严禁)。
完整示例
场景说明
飞翔科技的学生成绩管理系统使用 Spring Boot 2.7.18。小崔需要理解:为什么他的 pom.xml 可以不写 version?如果想升级版本,只需要改哪里?白歌让他做一个对比实验。
操作前:无父 POM 的 Maven 项目(手动管理版本)
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<!-- 没有 parent,没有 BOM -->
<groupId>com.feixiang</groupId>
<artifactId>student-management</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.27</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.5</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.75</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
</dependencies>
</project>
问题:
- 每个依赖都需要手动写
version。 spring-webmvc的传递依赖(如spring-core)的版本由 Maven 自行解析,可能与spring-webmvc不兼容。- 升级时逐个修改版本号,容易遗漏。
使用父 POM 继承的完整代码
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<!-- 继承 Spring Boot 父 POM,一键获得版本管理 + 插件绑定 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.feixiang</groupId>
<artifactId>student-management</artifactId>
<version>1.0.0</version>
<properties>
<!-- 自定义属性:覆盖 BOM 中某个依赖的版本 -->
<mysql.version>8.0.33</mysql.version>
</properties>
<dependencies>
<!-- 无需 version,由父 POM 的 BOM 锁定 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<!-- 可以省略,因为 BOM 已锁定 8.0.33;
也可以显式覆盖,但推荐在 <properties> 中统一覆盖 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 无需配置 version,父 POM 已绑定 spring-boot-maven-plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
版本覆盖机制:
在 spring-boot-starter-parent 中,BOM 的版本号被定义为 Maven 属性。例如:
<!-- 这是父 POM 内部的定义,开发者不可见,但可通过属性覆盖 -->
<properties>
<mysql.version>8.0.32</mysql.version>
<jackson.version>2.13.5</jackson.version>
</properties>
小崔可以在自己的 pom.xml 中通过同名属性覆盖:
<properties>
<!-- 将 MySQL 驱动从 BOM 默认的 8.0.32 升级到 8.0.33 -->
<mysql.version>8.0.33</mysql.version>
</properties>
注意:这是推荐的覆盖方式,而不是直接在
<dependency>中写<version>。通过属性覆盖,可以确保所有依赖该库的地方(包括传递依赖)都同步升级。
操作后运行结果及分析
执行 mvn dependency:tree -Dincludes=mysql 验证版本:
com.feixiang:student-management:jar:1.0.0
\- mysql:mysql-connector-java:jar:8.0.33:runtime
分析:
- 版本锁定生效:
mysql-connector-java的版本是8.0.33,既不是pom.xml中显式写的(因为没写),也不是 Maven 中央仓库的最新版,而是 BOM 锁定后、又被<mysql.version>属性覆盖后的结果。 - 传递依赖对齐:
spring-boot-starter-web内部依赖的jackson-databind、spring-webmvc、tomcat-embed-core等版本全部与2.7.18的 BOM 一致,无需手动干预。 - 插件绑定:
spring-boot-maven-plugin无需写<version>,父 POM 已绑定。执行mvn package会自动打包为 Fat JAR(包含BOOT-INF/classes和BOOT-INF/lib)。 - 升级路径:当 Spring Boot 发布
2.7.19时,小崔只需修改父 POM 的<version>,所有依赖和插件自动同步升级。
易错场景与面试考点
易错场景:公司有自定义父 POM,直接继承 spring-boot-starter-parent 导致冲突
飞翔科技内部有一个统一的父 POM com.feixiang:feixiang-parent:3.0.0,规定了公司级的插件配置、仓库地址和编码规范。小崔直接在他的项目中继承了 spring-boot-starter-parent,导致公司规范失效。
错误示范:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
后果:
- 丢失了公司父 POM 中定义的
<distributionManagement>(私服上传地址)。 - 丢失了公司统一的
maven-checkstyle-plugin配置。 - 公司架构组要求所有项目必须继承
feixiang-parent,小崔的项目无法通过 CI 流水线检查。
正确做法:使用 BOM 导入(方式二),而不是继承父 POM(方式一)。
<parent>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-parent</artifactId>
<version>3.0.0</version>
</parent>
<dependencyManagement>
<dependencies>
<!-- 导入 Spring Boot BOM,获得版本锁定 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.18</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 手动绑定 spring-boot-maven-plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.18</version>
</plugin>
</plugins>
</build>
关键区别:
importscope 的 BOM 只导入<dependencyManagement>中的版本定义,不会继承父 POM 的<pluginManagement>和属性。因此spring-boot-maven-plugin需要手动写version(或在公司父 POM 中绑定)。
面试考点
Q:BOM 是什么?spring-boot-starter-parent 和 spring-boot-dependencies 有什么区别?
BOM(Bill of Materials)是 Maven 的依赖管理技术,通过
<dependencyManagement>统一锁定依赖版本。spring-boot-dependencies是纯 BOM,只包含版本锁定;spring-boot-starter-parent是完整父 POM,继承它不仅能获得 BOM 的版本锁定,还能获得默认的插件绑定(spring-boot-maven-plugin)、编码设置(UTF-8)、资源过滤等构建能力。如果公司已有自定义父 POM,应使用importscope 导入spring-boot-dependencies,而非继承spring-boot-starter-parent。
Q:如何覆盖 BOM 中某个依赖的版本?
推荐方式:在
pom.xml的<properties>中定义与 BOM 中同名的属性。例如 BOM 内部定义了<mysql.version>8.0.32</mysql.version>,在自己的pom.xml中写<properties><mysql.version>8.0.33</mysql.version></properties>即可覆盖。这种方式确保所有依赖该库的地方(包括传递依赖)都同步升级。不推荐直接在<dependency>中写<version>,因为它只影响当前依赖声明,不保证传递依赖对齐。
Q:spring-boot-maven-plugin 的作用是什么?如果不继承父 POM,如何配置它?
spring-boot-maven-plugin负责将 Spring Boot 应用打包为可执行的 Fat JAR。它将应用类和所有依赖 JAR 打包到一个 JAR 中,并通过自定义的JarLauncher类加载器处理BOOT-INF/classes和BOOT-INF/lib目录。如果不继承父 POM,需要在<build><plugins>中手动声明该插件并指定version(与 Spring Boot 版本一致)。
Q:Maven 的 <dependencyManagement> 和 <dependencies> 有什么区别?
<dependencyManagement>只定义版本约束,不会将依赖实际引入项目。它声明了"如果本项目或传递依赖中出现了这个依赖,就使用这个版本"。<dependencies>才将依赖实际加入项目的 classpath。BOM 的精髓就在于:在<dependencyManagement>中统一锁定版本,在<dependencies>中按需引入依赖,两者分离,清晰可控。