【Maven】依赖冲突如何解决?

准备工作

1、创建一个空工程 maven_dependency_conflict_demo,在 maven_dependency_conflict_demo 创建不同的 Maven 工程模块,用于演示本文的一些点。
请添加图片描述

什么是依赖冲突?

  • 引入同一个依赖的多个不同版本时,就会发生依赖冲突。

演示 1

步骤:

1、在 maven_dependency_conflict_demo 中创建 2 个基于 Maven 的 JavaSE 项目 maven_00 和 maven_01。
2、在 maven_01 的 pom.xml 文件中引入 junit 4.12 版本。
3、将 maven_01 打包安装到私服。
4、在 maven_00 的 pom.xml 文件中引入 junit 4.13 版本和 maven_01。

结果如下图所示,可以看到,的的确确发生了依赖冲突,而且 Maven 自己处理了依赖冲突。Maven 处理冲突依据的是短路径优先原则,后面会提到。
请添加图片描述

Maven的依赖调解原则

Maven 在遇到依赖冲突时,会先根据路径最短优先声明顺序优先两大原则处理冲突。

1、短路径优先

当一个间接依赖存在多条引入路径时,引入路径短的会被解析使用。
见演示 1,依赖关系图如下图所示:
请添加图片描述

附:代码分支 2411291-larryla-maven-name-first。

2、声明顺序优先

如果存在短路径,则优先选择短路径。如果路径相同,则先声明者优先。

演示 2

步骤:

1、在 maven_dependency_conflict_demo 中创建 3 个基于 Maven 的 JavaSE 项目 maven_02、maven_03 和 maven_04。
2、在 maven_03 中引入 junit 4.12 版本。
3、将 maven_03 打包安装到私服。
4、在 maven_04 中引入 junit 4.13 版本。
5、将 maven_04 打包安装到私服。
6、在 maven_02 中引入 maven_03 和 maven_04,查看 junit 版本引入情况。

上面步骤对应的依赖关系图如下:
请添加图片描述
结果如下:

(1) 若 maven_03 在 maven_04 前面,则 maven_02 中引入的是 junit 4.12
请添加图片描述
(2) 若 maven_04 在 maven_03 前面,则 maven_02 中引入的是 junit 4.13
请添加图片描述

附:代码分支 2411291-larryla-maven-name-first。

3、特殊情况

(1) 子 pom 覆盖父 pom。

如果在父工程和子工程引入了相同依赖的不同版本,那么子工程中的版本会覆盖覆盖父工程中的版本。

步骤:

1、在 maven_dependency_conflict_demo 中创建 2 个基于 Maven 的 JavaSE 项目 maven_07 和 maven_08。
2、在 maven_07 中引入 junit 4.13 版本。
3、在 maven_08 重新指定的 junit 为 4.12 版本。

结果如下图所示,可以看到,子工程引入的是 junit 4.12 版本,而不是继承自父工程的 4.13 版本,这说明子工程覆盖了父工程的版本。
请添加图片描述
(2) 同一个 pom.xml 文件里配置相同资源的不同版本,后配置的覆盖先配置的。

在 maven_07 的 pom.xml 文件中先后引入 MyBatis 3.5.11 和 3.5.7 版本,结果如下图。可以看到,后声明的 3.5.7 版本覆盖了先声明的 3.5.11 版本。
请添加图片描述

排除依赖

情景

如果仅仅依靠 Maven 来进行依赖调解,在很多情况下是不适用的,这时需要我们手动排除依赖。

举个例子,当前项目存在下面的依赖关系:

依赖链路一:A -> B -> C -> X(2.0.0) // dist = 3
依赖链路二:A -> D -> X(1.0.0) // dist = 2

根据路径最短优先原则,X(1.0) 会被解析使用,也就是说 A 中实际用的是 1.0 版本的 X。但是,这样可能会导致错误。

假如 X(2.0) 相较于 X(1.0) 新增了 1 个类和 1 个方法,并且 A 中使用了新增的类和方法,编译不通过。

问题演示

步骤:

1、在 maven_dependency_conflict_demo 中创建 5 个基于 Maven 的 JavaSE 项目 maven_a、maven_b、maven_c 、maven_d 和 maven_x。
2、在 maven_x 的 1.0 版本打包安装到私服的 Release 仓库。
3、将 maven_x 的 2.0 版本打包安装到私服的 Release 仓库。
4、在 maven_c 中引入 maven_x 的 2.0 版本;将 maven_c 打包安装到私服的 Release 仓库。在 maven_b 中引入 maven_c;将 maven_b 打包安装到私服的 Release 仓库。在 maven_a 中引入 maven_b。
5、在 maven_d 中引入 maven_x 的 1.0 版本,将 maven_d 打包安装到私服的 Release 仓库。在 maven_a 中引入 maven_d。

