企业级依赖管理: 深入解读 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

116基于matlab的盲源信号分离

基于matlab的盲源信号分离。FASTICA方法&#xff0c;能够很好的将信号解混&#xff0c;可以替换数据进行分析。具有GUI界面&#xff0c;可以很好的进行操作。程序已调通&#xff0c;可直接运行。 116matlab盲源信号分离FASTICA (xiaohongshu.com)

java对象整理

1.对象的创建过程 首先class文件加载到内存中 这个过程如下 “加载”是“类加载”(Class Loading)过程的第一步。这个加载过程主要就是靠类加载器实现的&#xff0c; 包括用户自定义类加载器。 加载到内存后做的事情 申请对象内存 成员变量赋默认值 调用构造方法 成员变量顺序…

nginx报错upstream sent invalid header

nginx报错upstream sent invalid header 1.报错背景 最近由于nginx 1.20的某个漏洞需要升级到nginx1.25的版本。在测试环境升级完nginx后&#xff0c;发现应用直接报错502 bad gateway了。 然后查看nginx的errlog&#xff0c;发现&#xff1a; upstream sent invalid head…

【计算机视觉】角点检测(Harris、SIFT)

Harris 角点指的是窗口延任意方向移动&#xff0c;都有很大变化量的点。 用数学公式表示为&#xff1a; E(u,v)反映的移动后窗口的差异&#xff0c;w(x,y)为每个像素的点权值&#xff0c;I(xu,yv)是移动的像素值&#xff0c;I(x,y)是移动前的像素值。 将E(u,v)进行泰勒展开&am…

uni-appcss语法

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

二-从C到C++

本章会介绍一些C拓展的非面向对象的功能 引用 1 概念 引用从一定程度上讲是指针的平替&#xff0c;几乎被所有的面向对象语言所使用。引用相当于对某一目标变量起“别名”。 操作这个别名&#xff0c;与操作原变量一样。&#xff08;操作同一块地址&#xff09;不能有相同别名 …

ssrf之dict协议和file协议

1.dict协议 dict是什么协议呢&#xff1f; 定义&#xff1a;词典网络协议&#xff0c;在RFC 2009中进行描述。它的目标是超越Webster protocol&#xff0c;并允许客户端在使 用过程中访问更多字典。Dict服务器和客户机使用TCP端口2628。 官方介绍&#xff1a;http://dict.o…

文件监控-IT安全管理软件

文件监控和IT安全管理软件是用于保护企业数据和网络安全的工具。这些工具可以帮助企业监控文件的变化&#xff0c;防止未经授权的访问和修改&#xff0c;并确保数据的安全性和完整性。 一、具有哪些功能 文件监控软件可以实时监控文件系统的活动&#xff0c;包括文件的创建、修…

k8s配置安装ingress服务

k8s配置安装ingress服务 在kuboard页面,网络安装 安装完配置名称保存 apiVersion: networking.k8s.io/v1 kind: Ingress metadata:namespace: testname: pipeline spec:ingressClassName: ingressrules:- host: test.pipeline.comhttp:paths:- path: /pathType: Prefixbacke…

3D视觉-结构光测量法

概述 结构光测量法是一种通过光学投射模块将具有编码信息的结构光投射到物体表面&#xff0c;在被测物表面上形成由被测物体表面形状调制的光条图像&#xff0c;再由图像采集系统采集被测物表面漫反射的光条图像&#xff0c;通过高精度算法处理后&#xff0c;得出被测物表面的三…

oracle与mysql的分析函数(窗口函数)

分析函数定义 在SQL语句中&#xff0c;很多查询语句需要进行GROUP BY分组汇总&#xff0c;但是一旦经过分组&#xff0c;SELECT返回的记录数就会减少。为了保留所有原始行记录&#xff0c;并且仍可以进行分组数据分析&#xff0c;分析函数应运而生。 Oracle 8i 版本开始支持窗…

WebService

调试工具&#xff1a;Postman、SoapUI Soap WebService :.net WCF 、Java CFX WebService三要素&#xff1a; SOAP&#xff08;Simple Object Access Protocol&#xff09;&#xff1a;用来描述传递信息的格式&#xff0c; 可以和现存的许多因特网协议和格式结合使用&#x…

ClickHouse基础知识(六):ClickHouse的副本配置

副本的目的主要是保障数据的高可用性&#xff0c;即使一台 ClickHouse 节点宕机&#xff0c;那么也可以 从其他服务器获得相同的数据。 1. 副本写入流程 2. 配置步骤 ➢ 启动 zookeeper 集群 ➢ 在hadoop101的/etc/clickhouse-server/config.d目录下创建一个名为metrika.xml…

VUE3实现点击按钮下载文件功能

VUE3实现点击按钮下载文件功能 在写vue项目时&#xff0c;有个需求是点击表格中某一行的下载按钮&#xff0c;然后开始下载这一行对应的文件&#xff0c;效果如下&#xff1a; 表格每行的最右侧的蓝色按钮就是点击下载&#xff0c;这里涉及到原生的JavaScript写法&#xff0c;…

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

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

Qt/C++音视频开发62-电子放大/按下选择区域放大显示/任意选取区域放大

一、前言 电子放大这个功能思考了很久&#xff0c;也是一直拖到近期才静下心来完整这个小功能&#xff0c;这个功能的前提&#xff0c;主要得益于之前把滤镜打通了&#xff0c;玩出花样来了&#xff0c;只要传入对应的滤镜字符串&#xff0c;就可以实现各种各样的效果&#xf…

ssrf之curl协议,以及查看curl支持协议的办法

1.curl支持的协议 <?php phpinfo() ?> 将这个内容写在php中 打开小皮运行后可得 用ctrlf搜索curl得 可以查出curl所支持的协议 2.用ssrf获取信息 <?php function curl($url){ $chcurl_init();//创建cURL会话 curl_setopt($ch,CURLOPT_URL,$url);//设置cURL参…

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

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

重装系统以后无法git跟踪

总结&#xff1a;权限问题 故障定位 解决方案&#xff1a; 复制一份新的文件夹。&#xff08;新建的文件创建和写入权限都变了&#xff09; 修改文件为新的用户 执行提示的命令

OpenCV-Python(21):轮廓特征及周长、面积凸包检测和形状近似

2. 轮廓特征 轮廓特征是指由轮廓形状和结构衍生出来的一些特征参数。这些特征参数可以用于图像识别、目标检测和形状分析等应用中。常见的轮廓特征包括&#xff1a; 面积&#xff1a;轮廓所包围的区域的面积。周长&#xff1a;轮廓的周长&#xff0c;即轮廓线的长度。弧长&…