0x00. 前言

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

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

例子 1:

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

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

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

例子 2:

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

A------->Dubbo------->javassist-3.18.1.GA

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

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

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

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

  • 项目同一依赖应用,存在多版本,每个版本同一个类,可能存在差异。
  • 项目不同依赖应用,存在包名,类名完全一样的类。

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

0x01. 依赖冲突原因

1.1 依赖机制

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

1.2 仲裁机制

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

  • 优先按照依赖管理元素中指定的版本声明进行仲裁时,下面的两个原则都无效了
  • 短路径优先
  • 若路径相同,将看 pom 中声明的顺序。

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

第二条原则,如下图:

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

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

1.3 scope 属性

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

Maven 提供 6 种 scope

  • compile
  • provided
  • runtime
  • test
  • system
  • import

compile

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

provided

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

A----->B----->C

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

runtime

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

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

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

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>6.0.6</version><scope>runtime</scope>
</dependency>

知识点:这个好处在于,只能使用 JDBC 标准接口,这样就不会与特定的数据库绑定。后续若切换数据库,只需要更换 pom,然后修改相应的参数即可。

test

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

<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope>
</dependency>

system

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

import

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

    <dependencyManagement><dependencies><!-- Spring Boot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.6.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

知识点:通过这种方式,解决单继承问题,也可以更好将依赖分类。

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

如果依赖关系为: A—>B—>C,A 依赖 B,B 依赖 C。最左列代表 B 的 scope 属性,第一行代表 C 的 scope 属性

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

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

0x02. 解决冲突的方法

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

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

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

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

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

这个适合公共基础包,其他包不要随便使用provided,若使用一定要写清楚,使用过程中需要引入的依赖。

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

  1. 应用项目中使用 dependencyManagement 统一管理基础依赖,定义统一的版本,如常用中间包,工具包,日志包。
  2. 二方包中不要引入无关的依赖,做到尽量少的依赖。团队开发中,比较常见情况是二方包继承公共的父 pom,从而导致继承许多无相关的依赖,这种情况可以单独管理。
  3. 二方包做好向下兼容,不要随意改动现有类名,方法名,字段名。
  4. 项目应用上线之前,将 snapshot 替换成正式版本。虽然 snapshot 修改起来很方便,但是正因为这个特性,可以被随便修改。如果某次生产打包发布不注意,就会引入。
  5. 二方包不要使用同一个包名,类名。一般来说,团队开发中,包名,类名一样概率比较小。这种比较容易出现在一些重构项目,复制原来类,重构打包发布。对于情况下可以修改包名。如 cmomon-lang3common-lang 升级版, cmomon-lang3 包名为 org.apache.commons.lang3,而 common-lang 包名为 org.apache.commons.lang

0x03. 总结

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

ps:塞尔达中,你们第一次遇见人马,打了几次?小黑哥记得那天整整从晚上九点打到凌晨两点,就是打不过啊~

最后用一张思维导图,总结文章内容。

0x04. 帮助文档

Maven Dependency Scopes

Maven optional关键字透彻图解

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

包管理原则

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn

程序员需要了解依赖冲突的原因以及解决方案相关推荐

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

    前言 依赖冲突是日常开发中经常碰到的过程,如果运气好,并不会有什么问题.偏偏阿粉有点背,碰到好几次生产问题,排查一整晚,最后发现却是依赖冲突的引起的问题. 没碰到过这个问题同学可能没什么感觉,阿粉举两 ...

  2. 程序员找不到女朋友的原因,脱单看这里!

    程序员找不到女朋友的原因 程序员问禅师:"大师,我身体健康,思想端正,各方面都不错,为何就是找不到女朋友呢?" 禅师浅笑,答:"原因很简单,不过若想我告诉你,你需先写一段 ...

  3. scanf_s 发送访问冲突_程序员如何解决并发冲突的难题?

    作者 | 羽生结弦 责编 | 胡雪蕊 出品 | CSDN(ID: CSDNnews) 在大多数的应用中都会出现客户端同时发送多个请求对同一条数据就行修改,这个时候就会出现并发冲突.我们一般的做法会有如 ...

  4. 程序员如何解决并发冲突的难题?

    作者 | 羽生结弦 责编 | 胡雪蕊 出品 | CSDN(ID: CSDNnews) 在大多数的应用中都会出现客户端同时发送多个请求对同一条数据就行修改,这个时候就会出现并发冲突.我们一般的做法会有如 ...

  5. 程序员“不会”修电脑的原因

    老生长谈的问题,程序员会不会修电脑.(修电脑指一般简单软件安装,硬件组装) 程序员应该会,为什么,因为计算机专业基础课程有一门是计算机组成原理,学完也对计算机有些一些基本的了解吧.从硬件来看,简单一些 ...

  6. Java程序员进阶——Spring依赖注入原理分析

    Spring依赖注入原理分析 下面谈谈Spring是如何实现反转模式IOC或依赖注入模式DI: 平时,我们需要生成一个对象,使用new语法,如一个类为A public class A{public v ...

  7. Java程序员面试失败的5大原因

    也许这5点原因适用于所有的程序员 1.说得太少 尤其是那些开放式的问题,如"请介绍下你自己"或"请讲一下你曾经解决过的复杂问题".面试官会通过你对这些技术和非技 ...

  8. OSChina 周三乱弹 ——程序员下班后总是不关电脑原因竟然是这样

    2019独角兽企业重金招聘Python工程师标准>>> Osc乱弹歌单(2017)请戳(这里) [今日歌曲] @penny-osc:@小小编辑 分享林宥嘉的单曲<自然醒> ...

  9. 好家伙!程序员找不到女朋友的原因

    程序员问禅师:"大师,我身体健康,思想端正,各方面都不错,为何就是找不到女朋友呢?" 禅师浅笑,答:"原因很简单,不过若想我告诉你,你需先写一段java代码." ...

  10. 身在职场,让程序员跳槽的那些非钱原因!未来的路该怎么走?

    猪队友 有些领导没办法识别团队中坏了一锅汤的那粒"老鼠屎",这会让兢兢业业勤勉上进的其他成员感到不公. 不怕神一样的对手,就怕猪一样的队友. 绝望的产品 当你遇到这样的产品:方向不 ...

最新文章

  1. python学习点滴记录-Day10-线程
  2. Day12-BigDecimal笔记及练习
  3. nginx 内置变量大全(转)
  4. Servlet CDI Example Analysis
  5. html语言中div的用法,htmldiv标签怎么用
  6. 综述 | 知识图谱技术综述(上)
  7. ML Case Studies(0)
  8. javascript 阮一峰入门教程
  9. mysql新建数据库并执行sql文件
  10. 2021第十届小美赛-“认证杯”数学中国数学建模国际赛
  11. Thinkphp聚合支付整合第三方和四方支付系统带代理功能
  12. 江苏大学数字图像处理MATLAB人脸识别,MATLAB人脸识别代码
  13. android音视频播放技术背景,Android音视频开发-入门(一)
  14. Ubuntu内核版本升级
  15. windows通知栏中显示 微信等应用软件 的通知
  16. 【Unity VR开发窍门】如何在Unity中以VR视角捕捉游戏360度全景
  17. ModuleNotFoundError: No module named sklearn
  18. 啊哈C——学习2.4一起来找茬
  19. (iptables)火墙策略读取的先后顺序 + 引入数据包状态
  20. 判断请求是否来自微信端

热门文章

  1. Find n‘th number in a number system with only 3 and 4
  2. jquery中防止和其他JS框架冲突的办法
  3. GIST特征描述符使用
  4. 关于python的论文the philosophy_Philosophy in Python
  5. 拓端tecdat|用R语言软件估计光谱密度
  6. 电话聊天狂人 (25 分)(map映射 简单做法)
  7. 希利苏斯起始任务_魔兽世界希利苏斯巨剑任务线开启 7.3.5版本更新攻略为艾泽拉斯而战...
  8. layui登录源码_基于springboot+thymeleaf+mysql+layui实现的论坛系统
  9. python函数传入对象
  10. pytorch def __init__(self, num_classes, bkg_label, top_k, conf_thresh, nms_thresh):