上面步骤对应的依赖关系图如下:
请添加图片描述
maven_x 1.0.0 版本:
请添加图片描述
在 2.0.0 版本中,变化如下:

  • 在 BallSports 类中,新增方法 playFootBall。
  • 新增类 SwimmingSports,类中包含方法 FreeStyle。

结果如下图,依据短路径优先原则,在 maven_a 模块中引入了 maven_x 1.0.0 版本,舍弃了 maven_x 2.0.0 版本,所以无法使用 2.0.0 版本中新增的类和方法
请添加图片描述

解决方案

一般在解决依赖冲突的时候,我们都会优先保留版本较高的,这是因为大部分 jar 在升级的时候都会做到向下兼容。

1、exclusions标签

具体做法是,在 maven_a 的 pom.xml 文件中使用 exclusion 标签显式排除掉 maven_x 1.0.0 版本的依赖。结果如下图所示,成功引入 maven_x 2.0.0 版本,舍弃 1.0.0 版本。
请添加图片描述

2、optional标签

我们还可以使用 optional 标签,对外隐藏当前所依赖的资源。

具体做法是,在 maven_d 中引入 maven_x 1.0.0 版本时指定 optional 标签值为 true。

结果如下图所示,可以看到,使用 optional 标签可以使 maven_x 1.0.0 版本的 jar 包不再通过 maven_d 传递,也就是 maven_a 在引入 maven_b 时,得不到 maven_x 1.0.0 的 jar 包。

请添加图片描述

附:代码分支 2411292-larryla-maven-dependency-exclusion。

对比:标签和标签的区别

由上可知,在 maven_a → maven_d → maven_x 这条分支上,我们有两种解决依赖冲突的方法,即 exclusions 标签和 optional 标签。两种方法的本质都是不将 maven_x 1.0.0 jar 包传递到 maven_a 中。但区别在于:

  • 排除依赖是在 maven_a 引入 maven_d 依赖时,同时设置<exclusions>,maven_a 知道 maven_x 1.0.0 的存在,主动将其排除掉。这种适用于不能修改 maven_d 的配置文件的情况
  • 可选依赖是在 maven_d 上为其依赖项 maven_x 设置<optional>,是 maven_d 不让"外界"知道依赖 maven_x 的存在,不再传递依赖 maven_x,此时,maven_a 不知道 maven_x 1.0.0 的存在。这种适用于可以修改 maven_d 的配置文件的情况

思考 1

在"问题演示"中,我们复现了短路径优先可能导致的问题,即 maven_a 会引入 maven_x 1.0.0 版本,舍弃 maven_x 2.0.0 版本。那么问题来了,既然引入了 maven_x 1.0.0 版本,那么 maven_b 和 maven_c 会用哪个版本的 maven_x 呢?
请添加图片描述
结果如下,由编辑区的方法调用和右侧的依赖管理可以得知,maven_a 为了解决依赖冲突选择引入 maven_x 1.0.0 版本,而 maven_b 和 maven_c 不受 maven_a 的影响,引入的依旧是 maven_x 2.0.0 版本:
请添加图片描述
因此,上面给出的依赖传递图不是很精确的,应该如下:

  • maven_a 引入 maven_x 1.0.0
  • maven_b、maven_c 引入 maven_x 2.0.0
    请添加图片描述

附:代码分支 2411292-larryla-maven-dependency-exclusion。

思考 2

对于"情景"一节,Maven 在依据短路径优先原则调解冲突时可能会带来一些问题。假如 X(2.0) 相较于 X(1.0) 新增了 1 个类和 1 个方法:

  • A 中使用了新增的类,编译不通过。
  • A 中使用了新增的方法,编译不通过。

我们在"问题演示"一节复现了上面两个问题,并且在"解决方案"一节中使用两种方案 exclusions 和 optional,引入了高版本 maven_x 2.0.0 解决了这个问题。

但是,引入高版本就没有问题了吗?

还是下图的依赖关系,假如 maven_a 引入了 maven_x 2.0.0 版本。
请添加图片描述
假如 maven_x 2.0.0 相比于 maven_x 1.0.0 版本删掉了一个类,但 maven_a 已经引用了,这时会报错;maven_x 2.0.0 相比于 maven_x 1.0.0 版本删掉了一个方法,但 maven_a 已经引用了,同样也会报错。

演示

我们重新组织 maven_x 1.0.0 和 maven_x 2.0.0 版本包括的内容。

