作者 | 草捏子

来源 | 草捏子(ID:chaycao)

头图 |  CSDN 下载自东方IC

今年年初的时候,阅读过《Maven实战》,当时有了解到Maven可以依赖调解,即当包版本不一致时,会根据一定规则选择相应的包来加载,从而避免冲突。当时不解的是既然Maven都能解决冲突,为何还经常听到“发生了依赖冲突”,冲突不是解决了吗,还存在什么问题呢?直到这周在工作中自己遇到了,就明白是咋回事了。下面先从我的实际经历说起。

Maven依赖冲突经历

我在Y模块中,写了一个 Encryptor 类,主要是使用了 DigestUtils、MessageDigest、HmacUtils 等类对字符串进行加密(下面代码是随便写的,只表示使用到了这些类),如下:

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacUtils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class Encryptor {
public String encrype(String s) {MessageDigest sha256Digest = DigestUtils.getSha256Digest();
String result = Hex.encodeHexString(sha256Digest.digest(s.getBytes(StandardCharsets.UTF_8)));
return Hex.encodeHexString(HmacUtils.getHmacSha256(result.getBytes()).doFinal(result.getBytes()));}
public static void main(String[] args) {Encryptor encryptor = new Encryptor();
String s = "test";
String result = encryptor.encrype(s);System.out.println(result);}
/**output: fdd04dcac94e9803a72e4268141f773e2024a8fe46ba19a263be22c5ca83e931**/
}

执行单元测试可以正常运行。但是当整个应用启动时,则会报 IllegalAccessError 错误。

应用启动报错IllegalAccessError

在Y模块下的单元测试运行时不会报错,但是当整个应用启动,作为程序入口的X模块,调用Y模块中的 Encryptor 时,发生了 IllegalAccessError 报错。根据图中的具体报错信息,是说没有权限访问 getSha256Digest 方法,我Ctrl+B点进 getSha256Digest 方法查看,如下:

getSha256Digest是public

getSha256Digest 方法是 public 的访问级别,我一脸懵。由于这个方法很简单,既然报错,那我就索性不用了,换成下面这种写法。

