jar包升级中依赖冲突问题经验总结

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

依赖冲突,实际上指的是同一依赖存在多个版本时,maven项目在选择时发生冲突。归根到底,原因在于java在import依赖时是不能指定引入哪个版本的依赖,这就导致了在升级了某个A依赖后,由于依赖传递,A依赖的B依赖也随之升级到V2版本;而C依赖于D,D依赖于B的V1版本,这就使得项目中同时存在两个版本的B依赖,从而发生依赖冲突 。同时由于maven默认依赖处理策略,项目会引用V2版本的B依赖,而新版本中可能会废弃一些类或者方法,这就间接导致了运行项目时会报NoSuchMethodError、ClassNotFoundError异常,这也是依赖冲突的常见现象。

一、maven依赖管理机制

1 maven helper插件

首先进入IDEA插件中心,搜索maven helper插件下载并进行安装。
在这里插入图片描述
而插件入口就在pom文件中,点击Dependency Analyzer便进入了插件页面。
在这里插入图片描述

在插件页面中点击conflicts便清晰明了地看到插件已经把当前项目中存在的依赖冲突给列举出来了,在页面右侧以树形图的方式,展示了当前依赖在项目中的传递情况(传递路径从下往上越来越深),点击jump to source便可跳转到引用处,而选中依赖并右键可以很方便地将依赖在当前调用处直接exclude掉,这无疑大大简化了依赖冲突问题的排查。当然对于普通依赖,如果想查看它的依赖传递情况,点击 all dependencies as list/tree,插件以列表或者树形图方式展示指定依赖的传递路径。以上简要介绍了maven helper插件的使用,下面讲讲maven依赖原则。

二、依赖原则

2.1 最短路径原则

顾名思义,若在当前项目中同时传递依赖了两个不同版本的依赖,maven会自动选择传递路径较短的那个依赖版本。比如A-》V1版本的B,C-》D-》V2版本的B,这时maven会自动选择V1版本的B作为有效依赖。而在对组件升级架构后,启动项目后直接报如下错误。
在这里插入图片描述

不难看出,这是依赖冲突问题的典型表现,初步判断是spring-core版本冲突。首先在当前pom文件中已经规定了spring-core版本为5.1.18.RELEASE。
在这里插入图片描述
在借助maven helper插件在start启动模块的pom(对于maven多模块工程而言,启动模块所属的pom为所有pom的入口)中查看spring-core的传递依赖,发现其中较高版本的5.2.22.RELEASE spring-websocket依赖可能是冲突的罪魁祸首(由于升级框架而间接升级的)。
在这里插入图片描述
进一步查看,果不其然,该版本的spring-websocket依赖了5.2.22.RELEASE的spring-core。根据最短路径原则,相较于cfas-api-》cfas-service-》spring-websocket-》spring-core的依赖传递路径,显然maven会选择在当前项目中直接引用的5.1.18.RELEASE版本的spring-core。但是由于5.2.22.RELEASE的spring-core相较于5.1.18.RELEASE,某些类可能被替换或者删除了,而spring-websocket中依赖的是高版本的,这就间接导致项目运行报ClassNotFoundError异常。为此,在当前项目中指定spring-core版本为5.2.22.RELEASE后问题游刃而解。而当依赖传递路径长度相同时,根据是否在同一pom文件中分为以下俩种情况作为补充介绍。
在这里插入图片描述

2.2 覆盖原则

若在当前pom文件中先后引用了俩个不同版本的同一依赖,则后声明的会覆盖前面声明的。但这里要说严禁使用本情况,严禁在同一个pom中声明两个不同版本的依赖。比如当前项目中先声明了4.0 junit依赖,后面再增加3.8版本的并将其后声明,最后发现项目实际采用的依赖版本即为后声明的3.8版本。
在这里插入图片描述

2.3 最先声明原则

而对于不同pom文件,情况恰恰相反,项目中采用的有效依赖版本为先声明的。
如下helloworld2项目中,先后传递依赖了helloworld3-》1.2.2 commons-fileupload、helloworld-》1.3.3 commons-fileupload,在路径长度相同且存在于不同pom文件中的前提下,由于helloworld3是最先声明的,故最后生效的为helloworld3传递依赖的1.2.2 版本的commons-fileupload。
在这里插入图片描述

2.4 dependencyManagement

对于多工程项目,为了尽可能地减少依赖冲突的概率,可以使用父工程的pom.xml来声明管理公共的jar包依赖版本,这也就是maven中的继承。之所以引入继承,除了统一管理版本外,还为了避免传递依赖中对依赖scope的要求。如果是A-》B-》C,只有当B是compile范围时,A才依赖于C,这样的话每次A依赖C还得取决于B的作用范围。当引入继承后,只要A继承于B,对于B中的所有依赖A均能按需引用。子工程中通过parent标签便可以指定父工程,父工程中借助dependencyManagement标签便可限定某个依赖的版本,子工程中只需输入依赖的groupId和artifactId坐标即可,若是想覆盖父工程中的版本限制,再指定version就行了。
此外,如果子工程中没有在 中显示引用,是不会将jar包依赖进来的。但如果想继承多套pom文件,由于maven是单继承的,标签中只能包含一个父工程。这时就需要借助scope中的import范围了。通过在当前子工程中dependencyManagement内引入pom类型的范围为import的依赖,便可间接实现多继承。如下图所示,项目中其实就是引入了starfish-dependencies的pom中定义的所有dependency ,也即把starfish-dependencies依赖的jar包都引入进来。
在这里插入图片描述
在对组件升级架构时由于依赖冲突,对poi-ooxml依赖也连带升级了版本号,但是后面启动项目却报commons-io包中NoSuchMethodError错误。借助maven helper插件发现该依赖并未出现冲突,根据最短路径原则tika-core和poi-ooxml路径相对较短,路径长度相同时根据最先声明原则,项目应选取tika-core中的commons-io的版本。但是tika-core中相应版本号为2.11.0,不是真正选取的2.6版本。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
进一步观察发现,当前项目中通过dependencyManagement继承了starfish-dependencies工程,其中便限定了commons-io版本号为2.6,这也是当前项目实际选取的有效版本号。至此不难看出,dependencyManagement中指定的依赖的优先级高于传递依赖。
在这里插入图片描述
Maven官网中也对此进行了说明。若项目 pom文件中没有指定依赖的版本或是传递的依赖,在dependencyManagement中有指定此依赖版本,那就使其定义的版本号。故项目中通过直接指定commons-io版本号为poi-ooxml中传递依赖的版本,便成功解决了依赖冲突。
在这里插入图片描述

总结

在对项目进行依赖库扫描时若出现安全漏洞,最简单的解决方法便是升级相应的依赖版本到安全版本。但若是传递依赖出现漏洞,简单地将其exclude掉然后引入对应高版本后很容易导致依赖冲突,项目运行报错,故需要慎重考量。另外,对于构架升级这种情况而言,由于知道需要升级的依赖,同时借助maven helper插件和根据maven默认依赖原则,问题排查相对来说较为容易。对于依赖原则解释不清的,则往往是因为在子pom或者父pom中dependencyManagement限定导致的,其优先级是高于传递依赖。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值