maven_x 1.0.0 版本内容如下:

  • BallSports:playBasketBall 方法、playFootBall 方法

  • SwimmingSports:FreeStyle 方法
    请添加图片描述
    maven_x 1.0.0 版本内容变化如下:

  • 删掉 BallSports 类中的 playBasketBall 方法

  • 删掉 SwimmingSports 类

步骤:

1、在 maven_x 的 1.0 版本打包安装到私服的 Release 仓库。
2、将 maven_x 的 2.0 版本打包安装到私服的 Release 仓库。
3、在 maven_c 中引入 maven_x 的 2.0 版本;将 maven_c 打包安装到私服的 Release 仓库。在 maven_b 中引入 maven_c;将 maven_b 打包安装到私服的 Release 仓库。在 maven_a 中引入 maven_b。
4、在 maven_d 中引入 maven_x 的 1.0 版本,将 maven_d 打包安装到私服的 Release 仓库。在 maven_a 中引入 maven_d。

结果如下,可以看到 maven_a 中引入的是 maven_x 2.0.0 版本,无法使用 2.0.0 版本相比 1.0.0 版本删掉的方法和类,直接编译不通过。
请添加图片描述

附:代码分支 2411301-larryla-maven-dependency-exclusion-high-ver-problem。

聚合工程,统一管理版本

聚合工程:一个项目允许创建多个子模块,多个子模块组成一个整体,可以统一进行项目的构建。要弄明白聚合工程,得先清楚"父子工程"的概念:

  • 父工程:没有任何代码、仅有pom.xml的空项目,用来定义公共依赖、插件和配置;
  • 子工程:编写具体代码的子项目,可以继承父工程的依赖项、配置,还可以独立拓展。

而 Maven 聚合工程,就是基于父子工程结构,将一个完整项目,划分出不同的层次。这种方式可以很好的管理多模块之间的依赖关系,以及构建顺序,大大提高了开发效率、维护性。

为了防止不同子工程引入不同版本的依赖,在父工程中,统一对依赖的版本进行控制,规定所有子工程都使用同一版本的依赖,可以使用<dependencyManagement>标签来管理。

子工程在使用<dependencyManagement>中已有的依赖项时,不需要写<version>版本号,版本号在父工程中统一管理,这样做的好处在于:以后为项目的技术栈升级版本时,不需要单独修改每个子工程的POM,只需要修改父POM文件即可,减少了版本冲突的可能性。

演示

步骤:

1、在 maven_dependency_conflict_demo 中创建 2 个基于 Maven 的 JavaSE 项目 maven_05 和 maven_06。
2、在 maven_05 中引入 junit 4.13 版本,使用 dependencyManagement 标签统一管理。
3、在 maven_06 继承 maven_05 的 junit 4.13 版本。

上面步骤对应的依赖关系图如下:
请添加图片描述
结果如下图所示,可以看到,在 maven_06 工程中,只需要指定 groupId 和 artifactId,不需要指定 version,就可以从父工程 maven_05 中继承 junit 4.13 依赖。
请添加图片描述

其他

1、子工程继承父工程的依赖时,可以指定版本,这时子工程的版本会覆盖父工程的版本。

演示:

还是使用"演示"一节的模块。在 maven_06 模块中指定 junit 的版本为 4.12,那么 maven_06 引入的 junit 将是 4.12 版本,而不是父工程的 4.13 版本:
请添加图片描述
虽然可以在子工程中指定其他版本,但不建议这么做。如果这样做的话,使用 dependencyManagement 的意义就不大了。

2、父工程可以使用 properties 标签指定依赖的版本,然后在 GAV 坐标中引入该版本。这样在升级依赖版本时,只需要修改 properties 标签中变量的值,而无需修改 GAV 中的 version 标签的值。

演示:

还是使用"演示"一节的模块。在 maven_05 的 pom.xml 文件中,在 properties 标签中自定义一个 junit-version 标签,用于指定 junit 依赖的版本号。然后,在 version 标签中使用 ${junit-version} 的形式声明版本号。如下图所示,maven_06 依旧成功引入了 junit 4.13 版本。
请添加图片描述
3、dependencyManagement 标签只是用于声明依赖,不会在项目中引入依赖。

如下图所示,只是在聚合工程的子工程中引入了 junit 依赖,父工程并未引入依赖。
请添加图片描述
4、dependencyManagement 和 dependencies 的区别

  • <dependencies>:定义强制性依赖,写在该标签里的依赖项,子工程必须继承。
  • <dependencyManagement>:定义可选性依赖,该标签里的依赖项,子工程可选择性地继承。

