企业级依赖管理: 深入解读 Maven BOM

一、背景

当开发者在一个大型项目中使用 Maven 进行依赖管理时,项目往往会包含多个模块或子项目,并且这些模块会共享相同的依赖项。但是,不同模块可能会独立地指定各自的依赖版本,这可能导致以下问题:

  1. 依赖版本不一致: 不同模块中对相同依赖项使用不同的版本号,可能导致潜在的兼容性问题或冲突。
  2. 版本管理困难: 在多个模块中管理和维护依赖版本的一致性可能变得复杂,因为需要手动确保每个模块中的依赖版本保持同步。
  3. 重复配置: 在每个模块中单独指定依赖项的版本,导致了大量的重复配置,增加了维护成本。

为解决以上问题,Maven BOM(Bill of Materials,依赖关系管理)被引入,提供了一种集中管理依赖版本的方法。


此时有些同学可能会有疑问:我可以在项目的根pom中通过<dependencyManagement> 标签来集中化管理项目的所有依赖啊。

诚然,当项目使用 Maven 的 dependencyManagement 标签集中管理依赖时,确实能够集中指定依赖版本,但这种方式并不能将该项目的依赖版本供其他项目使用;相信大家对springboot并不陌生,在使用springboot的开发过程中我们通常可以看到如下依赖:

<!--引入Spring Boot官方维护的bom依赖清单-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.6.3</version><type>pom</type><scope>import</scope>
</dependency>
<!--引入阿里依赖bom清单-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>aliyun-spring-boot-dependencies</artifactId><version>1.0.0</version><type>pom</type><scope>import</scope>
</dependency

Spring Boot BOM(Bill of Materials,依赖关系管理)的优势在于,它不仅集中管理依赖版本,还允许其他项目通过引用 Spring Boot BOM 来继承其依赖版本管理的功能。换句话说,其他项目可以直接引入 Spring Boot BOM,继承其所管理的依赖版本,而无需单独指定每个依赖的版本。

相比之下,普通的 Maven 项目中,使用 dependencyManagement 标签虽然能够在当前项目中集中管理依赖版本,但其他项目无法直接继承该管理依赖版本的配置,需要手动复制相应的依赖管理部分到其他项目中,这样会增加维护成本且可能出现版本不一致的问题。

文章中提及的所有代码示例都可以在 GitHub 上找到:maven-bom-examples

二、什么是BOM

Maven 项目的打包类型时,通常可以分为三种:

  1. jar: 这是最常见的打包类型,适用于普通的 Java 项目。项目会以 jar 类型的格式进行打包。
  2. pom: 这种类型的项目通常被用作多模块项目的管理和维护。在父模块中定义了依赖项、插件等内容,实现了对版本的统一维护管理。
  3. war: 这类项目打包成为可运行的 Java Web 项目,例如可以在诸如 Tomcat、Jetty 等应用服务器上运行。

BOM 实际上是一个以 POM 类型定义的普通 Maven 项目,主要用于维护描述 Maven 项目所需的一系列公共依赖信息。通过引用 BOM 项目,可以实现对依赖版本的统一维护管理,而无需明确指定每个依赖项的版本号。


BOM 文件结构定义结构

<parent><groupId>org.example</groupId><artifactId>maven-bom-examples</artifactId><version>${revision}</version>
</parent>
<!--pom类型,多模块中依赖统一管理维护-->
<packaging>pom</packaging>
<artifactId>example-bom</artifactId><properties><!--统一维护管理变量-->
</properties><dependencyManagement><dependencies><!--统一维护管理的依赖--></dependencies>
</dependencyManagement><build><pluginManagement><plugins><!--插件依赖统一管理--></plugins></pluginManagement>
</build>

三、使用指南

3.1、创建bom

当创建 BOM(Bill Of Materials,物料清单)时,常见的两种方式包括:

  1. 单独仓库存放依赖: 在一个专门的仓库中,建立 BOM 模块用于存放特定需求的依赖信息。这种方法适用于需要隔离和管理独立的依赖需求,确保依赖版本独立于其他项目。
  2. 应用内创建 BOM 模块: 在现有应用的仓库中创建 BOM 模块,让应用的所有子模块共享使用。这种方法适用于在应用内统

选择使用哪种方式创建 BOM 取决于需求的特点和项目的架构,本文参考aliyun-spring-boot 实现方式,即应用内创建BOM模块。


