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

news2024/11/28 9:25:54

一、背景

当开发者在一个大型项目中使用 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.coloradmin.cn/o/1341496.html

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

相关文章

Activiti工作流框架学习笔记(二)之springboot2.0整合工作流Activiti6.0

文/朱季谦 以前在工作当中做过不少与工作流Activiti有关的工作&#xff0c;当时都是spring集成activiti5.22的项目&#xff0c;现在回过头去看&#xff0c;其实版本已经稍微老了&#xff0c;因此&#xff0c;基于先前的工作经验&#xff0c;决定用较新版本的技术来重新梳理下以…

字符串转成时间的SQL,一个多种数据库通用的函数

select date 2010-10-06 from dual; date 函数&#xff0c;此函数适用于&#xff1a; 1.MySQL数据库 2.Oracle数据库 3.达梦数据库 4.人大金仓数据库

NFC物联网构建移动智能仓储系统解决方案

仓储管理作为企业研发生产活动的重要环节&#xff0c;正朝着标准化、信息化方向发展。高新作为典型的资产密集 技术密集型企业,原料样品为代表的资产数量巨大品种繁杂&#xff0c;对仓库管理的实时性及准确性要求较高。传统的人工作业模式和管理方法逐渐难以满足仓储管理对高效…

【 YOLOv5】目标检测 YOLOv5 开源代码项目调试与讲解实战(4)-自制数据集及训练(使用makesense标注数据集)

如何制作和训练自己的数据集 看yolov5官网创建数据集1.搜索需要的图片2.创建标签标注数据集地址&#xff1a;放入图片后选择目标检测创建文档&#xff0c;每个标签写在单独的一行上传结果此处可以编辑类别把车框选选择类别即可导出数据 3.新建一个目录放数据写yaml文件 4. 测试…

软件测试/测试开发丨学习笔记之Python控制流-分支、循环

分支判断 什么是分支判断 一条一条语句顺序执行叫做顺序结构分支结构就是在某个判断条件后&#xff0c;选择一条分支去执行 1. IF if condition_1:statement_block_1 elif condition_2:statement_block_2 else:statement_block_32. if 嵌套 在嵌套 if 语句中&#xff0c;可…

【北亚服务器数据恢复】ZFS文件系统服务器ZPOOL下线的数据恢复案例

服务器数据恢复环境&#xff1a; 服务器中有32块硬盘&#xff0c;组建了3组RAIDZ&#xff0c;部分磁盘作为热备盘。zfs文件系统。 服务器故障&#xff1a; 服务器运行中突然崩溃&#xff0c;排除断电、进水、异常操作等外部因素。工作人员将服务器重启后发现无法进入操作系统。…

【CISSP学习笔记】6. 安全开发

该知识领域涉及如下考点&#xff0c;具体内容分布于如下各个子章节&#xff1a; 理解安全并将其融入软件开发生命周期 (SDLC) 中在软件开发环境中识别和应用安全控制评估软件安全的有效性评估获得软件对安全的影响定义并应用安全编码准则和标准 6.1. 系统开发控制 6.1.1. 软…

AI大模型引领未来智慧科研暨丨ChatGPT在地学、GIS、气象、农业、生态、环境等领域中的高级应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

Grafana Loki 配置解析

Grafana Loki 配置文件是一个YML文件&#xff0c;在Grafana Loki 快速尝鲜的示例中是loki-config.yaml&#xff0c;该文件包含关于Loki 服务和各个组件的配置信息。由于配置数量实在太多&#xff0c;没法全部翻译&#xff0c;只能后期有需要了再补充。 如下是Grafana Loki 快速…

JVM篇:JVM内存结构

程序计数器 程序计数器英文名叫&#xff1a;Program Counter Register 作用&#xff1a;用来记录下一条jvm指令的地址行号。 先来查看一段jvm指令&#xff0c;这些指令对应的java代码就是输出1-5 操作系统运行该Java程序时具体流程如下 语言解释&#xff1a;源文件通过编译转…

