news 2026/4/24 17:35:18

Maven中BOM(Bill of Materials)的使用详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Maven中BOM(Bill of Materials)的使用详解


目录

一、什么是BOM?

二、为什么需要BOM?

2.1 没有BOM时的痛点

2.2 使用BOM后的效果

三、BOM的两种使用方式

3.1 方式一:dependencyManagement + import(推荐)

3.2 方式二:通过 parent 继承

3.3 两种方式对比

四、如何自定义BOM?

4.1 创建BOM项目

4.2 发布BOM到私服

4.3 在业务项目中使用

五、多个BOM共存与版本优先级

5.1 版本优先级规则

5.2 版本覆盖示例

六、常见的开源BOM盘点

七、最佳实践总结

✅ 推荐做法

❌ 避免的做法

八、排查依赖版本问题的实用命令

九、总结


一、什么是BOM?

BOM,全称Bill of Materials(物料清单),这个词最早其实来源于制造业,指的是生产一个产品所需的所有原材料清单。Maven借用了这个概念,用来表示一种特殊的POM文件,它的核心作用就是统一管理一组相关依赖的版本号

打个生活中的比方:你去餐厅吃饭,可以选择单点,也可以选择套餐。单点的话,每道菜你都得自己选、自己搭配,万一搭配不好还可能"串味"。而套餐呢,厨师已经帮你把菜品和分量都搭配好了,你只管下单就行。BOM就相当于这个"套餐菜单"——它帮你把一组依赖的版本都预先定义好,你在项目里引用的时候,只需要写依赖的坐标,版本号都不用操心。

那它到底能帮我们解决什么问题呢?简单来说有三点:

  • 统一版本管理:所有依赖的版本号集中在一个地方维护,一改全改
  • 简化依赖声明:引用依赖时不用再写<version>标签,配置更清爽
  • 保持多模块一致性:在大型多模块项目中,确保所有子模块用的都是同一套版本

二、为什么需要BOM?

可能有同学会说:"我直接在每个依赖上写版本号不也挺好的吗?为什么非要搞个BOM呢?"

别急,我们来看一个真实场景你就明白了。

2.1 没有BOM时的痛点

假设你正在开发一个Spring Boot项目,用到了Web、JPA、Security、Test这几个Starter。如果不用BOM,你的pom.xml大概长这样:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.2.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.2.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.2.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>3.2.5</version> </dependency> </dependencies>

乍一看好像也没什么问题,但仔细想想:

问题

描述

版本散落各处

同样是3.2.5,你得在四个地方重复写,这还只是四个依赖,真实项目可能有几十个

升级成本高

哪天要升级到3.3.0,你得一个一个找、一个一个改,漏掉一个就可能出问题

版本不一致风险

团队里多人协作,张三改了这个模块的版本,李四改了那个模块的版本,最后合到一起就炸了

传递依赖冲突

不同版本的Starter底层可能依赖了不同版本的Spring Framework,一旦混用就会出现各种诡异的NoSuchMethodError

这些问题在小项目里可能还不明显,但一旦项目规模上来了,依赖管理就会变成一场噩梦。

2.2 使用BOM后的效果

现在我们用BOM来改造一下:

<!-- 在dependencyManagement中引入BOM --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- 使用依赖时无需指定版本,BOM已经帮你定好了 --> <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-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies>

看到区别了吗?版本号只出现了一次,就在引入BOM的那个地方。下面所有的依赖声明都干干净净,只有groupIdartifactId。将来要升级版本?改一个地方就够了,所有依赖自动跟着变。

这就是BOM的魅力所在——把"版本管理"这件事从分散变成集中,从手动变成自动。


三、BOM的两种使用方式

在Maven中,使用BOM主要有两种方式。它们各有特点,适用于不同的场景。

3.1 方式一:dependencyManagement+import(推荐)

这是最常用、也是最灵活的方式。通过在<dependencyManagement>中以<scope>import</scope>的形式将BOM导入到当前项目中。

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