新建一个maven-bom-examples项目,其目录结构如下:

maven-bom-examples
├─.idea
├─example-bom
│  └─pom.xml
└─pom.xml

example-bom模块pom.xml文件如下:

<parent><groupId>org.example</groupId><artifactId>maven-bom-examples</artifactId><version>${revision}</version>
</parent>
<!-- pom类型 -->
<packaging>pom</packaging>
<artifactId>example-bom</artifactId><properties><mysql.connector.java.version>8.0.33</mysql.connector.java.version><lingxi.cas.starter.version>1.1.4-RELEASE</lingxi.cas.starter.version><!-- 等等 -->
</properties><dependencyManagement><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.connector.java.version}</version></dependency><dependency><groupId>com.qihoo.finance.lingxi</groupId><artifactId>lingxi-cas-starter</artifactId><version>${lingxi.cas.starter.version}</version></dependency><!-- 等等 --></dependencies>
</dependencyManagement><build><pluginManagement><plugins><plugin><!-- 等等 --></plugin></plugins></pluginManagement>
</build></project>

3.2、错误使用:循环依赖

为了maven-bom-examples后续创建新的子模块可以使用bom的统一依赖,传统方式应该是将example-bom的GAV坐标放在maven-bom-examples<dependencyManagement>依赖标签中,从而作用于后续的子模块,pom如下:

<groupId>org.example</groupId>
<artifactId>maven-bom-examples</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<modules><module>example-bom</module>
</modules><properties><revision>1.0.0-SNAPSHOT</revision><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties><dependencyManagement><dependencies><dependency><groupId>org.example</groupId><artifactId>example-bom</artifactId><version>${revision}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement></project>

然而这种做法会导致项目打包报错,如下:循环依赖

> mvn clean install
[ERROR]   The project org.example:maven-bom-examples:1.0.0-SNAPSHOT (D:\IdeaProjects\maven-bom-examples\pom.xml) has 1 error
[ERROR]   The dependencies of type=pom and with scope=import form a cycle: org.example:example-bom:1.0.0-SNAPSHOT -> org.example:example-bom:1.0.0-SNAPSHOT @ org.example:example-bom:1.0.0-SNAPSHOT

导致循环依赖的原因在于:maven-bom-examples的POM 是以 type=pomscope=import 的方式导入example-bom的pom,而example-bom的pom又依赖于父pom,即maven-bom-examples,因而形成了循环依赖导致maven报错;需要注意的是如果依赖的是jar包类型的子模块则不会有循环依赖问题,

例如新增一个example-api子模块,此时目录结构如下:

maven-bom-examples
├─.idea
├─example-api
│  └─src
│  └─pom.xml
├─example-bom
│  └─pom.xml
└─pom.xml

maven-bom-examplespom新增example-api依赖,更改如下:

<dependencyManagement><dependencies>
<!--            <dependency>-->
<!--                <groupId>org.example</groupId>-->
<!--                <artifactId>example-bom</artifactId>-->
<!--                <version>${revision}</version>-->
<!--                <type>pom</type>-->
<!--                <scope>import</scope>-->
<!--            </dependency>--><dependency><groupId>org.example</groupId><artifactId>example-api</artifactId><version>${revision}</version></dependency></dependencies>
</dependencyManagement>

此时打包正常,可以看到example-apiexample-bom 处于同级目录,却只有example-bom 出现了循环依赖,这根本原因便在于<scope>import</scope>标签;以当前项目为例,当根pom引用了 <scope>import</scope> 的 BOM 时,而这个 BOM 又包含了指向根 POM 的依赖,就会导致循环依赖的问题。

example-api模块为普通jar包模块故不会导致循环依赖问题。

3.3、正确使用

正确的使用方式是创建一个名为 example-parent 模块,使其与 example-bom处于同级目录下,并在 example-parent 模块中导入 BOM 的依赖。然后,其他子模块只需引入 example-parent 模块作为其父模块,从而实现对 BOM 依赖的继承。此时目录结构如下:

maven-bom-examples
├─.idea
├─example-api
│  └─src
│  └─pom.xml
├─example-parent
│  └─pom.xml
├─example-bom
│  └─pom.xml
└─pom.xml