附:代码分支 2411302-larryla-maven-dependency-uni-manage

排查依赖冲突

在 MySQL 中,我们会借助一些工具进行慢 SQL 分析。那么,在 Maven 中,我们是否可以借助一些工具进行依赖冲突定位呢?

我们可以在插件市场下载"Maven Helper" 插件辅助排查依赖冲突。
请添加图片描述
在"排除依赖-情景"一节中,我们分析了如下依赖冲突情况,得到的结论是 Maven 会依据短路径优先原则引入 X(1.0) 版本。

依赖链路一:A -> B -> C -> X(2.0.0) // dist = 3
依赖链路二:A -> D -> X(1.0.0) // dist = 2

接下来,我们使用 Maven Helper 插件来验证一下。

打开 maven_a 的 pom.xml 文件,在下方点击 Dependency Analyzer,然后点击 Refresh UI,会弹出如下界面:
请添加图片描述
提示有一个依赖冲突,即 maven_x: 1.0.0。我们点开看看,发现与之冲突的是 maven_x 2.0.0。这就和上面呼应上了,Maven 依据短路径优先原则,选择了 1.0.0 版本,舍弃了 2.0.0 版本。
请添加图片描述
在"排除依赖-解决方案"一节中,我们基于排除依赖引入了 2.0.0 版本,这里,我们同样保留 2.0.0 版本。在 1.0.0 上右键,选择 exclude,表示排除掉该版本。然后点击上面的 Reimport,再点击 Refresh UI。
请添加图片描述
返回到 pom.xml 文件,发现点击上面的 exclude 按钮,就是在 pom.xml 文件中使用了 exclusions 标签:
请添加图片描述

思维导图

请添加图片描述
代码仓库:git@github.com:Acura-bit/maven_dependency_conflict_demo.git

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

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

相关文章

yolo辅助我们健身锻炼

使用软件辅助健身能够大大提升运动效果并帮助你更轻松地达成健身目标。确保每次锻炼都更加高效且针对性强,精确记录你的训练进度,帮助你更清晰地看到自己的进步,避免无效训练。 借助YOLO11的尖端计算机视觉技术,跟踪和分析锻炼变得异常简单。它可以无缝检测和监控多种锻炼…

YOLO系列论文综述(从YOLOv1到YOLOv11)【第3篇:YOLOv1——YOLO的开山之作】

YOLOv1 1 摘要2 YOLO: You Only Look Once2.1 如何工作2.2 网络架构2.3 训练2.4 优缺点 YOLO系列博文&#xff1a; 【第1篇&#xff1a;概述物体检测算法发展史、YOLO应用领域、评价指标和NMS】【第2篇&#xff1a;YOLO系列论文、代码和主要优缺点汇总】【第3篇&#xff1a;YOL…

基于Java Springboot学生信息管理系统

一、作品包含 源码数据库设计文档全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据库&…

基于Pytorch的CIFAR100数据集上从ResNet50到VGG16的知识蒸馏实验记录

知识蒸馏的概念 可以参照NeurIPS2015的论文“Distilling the Knowledge in a Neural Network”了解知识蒸馏的概念。 知识蒸馏的狭义概念就是从复杂模型中迁移知识来提升简单模型的性能。复杂模型称之为教师模型&#xff0c;简单模型称之为学生模型。最近&#xff0c;笔者重温…

#Java-JDK7、8的时间相关类,包装类

1. JDK7-Date类 我们先来看时间的相关知识点 世界标准时间: 格林尼治时间/格林威治时间(Greenwich Mean Time)简称GMT。目前世界标准时间(UTC)已经替换为:原子钟中国标准时间: 世界标准时间8小时 时间单位换算: 1秒1000毫秒 1毫秒1000微秒 1微秒1000纳秒 Date类 Date类…

glog在vs2022 hello world中使用

准备工作 设置dns为阿里云dns 223.5.5.5&#xff0c;下载cmake&#xff0c;vs2022&#xff0c;git git clone https://github.com/google/glog.git cd glog mkdir build cd build cmake .. 拷贝文件 新建hello world并设置 设置预处理器增加GLOG_USE_GLOG_EXPORT;GLOG_NO_AB…

深度学习:梯度下降法

损失函数 L&#xff1a;衡量单一训练样例的效果。 成本函数 J&#xff1a;用于衡量 w 和 b 的效果。 如何使用梯度下降法来训练或学习训练集上的参数w和b &#xff1f; 成本函数J是参数w和b的函数&#xff0c;它被定义为平均值&#xff1b; 损失函数L可以衡量你的算法效果&a…

半桥LLC谐振变换器及同步整流MATLAB仿真(二)