这里有两个特殊的标签需要注意:

  • <type>pom</type>—— 告诉Maven这不是一个普通的jar依赖,而是一个POM类型的文件
  • <scope>import</scope>—— 这是关键!它的意思是"把这个POM里定义的dependencyManagement内容导入到我的项目中来"

你可以把它理解为"复制粘贴":Maven会把BOM中dependencyManagement里定义的所有依赖版本,原封不动地"粘贴"到你当前项目的dependencyManagement中。这样一来,你在<dependencies>中声明这些依赖时,就不需要再写版本号了。

这种方式最大的好处是:你可以同时导入多个BOM。比如你的项目既用了Spring Boot,又用了Spring Cloud,还有公司内部的组件库,三个BOM可以并存,互不干扰。

3.2 方式二:通过parent继承

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent>

这种方式相信大家都很熟悉了,几乎每个Spring Boot项目都是这么开头的。它的本质是通过Maven的父POM继承机制来获取版本管理能力。

和import方式不同的是,parent继承不仅会继承版本管理,还会把父POM中定义的插件配置、编译参数、资源过滤规则等一并继承过来。这就好比import方式只是借了一份菜单,而parent方式是直接拜师学艺,师傅的全套手艺都传给你了。

但它有一个致命的限制:Maven的单继承机制决定了一个项目只能有一个parent。如果你的公司已经有了自己的父POM,那就没办法再继承spring-boot-starter-parent了。这时候就只能用方式一的import了。

3.3 两种方式对比

特性

import方式

parent继承方式

数量限制

✅ 可导入多个BOM

❌ 只能有一个parent

继承内容

仅版本管理

版本管理 + 插件 + 属性等

灵活性

高,可自由组合

较低,受单继承限制

适用场景

多框架混合使用、企业级项目

单一框架的标准项目

版本覆盖

需在import之前声明

可通过properties覆盖

我的建议是:在大多数企业项目中,优先使用import方式。它更灵活,不占用宝贵的parent位置,而且可以同时引入多个BOM。只有在纯粹的Spring Boot单体项目中,使用parent继承才更方便一些。


四、如何自定义BOM?

了解了BOM的用法之后,你可能会想:那些开源框架有现成的BOM可以用,但我们公司内部的组件怎么办?答案是——自己造一个!

在企业开发中,创建自己的BOM来统一管理内部组件版本,是一种非常常见也非常推荐的做法。下面我们一步步来实现。

4.1 创建BOM项目

首先,创建一个全新的Maven项目,这个项目不需要写任何Java代码,它的唯一职责就是管理版本号。pom.xml内容如下:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>mycompany-bom</artifactId> <version>1.0.0</version> <!-- 注意:packaging必须是pom,这是BOM的标志 --> <packaging>pom</packaging> <name>MyCompany BOM</name> <description>统一管理公司内部组件版本</description> <!-- 通过properties集中定义版本号,方便维护 --> <properties> <mycompany.common.version>2.1.0</mycompany.common.version> <mycompany.security.version>1.5.0</mycompany.security.version> <mycompany.logging.version>1.3.0</mycompany.logging.version> <hutool.version>5.8.25</hutool.version> <guava.version>33.0.0-jre</guava.version> </properties> <dependencyManagement> <dependencies> <!-- ========== 公司内部组件 ========== --> <dependency> <groupId>com.mycompany</groupId> <artifactId>mycompany-common-core</artifactId> <version>${mycompany.common.version}</version> </dependency> <dependency> <groupId>com.mycompany</groupId> <artifactId>mycompany-common-redis</artifactId> <version>${mycompany.common.version}</version> </dependency> <dependency> <groupId>com.mycompany</groupId> <artifactId>mycompany-security-starter</artifactId> <version>${mycompany.security.version}</version> </dependency> <dependency> <groupId>com.mycompany</groupId> <artifactId>mycompany-logging-starter</artifactId> <version>${mycompany.logging.version}</version> </dependency> <!-- ========== 常用第三方组件 ========== --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>${guava.version}</version> </dependency> </dependencies> </dependencyManagement> </project>

这里有几个要点值得说明:

第一,<packaging>pom</packaging>是必须的。它告诉Maven这个项目不会产出jar包,它就是一个纯粹的POM项目,专门用来做依赖管理。