example-parent pom如下:由于example-parentexample-bom处于同级目录下,故不会出现循环依赖问题。

<parent><groupId>org.example</groupId><artifactId>maven-bom-examples</artifactId><version>${revision}</version>
</parent>
<packaging>pom</packaging>
<artifactId>example-parent</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties><dependencyManagement><dependencies><dependency><groupId>org.example</groupId><artifactId>example-bom</artifactId><version>${revision}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement></project>

maven-bom-examplespom:无依赖

<groupId>org.example</groupId>
<artifactId>maven-bom-examples</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<modules><module>example-bom</module><module>example-api</module><module>example-parent</module>
</modules><properties><revision>1.0.0-SNAPSHOT</revision><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties></project>

3.4、创建子模块

由于 example-parent 模块引入了 BOM 依赖,因此,若要在后续的子模块中使用统一的依赖管理,只需要在 example-parent 父模块下创建相应的子模块即可实现统一依赖的继承。例如新建example-model,此时目录结构如下:

maven-bom-examples
├─.idea
├─example-api
│  ├─src
│  └─pom.xml
├─example-parent  
│  ├─example-model
│  │  ├─src
│  │  └─pom.xml
│  └─pom.xml
├─example-bom
│  └─pom.xml
└─pom.xml

example-parentpom如下:

<parent><groupId>org.example</groupId><artifactId>maven-bom-examples</artifactId><version>${revision}</version>
</parent>
<packaging>pom</packaging>
<artifactId>example-parent</artifactId><modules><!-- 新增子模块 --><module>example-model</module>
</modules><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties><dependencyManagement><dependencies><dependency><groupId>org.example</groupId><artifactId>example-bom</artifactId><version>${revision}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

example-modelpom如下:

<parent><groupId>org.example</groupId><artifactId>example-parent</artifactId><version>${revision}</version>
</parent><artifactId>example-model</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties><dependencies><!-- 子模块使用了bom依赖而无需指定version --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.qihoo.finance.lingxi</groupId><artifactId>lingxi-cas-starter</artifactId></dependency>
</dependencies>

3.5、扩展:<relativePath>

<relativePath> 元素是 Maven POM 文件中 <parent> 元素的一个子元素,用于指定父模块相对于当前子模块的路径,它告诉 Maven 在哪里找到父 POM,如下:

<parent><groupId>xxx</groupId><artifactId>xxx</artifactId><version>xxx</version><relativePath>../xxx/pom.xml</relativePath>
</parent>

这个元素通常在子模块的 POM 文件中用于指定父 POM 文件的路径,以便在项目中更灵活地管理父子模块之间的关系。

如果父模块就在当前项目的根目录下,则不需要指定 <relativePath> 元素,例如上面的example-model子模块就无需使用<relativePath> 标签。

之所以要介绍<relativePath> 标签是因为有时我们为了更直观的查看所有应用模块而将所有子模块全部放在根目录下,例如开源项目debezium:

在这里插入图片描述

此时新增example-relative子模块,目录结构如下:

maven-bom-examples
├─.idea
├─example-api
│  ├─src
│  └─pom.xml
├─example-parent  
│  ├─example-model
│  │  ├─src
│  │  └─pom.xml
│  └─pom.xml
├─example-bom
│  └─pom.xml
├─example-relative
│  ├─src
│  └─pom.xml
└─pom.xml

example-relativepom:

<parent><groupId>org.example</groupId><artifactId>example-parent</artifactId><version>${revision}</version><!-- 由于与example-parent同级目录, 使用relativePath标签指定父POM路径 --><relativePath>../example-parent/pom.xml</relativePath>
</parent><artifactId>example-relative</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties><dependencies><!-- 子模块无需指定version, 而是使用了bom依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
</dependencies>

example-parentpom:

<parent><groupId>org.example</groupId><artifactId>maven-bom-examples</artifactId><version>${revision}</version>
</parent>
<packaging>pom</packaging>
<artifactId>example-parent</artifactId><modules><module>example-model</module><!-- 新增子模块 --><module>../example-relative</module>
</modules><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties><dependencyManagement><dependencies><dependency><groupId>org.example</groupId><artifactId>example-bom</artifactId><version>${revision}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

四、总结

