用好这几个技巧,解决 Maven-Jar 包冲突易如反掌
大家在项目中肯定有碰到过Maven
的 Jar 包冲突问题,经常出现的场景为:
本地运行报NoSuchMethodError
,ClassNotFoundException
。明明在依赖里有这个 Jar 包啊。怎么运行不了!?
项目中明明定义着某个 jar 包版本为2.0.2
,怎么打包之后变成2.5.0
了!?
A 项目引 xxx.jar 包运行好好的,B 项目同样引入 xxx.jar 后,运行报错了。。是 B 项目有问题,还是 xxx.jar 包有问题!?
本地环境和测试环境运行的好好的,到了生产就报一堆NoSuchMethodError
,是我人品有问题还是生产环境有问题!?
这样的问题如果不熟悉maven
依赖机制的同学排查起来,估计挺头痛的。
而且maven
依赖结构不好的项目,在引入新的 Jar 包时的风险也是巨大的。小则影响性能,大则引起生产发布和运行时异常。
其实以上问题的根源都来自于Maven
的 Jar 包冲突和使用不当的依赖传递。这篇文章我就好好分析下以下 3 个内容:
依赖传递的原则和产生 Jar 包冲突的原理分析
定位冲突以及解决 Jar 包冲突的几个简单技巧
如何写一个干净依赖关系的
POM
文件
依赖传递原则
几乎所有的
Jar 包冲突都和依赖传递原则有关,所以我们先说Maven
中的依赖传递原则:
最短路径优先原则
假如引入了 2 个 Jar 包 A 和 B,都传递依赖了 Z 这个 Jar 包:
A -> X -> Y -> Z(2.5)
B -> X -> Z(2.0)
那其实最终生效的是 Z(2.0)这个版本。因为他的路径更加短。如果我本地引用了 Z(3.0)的包,那生效的就是 3.0 的版本。一样的道理。
最先声明优先原则
如果路径长短一样,优先选最先声明的那个。
A -> Z(3.0)
B -> Z(2.5)
这里 A 最先声明,所以传递过来的 Z 选择用 3.0 版本的。
Jar 包冲突的原理
假设我们项目中依赖了 A 和 B 两个 Jar 包。而 A 和 B 各自又有以下传递依赖
A -> X -> Z(2.0)
B -> X -> Y -> Z(2.5)
那最终系统中 Z 包就产生了冲突,2.0 和 2.5 两个版本冲突。但是 classpath 中只会依赖一个版本的 Z 包。根据传递依赖的最短路径优先原则,最终依赖的应该是 2.0 版本。
如果 Y 包中用了 Z 包 2.5 版本中新的 method 时候,当运行到这段逻辑的时候。就会报NoSuchMethodError
了。因为本来依赖的是 2.5 版本,但是因为 Jar 包冲突Maven
选择了 2.0 版本,2.0 版本中又没有这个新的 method,导致出错。
但要注意的是,不是所有冲突都会引起运行异常。相反,大部分公司的项目都会有一些 Jar 包冲突,但其实没有造成运行时的问题。
这是因为很多传递依赖的 Jar 包,不管是 2.0 版本也好,2.5 版本也好,都可以运行。
只有高版本 Jar 包不向下兼容,或者新增了某些低版本没有的 API 才有可能导致这样的问题
定位冲突
IDEA 提供了一个maven
依赖分析神器:Maven Helper
用这个插件能很好的显示出项目中所有的依赖树和冲突
这里面红色高亮的部分,就表明这个 Jar 包有了冲突。选中这个 jar 包,可以看到这 2 个版本的冲突的来源。
上图的例子,表明cruator-client
这个 Jar 包,有 2 个传递依赖,分别为 2.5.0 版本和 4.0.1 版本。冲突的描述为:
omitted for conflict with 2.5.0. 由于与 2.5.0 版本冲突而被省略
具体的层级在右边也一目了然了,所以maven
最终根据最短路径优先原则选择了 2.5.0 版本,4.0.1 版本被忽略。
这时候有同学会问:本地环境我可以利用Maven Helper
来定位,那么预生产或者生产环境呢。又没有 IDEA,如何定位冲突的细节?
可以利用 mvn 命令来解决:
mvn dependency:tree -Dverbose
此处一定不要省略
-Dverbose
参数,要不然是不会显示被忽略的包的
其实 mvn 命令行一样好用。非常清晰明确。
解决 Jar 包冲突的几个实用技巧
排除法
还是上面的那个例子,现在生效的是 2.5.0,如果想生效 4.0.1。只需要在 2.5.0 上面点exclude
就行了。
版本锁定法
如果很多个依赖都传递了 Jar 包 A,涉及了很多个版本,但是你只想指定一个版本。用排除法一个个去exclude
太麻烦,而且exclude
在 pom 文件中也会体现,太多的话,也影响代码整洁和阅读感受。
这时候需要用到版本锁定法
何谓版本锁定法?公司的项目一般都会有父级 pom,你想指定哪个版本只需要在你项目的父 POM 中(当然在本工程内也可以)定义如下:(还是举上个例子,指定 4.0.1 版本)
评论