第二,版本号建议用<properties>来管理。你可能注意到了,我没有直接在<version>标签里写死版本号,而是用属性变量来引用。这样做的好处是:所有版本号都集中在<properties>里,一目了然,改起来也方便。而且,使用方还可以通过覆盖属性的方式来自定义某个特定组件的版本。

第三,建议把内部组件和第三方组件分开管理。用注释分隔开,结构清晰,后续维护的时候一眼就能找到要改的地方。

4.2 发布BOM到私服

BOM写好之后,需要发布到公司的Maven私服(比如Nexus或Artifactory),这样其他项目才能引用到它:

mvn clean deploy

这一步和发布普通的jar包没什么区别,Maven会把这个POM文件上传到私服的对应仓库中。

4.3 在业务项目中使用

发布完成后,业务项目就可以通过import的方式引入这个BOM了:

<dependencyManagement> <dependencies> <!-- 引入公司BOM --> <dependency> <groupId>com.mycompany</groupId> <artifactId>mycompany-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 直接使用,无需版本号,是不是很清爽? --> <dependency> <groupId>com.mycompany</groupId> <artifactId>mycompany-common-core</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> </dependencies>

从此以后,公司所有项目只要引入这一个BOM,内部组件和常用第三方库的版本就自动统一了。新人入职不用再纠结"这个库该用哪个版本",老项目升级也只需要改BOM的版本号就行。


五、多个BOM共存与版本优先级

在实际的企业项目中,只用一个BOM几乎是不可能的。你的项目可能同时需要Spring Boot的BOM、Spring Cloud的BOM、还有公司内部的BOM。这时候就涉及到一个关键问题:如果多个BOM中定义了同一个依赖的不同版本,Maven到底听谁的?

先来看一个典型的多BOM配置:

<dependencyManagement> <dependencies> <!-- BOM 1: Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.5</version> <type>pom</type> <scope>import</scope> </dependency> <!-- BOM 2: Spring Cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.1</version> <type>pom</type> <scope>import</scope> </dependency> <!-- BOM 3: 公司内部BOM --> <dependency> <groupId>com.mycompany</groupId> <artifactId>mycompany-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

5.1 版本优先级规则

Maven对于版本冲突的解决遵循一个很明确的优先级规则,从高到低依次是:

1️⃣ 当前项目 <dependencyManagement> 中直接声明的版本(非import的) 2️⃣ 先声明的BOM中的版本(声明顺序靠前的优先) 3️⃣ 后声明的BOM中的版本

简单来说就是:"直接声明 > 先导入的BOM > 后导入的BOM"

举个例子:假设Spring Boot的BOM里定义了jackson-databind的版本是2.15.4,而你的公司BOM里定义的是2.16.1。由于Spring Boot的BOM写在前面,Maven最终会使用2.15.4

这个规则其实很好理解——Maven认为你写在前面的BOM优先级更高,因为那通常是你更"信任"的版本来源。

5.2 版本覆盖示例

那如果我就是想用2.16.1版本的jackson-databind呢?有两种办法:

办法一:调整BOM的声明顺序,把包含你想要版本的BOM放到前面。但这种方式可能会影响其他依赖的版本解析,不太推荐。

办法二(推荐):在import之前显式声明该依赖的版本。

<dependencyManagement> <dependencies> <!-- ✅ 先声明要覆盖的版本,这个优先级最高 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> </dependency> <!-- 再导入BOM,BOM中的jackson-databind版本会被上面的覆盖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

⚠️这里有一个很多人踩过的坑:覆盖版本的声明必须写在BOM的import语句之前,写在后面是无效的。因为Maven在解析dependencyManagement时,对于同一个依赖,只认第一次出现的版本定义。


六、常见的开源BOM盘点

了解了BOM的原理和用法之后,我们来看看Java生态中有哪些常用的BOM。这些BOM都是由各大开源社区维护的,经过了大量项目的验证,可以放心使用:

BOM

artifactId

说明

Spring Boot

spring-boot-dependencies