public String encrype(String s) {try {MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");String result = Hex.encodeHexString(sha256Digest.digest(s.getBytes(StandardCharsets.UTF_8)));return Hex.encodeHexString(HmacUtils.getHmacSha256(result.getBytes()).doFinal(result.getBytes()));} catch (NoSuchAlgorithmException e) {e.printStackTrace();return "error";}
}

又报错了,好吧,真是躲不过了!报错如下:

应用启动报错ClassNotFoundException

这次报的是 ClassNotFoundException,HmacUtils 这个类找不到。可是我 Ctrl+B 进去,这个类好好的就在那里啊。这时我才把注意力集中在思考是不是发生了 Maven 依赖冲突。我打开 pom.xml,用 Dependency Analyzer 查看,果然我使用的 commons-codec 包发生了冲突。

X模块的依赖树

在Y模块中,依赖关系:Y -> B -> C -> commons-codec-1.10。而在X模块中,引用了A包:X -> A -> commons-codec-1.6,也引用了Y模块:X -> Y -> B -> C -> commons-codec-1.10。可见 commons-codec 包有两个版本1.6和1.10,所以Maven会进行依赖调解,第一原则是“路径最短者优先”,自然只会使用1.6版本的包。而我再去查看1.6的包下,getSha256Digest 方法是 private 的访问级别,HmacUtils 这个类也不存在。解释了之前的报错。解决该冲突,通过排除依赖便能解决了,将A包下的 commons-codec 排除,如下:

<dependencies>
<dependency>
<groupId>com.chaycao.maven.dependency</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>commons-codec</artifactId>
<groupId>commons-codec</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.chaycao.maven.dependency</groupId>
<artifactId>Y</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

排除后,这时将只有1.10版本的包,程序也可以正常运行了。

为什么需要Maven依赖调解

问题已经解决了,大家是不是也明白了,为什么依赖冲突会常导致发生 NoClassDefFoundError、NoSuchMethodException、IllegalAccessError 等错误。虽然Y模块在编译时,由于引入了 commons-codec 1.10 能正常编译,但是在运行时,由于依赖冲突,只加载了1.6版本的包,所以不能正常运行。

注意:代码的编译仅仅是编译当前的代码。编译成功后,最后能否正常运行,还要取决于运行时的环境是否等同或兼容编译时环境。

下面我们想想为什么需要 Maven 依赖调解,如果不调解行不行。

当使用 Maven 的过程中,如果同时引入了 groupId 和 artifactId 相同而 version 不同的包时,Maven 会认为发生了依赖冲突,将进行依赖调解,通过两个原则决定使用哪个版本的包:第一原则,路径最近者优先,如前文。如果路径相同,则使用第二原则,在 pom 中第一声明者优先。而当我们在点击 Run 运行时,classpath 中将只会有一个明确版本的包。

思考一下。Java 在运行时,是否能引入版本不同的包。其实这个问题是在问,java 命令的 classpath 参数中能不能有多个版本不同的包,当然是可以的。classpath 参数的是用于指示JVM如何搜索 class 文件,当你在 classpath 中指定的路径下有多个版本不同的包,JVM 都会去 jar 包下搜索 class 文件进行加载,而至于 class 能不能成功加载,则在于 ClassLoader 的逻辑,当同名类被加载时,则不会再被加载,即同一个类只会被加载一次。这也意味,当有多个版本不同的包时,包在 classpath 中的顺序,决定了哪个包中的类能先被加载。而这样具有不确定性。因为在生产环境下通常使用shell命令将 jar 包拼接:

LIB_DIR=lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"`

不同环境下得到的 jar 包顺序可能是不同的。而 Maven 依赖调解将使得只有一个明确版本的包参与构建,从而避免不确定性。

排查在线问题的利器-Arthas

Arthas,早有听说,但一直未使用过,这次我尝试了下,觉得确实可以,安利下。对于前文说的依赖冲突情况,当发生 IllegalAccessError 报错时,可以通过 Arthas 直接查看运行情况下的 DigestUtils。我们把代码变为最初的情况,且在 Main 类中加个死循环,为了让程序不死掉,以通过 Arthas 观察。

public class Main {
public static void main(String[] args) {
while (true) {
try {Encryptor encryptor = new Encryptor();String s = "1234567890";String result = encryptor.encrype(s);System.out.println(result);} catch (Throwable e) {}}}
}

打开 Arthas,连接上我们的程序(可以通过官方教程学习),然后通过sc命令查看 DigestUtils:

[arthas@32328]$ sc -d org.apache.commons.codec.digest.DigestUtils
class-info        org.apache.commons.codec.digest.DigestUtilscode-source       /D:/mavenrepo/commons-codec/commons-codec/1.6/commons-codec-1.6.jarname              org.apache.commons.codec.digest.DigestUtilsisInterface       falseisAnnotation      falseisEnum            falseisAnonymousClass  falseisArray           falseisLocalClass      falseisMemberClass     falseisPrimitive       falseisSynthetic       falsesimple-name       DigestUtilsmodifier          publicannotationinterfaces
super-class       +-java.lang.Object
class-loader      +-sun.misc.Launcher$AppClassLoader@58644d46+-sun.misc.Launcher$ExtClassLoader@24e74ca5classLoaderHash   58644d46

可以从 code-source 中清晰的查到 DigestUtils 是哪个包下的 Class,这时就该意识到发生了依赖冲突问题。

而通过 jad 命令,还能反编译,在线看代码。好用!

参考:

1、Arthas 实战,助你解决同名类依赖冲突问题

https://www.cnblogs.com/goodAndyxublog/p/12424734.html

2、Maven依赖冲突问题原理简析

https://blog.csdn.net/qq_27529917/article/details/79741607

3、重新看待Jar包冲突问题及解决方案

http://www.yangbing.club/2017/07/15/solution-for-jar-conflicts/

更多精彩推荐
☞倪光南、求伯君“出山”:爱解 Bug、无惧“35岁魔咒”、编码之路痛并快乐!
☞我坦白!我是第五位飞上太空的程序员游客
☞腾讯回应发布虚假广告被罚20万;苹果客服回应iPhone 12屏幕发绿;Chrome 87 正式版发布|极客头条
☞赠书 | 图像分类问题建模方案探索实践☞大神们都是如何在时间序列中进行特征提取的?看完就懂了!
☞Value DeFi遭黑客攻击始末,闪电贷这次又带走了700万美元
点分享点点赞点在看

Maven 依赖冲突踩坑后,将依赖调解、类加载彻底整明白了相关推荐

  1. 一次Maven依赖冲突采坑,把依赖调解、类加载彻底整明白了

    今年年初的时候,阅读过<Maven实战>,当时有了解到Maven可以依赖调解,即当包版本不一致时,会根据一定规则选择相应的包来加载,从而避免冲突.当时不解的是既然Maven都能解决冲突,为 ...

  2. SQLServer字段替换隐藏字符CHAR(0),踩坑后Get新技能

    大半夜的不由的想说一句:世界上本没有坑,挖的人多了,于是就有了坑.但踩的坑多了,想问题的角度也就改变了,一切都很值. 坑王驾到背景:用Kettle导SQLServer中的十几张表到PostgreSQL ...

  3. Mac上使用Docker Desktop启动Kubernetes,踩坑后终于搞掂

    1 前言 Kubernetes又简称k8s,是Google开源的容器集群管理系统,最近也是火热.闲来无事(为了发文),捣鼓了一下,在Mac上搭建Kubernetes,遇到一些坑,也记录一下. 另外,D ...

  4. VS配置FFmpeg踩坑后的成功版

    vs2019配置FFmpeg5.1 1.下载FFmpeg配置文件 这里的配置条件是:win7+vs2019+FFmpeg5.1 (ffmpeg版本应该没关系,下载最新就可以) 下载链接:https:/ ...

  5. Cocos2d-x-4.0安装流程(踩坑后总结)

    一.环境配置 1.Python 2:在官网下载2.7版本即可 安装时选择如图选项添加环境变量. win+R->cmd调出命令框输入python,结果如下则配置成功. 2.cmake:在官网下载最 ...

  6. maven依赖传递和排除依赖冲突

    1 依赖的传递 假如 A项目 依赖 a.jar 1.0.1,b.jar 1.0.1,没有直接依赖c.jar 1.0.1,但是b.jar 1.0.1依赖了c.jar 1.0.1,可以说A项目间接依赖了c ...

  7. 【黑马Java笔记+踩坑】Maven高级

    用于复习快速回顾. Maven基础: JavaWeb基础3--Maven&MyBatis_vincewm的博客-CSDN博客 目录 0,解除端口调用 1,分模块开发 1.1 分模块开发设计 1 ...

  8. 【Android Gradle 插件】Android 依赖管理 ⑥ ( 依赖冲突处理 | transitive 依赖传递设置 | exclude 依赖排除设置 | force 强制指定依赖库 )

    文章目录 一.查询 Android 依赖库的配置 二.通过 ModuleDependency#transitive 依赖传递设置 三.通过 ModuleDependency#exclude 设置排除子 ...

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

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

最新文章

  1. 收藏 | 精选11篇AI领域论文(附代码、数据集链接)
  2. SQL SERVER出现大量一致性错误的解决方法
  3. Linux上的ftp配置,及错误500 OOPS: could not bind listening IPv4 socket解决
  4. 全球及中国手机便携式移动电源行业营销模式及投资竞争力分析报告2021-2027年版
  5. Centos root权限的变化
  6. c++学习笔记之静态成员函数
  7. 学生信息链表,建立,插入,删除,遍历,查找,修改,最大(小)值,平均...
  8. javaweb利用servlet与struts2实现可点击刷新的基础图片验证码
  9. 我在互联网上买了很多课程,但是感觉在互联网学习没有什么效果很多买的课我都不想学了
  10. 程序设计习惯养成计划---二、测试代码
  11. win10下安装 迅雷精简版,提示阻止此应用
  12. wamp php 教程,WAMP 添加php新版本
  13. PS制作科幻特效的金色立体文字
  14. Selenium.Chrome.ChromeDriver指纹去除
  15. 拆解易鑫2020:担保服务收入猛增,助贷业务营收占比超三成
  16. 【数据库】聊一下数据库的锁机制
  17. 离散数学——范式(一)定义与求解
  18. 编写一个程序,计算学生的总分和平均成绩(一)
  19. AutoSAR系列讲解 - 交流专区
  20. kindEditor 富文本编辑器 使用介绍

热门文章

  1. 敏捷开发“松结对编程”系列之七:问题集之一
  2. java oracle的2种分页方法
  3. Collections集合工具类的方法
  4. Hashtable 为什么不叫 HashTable?
  5. 使用pt-query-digest进行日志分析
  6. jvm-内存区域与内存溢出异常
  7. 幸福就是有人爱、有事做、有所期待(转)
  8. 《那些年啊,那些事——一个程序员的奋斗史》——65
  9. SAS Viya应用简介
  10. Vmware安装CentOS7后访问不了外网