当涉及到大型项目和多模块应用时,Maven BOM(Bill of Materials)提供了一种管理依赖版本的强大机制。通过创建BOM可以:

  • 集中管理依赖的版本号,确保各模块使用相同的依赖版本。
  • 减少版本冲突和不一致性的问题,提高项目的稳定性和可靠性。
  • 允许其他项目引用BOM并继承其依赖版本,减少重复配置的需求,提高项目的可维护性和一致性。

深入理解和有效使用Maven BOM有助于简化依赖管理过程,提高项目的开发效率和整体质量。

五、相关资料

  • debezium
  • aliyun-spring-boot-parent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/589346.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在多Module项目中,给IDEA底部选项卡区域添加Services选项卡

一般一个spring cloud项目中大大小小存在几个十几个module编写具体的微服务项目。此时&#xff0c;如果要调试测需要依次启动各个项目比较麻烦。 idea其实提供了各module的启动管理工具了&#xff0c;可以快速启动和关闭各个服务&#xff0c;也能批量操作&#xff0c;比如一次…

25. 数组作为函数参数

写代码时&#xff0c;我们会将数组作为参数传给函数 冒泡排序&#xff1a; 两两相邻的元素进行比较&#xff0c;可能的话进行交换 一趟冒泡排序会将一个元素放在其最后应该在的位置 10个数字只需9趟&#xff0c;第一趟10个数字待排序&#xff0c;9对比较&#xff1b;第二趟…

计算机科学速成课【学习笔记】(1)——计算机早期历史

本集课程B站链接&#xff1a; 【计算机科学速成课】[40集全/精校] - Crash Course Computer Science_哔哩哔哩_bilibili【计算机科学速成课】[40集全/精校] - Crash Course Computer Science共计40条视频&#xff0c;包括&#xff1a;1. 计算机早期历史-Early Computing、2. 电…

【2024最新版】neo4j安装配置

neo4j安装 写在最前面下载配置环境&#xff08;还是不行&#xff1f;&#xff09;启动neo4jpython中调用 写在最前面 之前我安装过&#xff0c;还写了一篇笔记 结果意外发现没有了&#xff0c;而且和之前安装的步骤不一样了&#xff0c;因此再次记录安装过程 下载 https://ne…

HLS 2017.4 导出 RTL 报错:ERROR: [IMPL 213-28] Failed to generate IP.

软件版本&#xff1a;HLS 2017.4 在使用 HLS 导出 RTL 的过程中产生如下错误&#xff1a; 参考 Xilinx 解决方案&#xff1a;https://support.xilinx.com/s/article/76960?languageen_US 问题描述 DESCRIPTION As of January 1st 2022, the export_ip command used by Vivad…

在Mac上恢复SD卡数据的 6 个有效应用程序

慌&#xff01;SD卡里的照片和视频不小心删了&#xff0c;Mac设备上还恢复不了数据&#xff01; 遇到这种情况&#xff0c;你需要的是一款可靠的Mac适用的SD卡恢复软件。我们为你准备了一份最佳的SD卡恢复软件列表&#xff0c;并且还有详细的评论。另外&#xff0c;我们还会给…

FTP简介及搭建计算机端口的介绍

目录 一. FTP的简介 二. FTP的主要作用 三. 搭建FTP服务器 3.1 开启防火墙 3.2 创建组 3.3 创建用户 3.4 用户绑定组 3.5 安装FTP服务器 3.6 配置FTP服务器 3.7 配置FTP文件夹的权限 3.8 连接测试 3.8.1 服务器本机测试 3.8.2 外部服务器测试 3.8.3 借助工具MobalXterm 四…

让电脑变得更聪明——用python实现五子棋游戏

作为经典的棋类游戏&#xff0c;五子棋深受大众喜爱&#xff0c;但如果仅实现人与人的博弈&#xff0c;那程序很简单&#xff0c;如果要实现人机对战&#xff0c;教会计算机如何战胜人类&#xff0c;那就不是十分容易的事了。本文我们先从简单入手&#xff0c;完成五子棋游戏的…

DNS域名查询过程

目录 DNS&#xff08;Domain Names System&#xff09; 域名转IP IP转域名 域名 域名查询流程 浏览器DNS缓存 操作系统缓存 本地host文件 完整流程 递归查询 迭代查询 DNS&#xff08;Domain Names System&#xff09; 域名系统&#xff0c;将域名和 IP 地址进行转…