Spring Boot全家桶版本管理,覆盖了几百个常用依赖

Spring Cloud

spring-cloud-dependencies

微服务组件版本管理,和Spring Boot版本有对应关系

Spring Cloud Alibaba

spring-cloud-alibaba-dependencies

阿里巴巴微服务组件(Nacos、Sentinel等)

JUnit 5

junit-bom

JUnit 5测试框架全家桶

Jackson

jackson-bom

JSON处理库,确保core/databind/annotations版本一致

Netty

netty-bom

网络通信框架,子模块众多,BOM管理非常必要

Log4j2

log4j-bom

日志框架版本统一

AWS SDK

bom(software.amazon.awssdk)

AWS开发工具包,服务模块极多,必须用BOM管理

其中,Spring Boot的BOM可以说是最"大而全"的,它不仅管理了Spring自家的组件版本,还帮你管理了大量常用第三方库的版本,比如Jackson、Logback、HikariCP、Tomcat等等。这也是为什么用了Spring Boot之后,很多依赖都不需要写版本号的原因。


七、最佳实践总结

经过这么多年的项目实践,我总结了一些关于BOM使用的经验,分享给大家。

✅ 推荐做法

1. 企业项目必建BOM。不管公司大小,只要有超过两个项目共享组件,就应该建立统一的BOM。这是依赖治理的第一步,也是最重要的一步。

2. 优先使用import方式而非parent继承。import方式更灵活,不占用parent位置,支持多BOM共存。除非你的项目确实需要继承父POM的插件配置,否则import是更好的选择。

3. 版本号一定要用properties管理。把所有版本号集中定义在<properties>中,不要直接硬编码在<version>标签里。这样不仅便于查找和修改,还方便下游项目通过覆盖属性来自定义版本。

4. BOM应该是一个独立的项目。不要把BOM的POM和业务代码混在一起。它应该有自己独立的Git仓库、独立的版本号、独立的发布流程。

5. BOM版本号要遵循语义化版本规范(SemVer)。主版本号表示不兼容的变更,次版本号表示新增功能,修订号表示Bug修复。这样使用方看到版本号就能大致判断升级的风险。

6. 维护一份版本兼容矩阵文档。记录BOM各版本与Spring Boot、JDK等基础设施的兼容关系,方便团队成员查阅。

❌ 避免的做法

1. 不要在BOM中使用<dependencies>BOM里只应该有<dependencyManagement>。如果你在BOM里写了<dependencies>,那所有引入这个BOM的项目都会被强制引入这些依赖,这不是BOM该干的事。

2. 不要在BOM中包含业务逻辑代码。BOM就是一个纯粹的版本管理工具,不要给它加戏。

3. 不要频繁发布SNAPSHOT版本到生产环境。BOM的稳定性直接影响所有下游项目,发布前一定要充分测试。

4. 不要在子模块中随意覆盖BOM定义的版本。除非你有充分的理由(比如某个组件确实需要特定版本来修复一个严重Bug),否则应该尊重BOM的版本定义。随意覆盖版本会破坏BOM统一管理的初衷。


八、排查依赖版本问题的实用命令

即使用了BOM,在实际开发中还是难免会遇到依赖版本相关的问题。这时候以下几个Maven命令就是你的好帮手:

# 查看完整的依赖树——这是排查依赖问题的第一步 # 它会展示项目所有依赖的层级关系,包括传递依赖 mvn dependency:tree # 查看有效POM——展开所有继承和import后的最终POM # 当你不确定某个版本到底从哪来的时候,看这个最直观 mvn help:effective-pom # 分析依赖问题——找出未声明但使用了的依赖,以及声明了但没使用的依赖 mvn dependency:analyze # 查看某个具体依赖的版本来源——精确定位某个依赖的版本是怎么解析出来的 # 比如你想知道jackson-databind到底用的哪个版本、从哪个BOM来的 mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind

其中mvn dependency:tree是我用得最多的命令,几乎每次遇到NoSuchMethodErrorClassNotFoundException这类运行时错误,第一反应就是跑一下这个命令看看依赖树。很多时候问题就出在某个传递依赖的版本不对。


