程序员需要了解依赖冲突的原因以及解决办法-LMLPHP

0x00. 前言

依赖冲突是日常开发中经常碰到的过程,如果运气好,并不会有什么问题。偏偏阿粉有点背,碰到好几次生产问题,排查一整晚,最后发现却是依赖冲突的引起的问题。

没碰到过这个问题同学可能没什么感觉,阿粉举两个最近碰到例子,让大家感受一些。

例子 1:

我们公司有个古老的业务基础包 A。B,C 业务依赖这个包。某个团队拷贝 A 的部分代码进行重构,类名与路径完全一样,然后重新打包成 D 发布。

一次业务改动,B 业务也引入了 D 包,测试环境运行的时候,一切 OK,但是在生产运行时,却抛出 NoSuchMethodError

问题原因在于 B 业务依赖 A,D。而 A,D 存在两个同包同名类,运行的时候,具体加载谁,不同环境还真不一样。

例子 2:

A 业务使用 Dubbo 进行 RPC调用, Dubbo 需要依赖 javassist。当前依赖关系为:

某次改动中引入另外一个第三方开源包,其依赖 javassist-3.15.0-GA 。生产发布的时候,将 javassist-3.15.0-GA 打包到应用中,由于生产环节为 JDK1.8,从而导致运行直接失败。

除了上述问题,依赖冲突还可能导致应用抛出 ClassNotFoundExceptionNoClassDefFoundError 等错误。

抛出错误这种情况还算好,还比较容易定位问题。怕就怕,不同版本同一个类内部逻辑不同,从而导致业务异常。这种问题,真的很让人抓狂,让人头秃。

程序员需要了解依赖冲突的原因以及解决办法-LMLPHP


仔细分析依赖冲突,主要可以分为两类:

下面我们分析一下依赖冲突产生的原因。

0x01. 依赖冲突原因

1.1 依赖机制

Maven 依赖分为两种情况,直接依赖与间接依赖,这个比较好理解,大家直接看图就好。


程序员需要了解依赖冲突的原因以及解决办法-LMLPHP


1.2 仲裁机制

如果 A 应用间接依赖多个 C 应用,且版本都不一样,Maven 将会通过仲裁机制选择:

第一条原则,我们下面再说。

第二条原则,如下图:

程序员需要了解依赖冲突的原因以及解决办法-LMLPHP


A 间接依赖两个版本 E,这种情况下,由于 A 到 E-1.0 路径最短,所以 A 中将会使用 E-1.0。

如果路径恰好一样,那么这种情况下 Maven 只能根据 pom 中的顺序,选择最先声明的,这也是个无奈的选择。

1.3 scope 属性

Maven 项目可以分为三个阶段:编译阶段,测试阶段,运行阶段了。通过 scope 属性,我们可以决定依赖应用是否参与以上阶段,也将会影响依赖传递。

Maven 提供 6 种 scope :

compile

compile 是 Maven 默认属性,将会使依赖包参与项目的编译,测试,运行阶段。当然,项目打包之后将会包含该依赖。

provided

provided 意味着依赖仅参与项目编译,测试的阶段。若有如下依赖关系:

C 的 scope 为provided,C 将会参与 B 的编译,测试阶段,但是 C 不会传递给 A。如果 A 运行过程需要 C,需要自己直接引入 C 依赖。典型如 Servlet API,因为 Tomcat 等容器内部会提供。

runtime

runtime 代表依赖不再参与项目编译阶段,只参与测试,运行阶段。

若依赖不参与编译阶段,这种情况 IDE 中是无法导入相应的类的。若存在依赖类,编译过程中将会报错。

典型的例子是 JDBC 驱动包,如 mysql :

test

test 仅参与测试阶段的工作,典型的例子为 junit

system

system 与 provided 范围一致,只不过 system 需要使用 systemPath 属性指定本地路径,而 provided 将会从 Maven 仓库拉取。

import

import 比较特殊,不会参与以上阶段运行。其只能在 dependencyManagement下使用,且 type需要为 pom。典型的例子为 Spring-boot 依赖。

另外 Maven scope 将会影响依赖传递。

程序员需要了解依赖冲突的原因以及解决办法-LMLPHP

如上所示,当 C 的 scope 为 provided/test, C 只在 B 中起作用,不会通过间接依赖传递给 A。

当且仅当 B 的 scope 为 compile,且 C scope 为 runtime ,A 将会间接依赖 C,且 scope 为 runtime。其他情况下,C 的 scope 将会与 B 的 scope 一致。

0x02. 解决冲突的方法

2.1 使用 Maven 属性控制依赖传递

依赖冲突时,根据错误日志,定位到冲突类,定位相应 jar 包,最后通过 excludes 排除相应的包。

另外可以结合 IDEA Maven Helper 插件,主动检查冲突依赖,提前排除。

程序员需要了解依赖冲突的原因以及解决办法-LMLPHP

通过插件,我们可以清晰看到冲突包,以及依赖路径,还有相应的 Scope

除了排除依赖,我们可以通过合理的设置 scope 属性,不让依赖传播下去。比如说,A 需要是使用 Spring-beans 包中某些类。如果其他项目铁定会使用 Spring,那么我们可以将 A 中 Spring-beansscope 设置为 provided,让其他项目自己选择引入 Spring-beans 的版本。

以上方法虽然治标,但是不治本。如果想依赖冲突不发生,我们需要提前建立一定的规范,团队一起遵守,才能有效避免该类问题。

0x03. 总结

如果我们把 NPE 问题当做新手村普通怪物,那么依赖冲突问题就是人马这种精英怪。刚开始遇到,我们会被虐的比较惨。只有我们不断升级,学习掌握技巧,然后才能可以从容不迫解决。

程序员需要了解依赖冲突的原因以及解决办法-LMLPHP

0x04. 帮助文档

Maven Dependency Scopes

Maven optional关键字透彻图解

这篇文章写的很好,大家可以看下。,重新看待Jar包冲突问题及解决方案

包管理原则

精彩回顾:

学渣阿粉的首次阿里面试之路

阿粉带你学习设计模式之原型(Prototype)模式

因新型肺炎,多国对中国入境人员实施管制,网友猛批!

最最最实在的干货内容,整合一起发给大家

    疫情下,B类人怎么样啦?

< END >


如果大家喜欢我们的文章,欢迎大家转发,点击在看让更多的人看到。也欢迎大家热爱技术和学习的朋友加入的我们的知识星球当中,我们共同成长,进步。我们的星球即将涨价,目前加入还能赶上最后一波最低价,过时不候!

程序员需要了解依赖冲突的原因以及解决办法-LMLPHP

本文分享自微信公众号 - Java极客技术(Javageektech)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

03-16 12:28