在上文《半桥LLC谐振变换器及同步整流MATLAB仿真&#xff08;一&#xff09;》讲解了半桥LLC谐振变换器的工作原理&#xff0c;本文将利用MATLAB搭建电路模型进行仿真。 参数&#xff1a;输入电压&#xff1a;400Vdc&#xff1b;输出电压范围&#xff1a;36-50V &#xff1b;输…

利用若依代码生成器实现课程管理模块开发

目录 前言1. 环境准备1.1 数据库表设计与导入 2. 使用若依代码生成器生成模块代码2.1 导入数据库表2.2 配置生成规则2.2.1 基本信息配置2.2.2 字段信息配置2.2.3 生成信息配置 3. 下载与集成生成代码3.1 解压与集成3.2 启动项目并验证 4. 优化与扩展4.1 前端优化4.2 后端扩展 结…

AI前景分析展望——GPTo1 SoraAI

引言 人工智能&#xff08;AI&#xff09;领域的飞速发展已不仅仅局限于学术研究&#xff0c;它已渗透到各个行业&#xff0c;影响着从生产制造到创意产业的方方面面。在这场技术革新的浪潮中&#xff0c;一些领先的AI模型&#xff0c;像Sora和OpenAI的O1&#xff0c;凭借其强大…

springboot359智慧草莓基地管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;智慧草莓基地管理系统 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本智慧草莓基地管理系统就…

排序算法之插入排序篇

插入排序 思路&#xff1a; 就是将没有排序的元素逐步地插入到已经排好序的元素后面&#xff0c;保持元素的有序 视频的实现过程如下&#xff1a; 插入排序全过程 代码实现过程如下&#xff1a; public static void Insertion(int[] arr) { for (int i 1; i < arr.length…

【机器学习】支持向量机SVR、SVC分析简明教程

关于使用SVM进行回归分析的介绍很少&#xff0c;在这里&#xff0c;我们讨论一下SVR的理论知识&#xff0c;并对该方法有一个简明的理解。 1. SVC简单介绍 SVR全称是support vector regression&#xff0c;是SVM&#xff08;支持向量机support vector machine&#xff09;对回…

SpringMVC-08-json

8. Json 8.1. 什么是Json JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式&#xff0c;目前使用特别广泛。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写&#xf…

APM32使用I2C驱动OLED

实验效果 本次实验主要讲APM32的I2C外设的初始化和APM32作为主机如何发送数据&#xff0c;OLED的驱动写起来较难本次实验不涉及。由于条件有限本次只能讲主机发送&#xff0c;接收也没有涉及。 硬件原理图 源代码 I2C初始化部分 #ifndef __BSP__IIC_H__ #define __BSP__IIC_…

QT布局详解

ui设计器设计界面很方便&#xff0c;为什么还要手写代码? (1)更好的控制布局 (2)更好的设置qss (3)代码复用 创建水平布局 包含头文件 #include<QHBoxLayout> 创建水平布局QHBoxLayout *pHLay new QHBoxLayout(父窗口指针);//一般填this QPushButton *pBtn1 n…

宏集eXware物联网网关在水务管理系统上的应用

一、前言 水务管理系统涵盖了对城市水网、供水、排水、污水处理等多个环节的监控与管理。随着物联网&#xff08;IoT&#xff09;技术的快速发展&#xff0c;物联网网关逐渐成为水务管理系统中的关键组成部分。 宏集物联网网关以其高效的数据采集、传输和管理功能&#xff0c…

不修改内核镜像的情况下,使用内核模块实现高效监控调度时延

一、背景 在之前的博客 调度时延的观测_csdn 调度时延的观测 杰克崔-CSDN博客 里&#xff0c;我们讲了多种监控调度时延的方法&#xff0c;有依靠系统现有节点来监控&#xff0c;但是依赖系统现有节点做不到每个单词调度时延的监控&#xff0c;也讲了通过修改内核代码&#xf…

在 ASP.NET C# Web API 中实现 Serilog 以增强请求和响应的日志记录

介绍 日志记录是任何 Web 应用程序的关键方面。它有助于调试、性能监控和了解用户交互。在 ASP.NET C# 中&#xff0c;集成 Serilog 作为记录请求和响应&#xff08;包括传入和传出的数据&#xff09;的中间件可以显著提高 Web API 的可观察性和故障排除能力。 在过去的几周里&…

【开源免费】基于Vue和SpringBoot的技术交流分享平台(附论文)

博主说明&#xff1a;本文项目编号 T 053 &#xff0c;文末自助获取源码 \color{red}{T053&#xff0c;文末自助获取源码} T053&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…