九、总结

最后,我们用一张表来回顾一下本文的核心内容:

维度

说明

是什么

BOM是一种特殊的POM,专门用于集中定义一组依赖的版本号

为什么用

统一版本、避免冲突、简化配置、降低维护成本

怎么用

推荐dependencyManagement+import,也可以用parent继承

怎么建

创建packaging=pom的独立项目,在dependencyManagement中定义版本

优先级

直接声明 > 先import的BOM > 后import的BOM

一句话总结:BOM是Maven依赖管理的"中央版本控制台",用好它能让你的项目依赖管理从混乱走向有序。无论是使用开源框架提供的BOM,还是在企业内部自建BOM,它都是Java项目工程化实践中不可或缺的一环。

如果你的团队还没有用上BOM,不妨从今天开始,先把项目中最常用的那些依赖整理成一个BOM。相信我,当你体验过"改一个版本号,全公司项目自动统一"的快感之后,你就再也回不去了。😄


📌如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎评论区交流讨论~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/24 17:35:18

QML Image 图像组件示例合集

目录1. 引言2. 演示效果3. 代码说明3.1 图片缩放3.2 图片平移3.3 图片旋转3.4 网络图片加载3.5 图片填充模式3.6 图片镜像3.7 图片 Mipmap3.8 动态图播放4. 技术要点4.1 Image 属性速查4.2 变换类型对比4.3 Image vs AnimatedImage5. 工程下载1. 引言 QML 的 Image 组件提供了…

作者头像 李华
网站建设 2026/4/24 17:33:25

VMware ESXi 防火墙端口放行全教程:命令行规则集配置与实操指南

本文针对 VMware ESXi 虚拟化环境下的防火墙端口放行需求&#xff0c;详解 ESXi 基于规则集的防火墙管理核心逻辑&#xff0c;手把手教你通过 esxcli 命令行完成端口规则的启用、状态校验、自定义配置与持久化设置。全文兼顾新手入门与运维实操&#xff0c;梳理常见配置误区&am…

作者头像 李华
网站建设 2026/4/24 17:32:53

国产AI模型平台崛起:模力方舟如何破解HuggingFace本土化难题

在全球AI开发领域&#xff0c;HuggingFace长期占据着模型社区的主导地位&#xff0c;但随着AI技术从实验室走向产业落地&#xff0c;中国开发者正面临着一个关键抉择&#xff1a;是继续依赖国际平台&#xff0c;还是拥抱更懂本土需求的国产解决方案&#xff1f;在这场全球资源与…

作者头像 李华
网站建设 2026/4/24 17:31:44

FRCRN开源大模型教程:噪声标签体系构建与半监督降噪新思路

FRCRN开源大模型教程&#xff1a;噪声标签体系构建与半监督降噪新思路 语音降噪&#xff0c;听起来是个技术活儿&#xff0c;但你可能每天都在和它打交道。打电话时对方听不清&#xff0c;录播客背景音太吵&#xff0c;或者想用语音转文字却总被杂音干扰——这些问题的核心&am…

作者头像 李华
网站建设 2026/4/24 17:31:23

终极歌词解决方案:OpenLyrics让foobar2000歌词显示更智能更美观

终极歌词解决方案&#xff1a;OpenLyrics让foobar2000歌词显示更智能更美观 【免费下载链接】foo_openlyrics An open-source lyric display panel for foobar2000 项目地址: https://gitcode.com/gh_mirrors/fo/foo_openlyrics 还在为foobar2000找不到好用的歌词插件而…

作者头像 李华
网站建设 2026/4/24 17:29:47

D3KeyHelper:解放双手,让暗黑3操作更智能的5大核心功能

D3KeyHelper&#xff1a;解放双手&#xff0c;让暗黑3操作更智能的5大核心功能 【免费下载链接】D3keyHelper D3KeyHelper是一个有图形界面&#xff0c;可自定义配置的暗黑3鼠标宏工具。 项目地址: https://gitcode.com/gh_mirrors/d3/D3keyHelper 厌倦了在《暗黑破坏神…

作者头像 李华