【Spring】AOP的AspectJ开发

AOP基础不了解可以阅读&#xff1a;【Spring】AOP原来如此-CSDN博客 AspectJ是一个居于JAVA开发的AOP框架 基于XML的声明式AspectJ 基于XML的声明式AspectJ是通过XML文件来定义切面&#xff0c;切入点及通知&#xff0c;所有的切面、切入点和通知必须定义在内&#xff0c; 元…

【SpringBoot】常用注解

RequestBody&#xff1a;自动将请求体中的 json 数据转换为实体类对象。 这个例子凑巧传入的json属性键名和User键名一致&#xff0c;可以直接使用User实体类对象&#xff0c;如果键名不一致则需要用一个Map 类接收参数&#xff1a; PutMapping("/update")public R…

给多行文本的每行添加指定的前缀textwrap.indent()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 给多行文本的每行添加指定的前缀 textwrap.indent() [太阳]选择题 请问以下代码输出的第一行结果是&#xff1f; import textwrap text welcome to China! print("【显示】text\n&quo…

Head First Design Patterns - 观察者模式

观察者模式 观察者模式定义了对象之间的一对多依赖&#xff0c;当一个对象改变状态时&#xff0c;它的所有依赖者都会收到通知并自动更新。观察者模式是一种对象行为型模式。 场景 很多用户都订阅了某一公众号&#xff0c;当该公众号更新时&#xff0c;所以用户都会收到消息…

C++17中的内联变量

在C11中&#xff1a; (1).声明为constexpr的函数隐式地是内联函数; (2).deleted函数隐式地是一个内联函数。 在内联函数中&#xff1a; 1.所有函数定义中的函数局部静态对象(function-local static object)在所有翻译单元之间共享(它们都引用一个翻译单…

【c++————————构造函数和析构函数】

【c————————构造函数和析构函数】 欢迎阅读新一期的c模块————构造函数和析构函数 ✒️个人主页&#xff1a;-Joker- &#x1f3f7;️专栏&#xff1a;C &#x1f4dc;代码仓库&#xff1a;c_code &#x1f339;&#x1f339;欢迎大佬们的阅读和三连关注&#xff0c…

Dependency Track:智能组件分析平台。

Dependency Track:智能组件分析平台。 ############################# 免责声明:工具本身并无好坏,希望大家以遵守《网络安全法》相关法律为前提来使用该工具,支持研究学习,切勿用于非法犯罪活动,对于恶意使用该工具造成的损失,和本人及开发者无关。 ################…

Linux | 解决问题Ubuntu重启无法进入系统以及网络无法连接【图文详解】

Ubuntu18.04重启无法进入系统&#xff0c;重开后如图 一直在加载系统内核4.15.0-213-generic,无法加载 错误原因 原本的系统是Ubuntu16.04,使用命令升级到Ubuntu18.04版本&#xff0c;升级重启后&#xff0c;远程无法连接&#xff01; 错误解决 第一步&#xff1a;进入GRUB…

AIGC入门系列1:感性的认识扩散模型

1、序言 大家好&#xff0c;欢迎来到AI手工星的频道&#xff0c;我是专注AI领域的手工星。AIGC已经成为AI又一个非常爆火的领域&#xff0c;并且与之前的AI模型不同&#xff0c;AIGC更适合普通人使用&#xff0c;我们不仅可以与chatgpt对话&#xff0c;也能通过绘画模型生成想…

使用ASP.NET MiniAPI 调试未匹配请求路径

本文将介绍如何在使用ASP.NET MiniAPI时调试未匹配到的请求路径。我们将详细讨论使用MapFallback方法、中间件等工具来解决此类问题。 1. 引言 ASP.NET MiniAPI是一个轻量级的Web API框架&#xff0c;它可以让我们快速地构建和部署RESTful服务。然而&#xff0c;在开发过程中如…

PACC:数据中心网络的主动 CNP 生成方案

PACC&#xff1a;数据中心网络的主动 CNP 生成方案 文章目录 PACC&#xff1a;数据中心网络的主动 CNP 生成方案PACC算法CNP数据结构PACC参数仿真结果参考文献 PACC算法 CNP数据结构 PACC参数 仿真结果 PACC Hadoop Load0.2 的情况&#xff1a; PACC Hadoop Load0.4 的情况&a…