用好这几个技巧,解决 Maven Jar 包冲突易如反掌 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
bryan31
V2EX    Java

用好这几个技巧,解决 Maven Jar 包冲突易如反掌

  •  
  •   bryan31 2020-08-31 10:36:49 +08:00 2655 次点击
    这是一个创建于 1868 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    大家在项目中肯定有碰到过Maven的 Jar 包冲突问题,经常出现的场景为:

    本地运行报NoSuchMethodErrorClassNotFoundException。明明在依赖里有这个 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

    1

    用这个插件能很好的显示出项目中所有的依赖树和冲突

    2

    这里面红色高亮的部分,就表明这个 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参数,要不然是不会显示被忽略的包的

    3

    其实 mvn 命令行一样好用。非常清晰明确。

    解决 Jar 包冲突的几个实用技巧

    排除法

    还是上面的那个例子,现在生效的是 2.5.0,如果想生效 4.0.1 。只需要在 2.5.0 上面点exclude就行了。

    4

    版本锁定法

    如果很多个依赖都传递了 Jar 包 A,涉及了很多个版本,但是你只想指定一个版本。用排除法一个个去exclude太麻烦,而且exclude在 pom 文件中也会体现,太多的话,也影响代码整洁和阅读感受。

    这时候需要用到版本锁定法

    何谓版本锁定法?公司的项目一般都会有父级 pom,你想指定哪个版本只需要在你项目的父 POM 中(当然在本工程内也可以)定义如下:(还是举上个例子,指定 4.0.1 版本)

    <dependencyManagement> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-client</artifactId> <version>4.0.1</version> </dependency> </dependencyManagement> 

    锁定版本法可以打破 2 个依赖传递的原则,优先级为最高

    锁定版本后,依赖树为:

    5

    都统一变成 4.0.1,锁定版本有一个好处:版本锁定并不排除 Jar 包,而且显示的把所有版本不一致的 Jar 包变成统一一个版本,这样在阅读代码时比较友好。也不用忍受一大堆的exclude标签。

    如何写一个干净依赖关系的POM文件

    我本人是有些轻度代码洁癖的人,所以即便是 pom 文件的依赖关系也想干净而整洁。如何写好干净的 POM 呢,作者认为有几点技巧要注意:

    • 尽量在父 POM 中定义<dependencyManagement>,来进行本项目一些依赖版本的管理,这样可以从很大程度上解决一定的冲突
    • 如果是提供给别人依赖的 Jar 包,尽可能不要传递依赖不必要的 Jar 包
    • 使用mvn dependency:analyze-only命令用于检测那些声明了但是没被使用的依赖,如有有一些是你自己声明的,那尽量去掉
    • 使用mvn dependency:analyze-duplicate命令用来分析重复定义的依赖,清理那些重复定义的依赖

    最后

    其实庞大的项目依赖传递也一定多。但是不管多复杂的依赖关系,看到不要害怕。就这么几条原则,细心的去分析,所有的依赖都有迹可循。

    这些传递依赖如果管理的好,能让你的维护成本大大降低。如果管不好,这群野孩子每一个都可能是引发下一个NoSuchMethodError的导火索。

    关注作者

    觉得有用的话,请关注下我的公众号「元人部落」,作者坚持原创的内容技术分享,也有开源作品,欢迎关注

    开源仓库为: https://gitee.com/bryan31

    公众号一般周更,每次会分享一些实用的技术,陪你一起成长

    关注后回复“资料”领取 50G 的视频资料,包括一套企业级微服务的视频教学

    img

    6 条回复    2020-09-02 16:35:08 +08:00
    sonice
        1
    sonice  
       2020-08-31 12:00:05 +08:00
    有用,感谢
    shenmimu
        2
    shenmimu  
       2020-08-31 19:23:18 +08:00   1
    还有可能会有 不同 jar 之内的同名类冲突
    分析和排除依赖通常是在事后,最佳实践应该是使用 maven-enforcer-plugin 检查冲突,在引入时避免
    wybhdxfx
        3
    wybhdxfx  
       2020-09-01 00:34:02 +08:00
    感谢,学习了。
    yumenawei
        4
    yumenawei  
       2020-09-01 09:22:20 +08:00
    感谢分享
    cheng6563
        5
    cheng6563  
       2020-09-01 09:32:42 +08:00
    感觉还是 gradle 那种高版本优先好用些
    cnzjl
        6
    cnzjl  
       2020-09-02 16:35:08 +08:00
    感谢分享,收藏了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2558 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 05:01 PVG 13:01 LAX 22:01 JFK 01:01
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86