设计模式-对象池模式

设计模式专栏 模式介绍模式特点应用场景对象池模式和工厂模式的区别代码示例Java实现对象池模式Python实现对象池模式 对象池模式在spring中的应用 模式介绍 对象池模式是一种创建型设计模式&#xff0c;它将对象预先创建并初始化后放入一个池中&#xff0c;以供其他对象使用。…

python如何读取被压缩的图像

读取压缩的图像数据&#xff1a; PackBits 压缩介绍&#xff1a; CCITT T.3 压缩介绍&#xff1a; 读取压缩的图像数据&#xff1a; 在做图像处理的时候&#xff0c;平时都是使用 函数io.imread() 或者是 函数cv2.imread( ) 函数来读取图像数据&#xff0c;很少用PIL.Image…

数据结构——红黑树 and B-树

红黑树 根据平衡条件第4、5两点 最短路径&#xff0c;都是黑色 最长路径&#xff0c;红黑相间 最长是最短的两倍 B-树

Android集成OpenSSL实现加解密-集成

导入so 将编译生成的 OpenSSL 动态库文件&#xff08;.so 文件&#xff09;复制到你的 Android 项目的 libs 目录中 导入头文件 将编译生成的include文件夹导入到项目中 build.gradle添加配置 defaultConfig {……testInstrumentationRunner "androidx.test.runner…

大厂前端面试题总结(百度、字节跳动、腾讯、小米.....),附上热乎面试经验!

先简单介绍下自己&#xff0c;我“平平无奇小天才”一枚&#xff0c;毕业于南方普通985普通学生&#xff0c;有幸去百度、字节面试&#xff0c;感觉大公司就是不一样&#xff0c;印象最深的是字节&#xff0c;所以有必要总结一下面试经验&#xff0c;以及面试中遇到的一些问题&…

SuperMap iServer发布的ArcGIS REST 地图服务如何通过ArcGIS API进行要素查询

作者&#xff1a;yx 前言 前面我们介绍了SuperMap iServer发布的ArcGIS REST 地图服务如何通过ArcGIS API加载&#xff0c;这里呢我们再来看看如何进行要素查询呢&#xff1f; 一、服务发布 SuperMap iServer发布的ArcGIS REST 地图服务如何通过ArcGIS API加载已经介绍如何发…

gem5学习(7):内存系统中创建 SimObjects--Creating SimObjects in the memory system

目录 一、gem5 master and slave ports 二、Packets 三、Port interface 1、主设备发送请求时从设备忙 2、从设备发送响应时主设备忙 四、Simple memory object example 1、Declare the SimObject 2、Define the SimpleMemobj class 3、Define the SimpleMemobj class…

docker compose 部署 grafana + loki + vector 监控kafka消息

Centos7 随笔记录记录 docker compose 统一管理 granfana loki vector 监控kafka 信息。 当然如果仅仅是想通过 Grafana 监控kafka&#xff0c;推荐使用 Grafana Prometheus 通过JMX监控kafka 目录 1. 目录结构 2. 前提已安装Docker-Compose 3. docker-compose 自定义服…

009:vue结合el-table实现表格行拖拽排序(基于sortablejs)

文章目录 1. 实现效果2. 安装 sortablejs 插件3. 完整组件代码4. 注意点 1. 实现效果 2. 安装 sortablejs 插件 sortablejs 更多用法 cnpm i --save sortablejs3. 完整组件代码 <template><div class"home"><div class"body"><el-ta…

车路协同中 CUDA 鱼眼相机矫正、检测、追踪

在车路协同中,鱼眼一般用来补充杆件下方的盲区,需要实现目标检测、追踪、定位。在目标追踪任务中,通常的球机或者枪机方案,无法避免人群遮挡的问题,从而导致较高的ID Swich,造成追踪不稳定。但是鱼眼相机的顶视角安装方式,天然缓解了遮挡的问题,从而实现杆件下方的盲区…