【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
相关文章:
【Maven】依赖冲突如何解决?
准备工作 1、创建一个空工程 maven_dependency_conflict_demo,在 maven_dependency_conflict_demo 创建不同的 Maven 工程模块,用于演示本文的一些点。 什么是依赖冲突? 当引入同一个依赖的多个不同版本时,就会发生依赖冲突。…...
什么是EMS
EMS是能量管理系统(Energy Management System)的缩写,是一种集成的技术解决方案,旨在帮助企业和组织更有效地管理和优化其能源使用。EMS通过收集、分析和报告能源数据来识别节能机会,并提供工具以实施改进措施。 主要…...
26页PDF | 数据中台能力框架及评估体系解读(限免下载)
一、前言 这份报告详细解读了数据中台的发展历程、核心概念、能力框架及成熟度评估体系。它从阿里巴巴的“大中台,小前台”战略出发,探讨了数据中台如何通过整合企业内部的数据资源和能力,加速业务迭代、降低成本,并推动业务增长…...
【Vue3】【Naive UI】< a >标签
【Vue3】【Naive UI】< a >标签 超链接及相关属性其他属性 【VUE3】【Naive UI】<NCard> 标签 【VUE3】【Naive UI】<n-button> 标签 【VUE3】【Naive UI】<a> 标签 <a> 标签HTML中的一个锚&…...
分页查询日期格式不对
方式一:在属性上加入注解,对日期进行格式化 方式二:在 WebMvcConfiguration 中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理 /*** 统一转换处理扩展spring mvc* 后端返回前端的进行统一转化处理* param converters*/Overrideprotected voi…...
DAY140权限提升-Linux系统权限提升篇VulnhubPATH变量NFS服务Cron任务配合SUID
一、演示案例-Linux系统提权-Web&普通用户-SUID-NFS安全 NFS是一种基于TCP/IP 传输的网络文件系统协议,通过使用NFS协议,客户机可以像访问本地目录一样访问远程服务器中的共享资源。 https://www.virtualbox.org/wiki/Downloads https://www.vuln…...
HTTPS 的应用数据是如何保证完整性的?
在 HTTPS 中,确保 应用数据的完整性 是通过以下几个关键机制来实现的: 消息认证码(MAC):用于确保数据在传输过程中未被篡改。加密:通过加密数据防止数据被窃取,并与 MAC 配合使用,确…...
Unity ShaderLab 实现3D物体描边
实现思路: 给物体添加第二个材质球,在shader的顶点着色器中使顶点的位置变大,然后在片元着色器中输出描边颜色。 shader Graph实现如下: ShaderLab实现如下: Shader "Custom/Outline" {Properties{[HDR]_…...
SQL进阶——C++与SQL进阶实践
在C开发中,SQL数据库的操作是开发者常见的任务之一。虽然前面我们已经介绍了如何在C中通过数据库连接执行基本的SQL查询,但在实际项目中,我们通常需要更加复杂和高效的数据库操作。存储过程与函数的调用、复杂SQL查询的编写、以及动态构造SQL…...
AIGC--------AIGC在医疗健康领域的潜力
AIGC在医疗健康领域的潜力 引言 AIGC(Artificial Intelligence Generated Content,人工智能生成内容)是一种通过深度学习和自然语言处理(NLP)等技术生成内容的方式。近年来,AIGC在医疗健康领域展现出了极…...
node.js中实现MySQL的增量备份
有时候,我们需要对生产库进行备份,不要求实时性很高,大概每天一次就行,为性能考虑,只备份最新更改内容,即增量备份即可,这种场景下对DB的设计和备份语句有所要求。 首先要求按源表各字段定义目标…...
Java线程池提交任务流程底层源码与源码解析
前言 嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的…...
新型大语言模型的预训练与后训练范式,Meta的Llama 3.1语言模型
前言:大型语言模型(LLMs)的发展历程可以说是非常长,从早期的GPT模型一路走到了今天这些复杂的、公开权重的大型语言模型。最初,LLM的训练过程只关注预训练,但后来逐步扩展到了包括预训练和后训练在内的完整…...
硬菜3道+馒头
硬菜3道 1、可乐鸡翅 》鸡翅滑刀酱油耗油胡椒粉盐》 搅拌腌制3-5分钟 》油锅,直到2面煎黄 》倒入可乐,到大火收汁,出锅 2、洋葱牛肉 》冻牛肉切薄酱油耗油胡椒粉盐 》手指摇匀 》加入生粉水,继续摇匀》直到粘稠 》油锅牛肉炒半熟&…...
YOLO系列论文综述(从YOLOv1到YOLOv11)【第14篇:YOLOv11——在速度和准确性方面具有无与伦比的性能】
YOLOv11 1 摘要2 改进点3 模型性能4 模型架构 YOLO系列博文: 【第1篇:概述物体检测算法发展史、YOLO应用领域、评价指标和NMS】【第2篇:YOLO系列论文、代码和主要优缺点汇总】【第3篇:YOLOv1——YOLO的开山之作】【第4篇ÿ…...
【Spring】聊聊@EventListener注解原理
1.一个Demo出发 在平时的开发中,其实编写同步线程代码是比较容易的,但是如何将一些操作和另外一些操作进行解除耦合,而事件方式 是一种很好的解耦合方式,比如当一个用户注销一个APP之后,需要发送一些短信 让他引流回来…...
LangChain——HTML文本分割 多种文本分割
Text Splitters 文本分割器 加载文档后,您通常会想要对其进行转换以更好地适合您的应用程序。最简单的例子是,您可能希望将长文档分割成更小的块,以适合模型的上下文窗口。 LangChain 有许多内置的文档转换器,可以轻松地拆分、组…...
梯度爆炸与消失
梯度爆炸和梯度消失 一、概念解析 (一)梯度爆炸 定义 在深度神经网络训练的反向传播过程中,梯度爆炸是指梯度的值过大的现象。这会使模型的参数更新出现异常。 产生原因 深层网络与链式法则:深度神经网络按链式法则计算某层权重…...
关于扩散方程的解
1-D 扩散方程的形式 Cauchy齐次方程 这个解无积分无级数,很简单的形式 美其名曰:基本解。 把基本解和初值做卷积,就得到cauchy方程的解。...
如何监控Elasticsearch集群状态?
大家好,我是锋哥。今天分享关于【如何监控Elasticsearch集群状态?】面试题。希望对大家有帮助; 如何监控Elasticsearch集群状态? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 监控 Elasticsearch 集群的状态对于确保…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
