Maven中jar包冲突是令程序员常见比较头疼的问题。所以我们需要知道 jar包冲突的原理,才能更好的去解决jar包冲突的问题。本文将从jar包冲突的原理和解决两个方面阐述Maven中jar包冲突的解决办法。
一、Maven 包冲突常见问题
•问题1:Caused by:java.lang.NoSuchMethodError
这个错误表明试图调用一个不存在的方法。通常发生在编译时所依赖的类库与运行时所用的类库不匹配时。
•问题2:Caused by: java.lang.NoClassDefFoundError
类加载器试图加载类的定义,但是找不到这个类的定义,而实际上这个类文件是存在的。 发生NoClassDefFoundError错误,是因为JVM在编译时能找到合适的类,而在运行时不能找到合适的类,导致的错误。
•问题3:Caused by: java.lang.ClassNotFoundException
是一个在 Java 程序运行时出现的异常,表明 Java 虚拟机 (JVM) 无法找到某个类的定义。
以上三个问题都是运行时异常,大概率都是因为jar冲突导致,基于此首先需要了解什么是依赖传递。
二、依赖传递
在了解依赖传递之前,需要重新回顾一下maven 中dependency
中可以配置元素说明如下图
Maven 依赖传递是指,在项目中引入一个依赖,而这个依赖又依赖其他的依赖,Maven 会自动解析这些依赖并引入到项目中。依赖冲突是一把双刃剑,管理得当就可以在当前项目中直接使用被依赖项目的依赖库而不需要手动声明它们,如果管理不当就会导致依赖冲突问题。
所以我们需要深入了解一下maven内部是通过那些机制和规则进行依赖管理:
1、依赖调节
基于最短路径优先原则:Maven 会选择依赖树中最短路径的版本。例如:A、 B 和 C 的依赖关系定义为 A-> B-> C-> D 2.0和 A-> E-> D 1.0,然后在构建 A 时使用 D 1.0,因为从 A 到 D 到 E 的路径更短。
A
├── B
│ └── C
│ └── D 2.0
└── E
└── D 1.0
2、使用dependencyManagement
统一版本控制
dependencyManagement是一个机制,它可以让多个模块共享同一个依赖版本号,从而避免版本冲突和重复依赖的问题。当一个项目中有多个模块时,每个模块都可以声明自己的依赖,但是如果每个模块都声明了不同版本的同一个依赖,就会导致版本冲突和重复依赖的问题。为了解决这个问题,可以在父模块的pom.xml文件中使用dependencyManagement元素来声明依赖的版本号,然后在子模块中引用这个版本号即可。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>some.groupId</groupId>
<artifactId>A</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
使用dependencyManagement
的好处:
1)可以让项目中的所有模块都使用相同的依赖版本号,从而避免版本冲突和重复依赖的问题。
2)可以方便地升级依赖版本号,只需要修改父模块的pom.xml文件即可,而不需要修改每个子模块的pom.xml文件。
注意:maven判断依赖的最小单元是 groupId
+ artifactId
+ type
+ classifier
,但是引用type默认值为jar,classifier 默认值为null, 所以在不指定这两项值的情况下最小单元为 groupId
+ artifactId
。
3、依赖范围
Maven中的依赖范围(scope)用来控制依赖库在不同阶段的使用范围。Maven中定义了以下几种依赖范围:
•compile
:默认的依赖范围,表示依赖库在编译、测试、运行时都可用。
•provided
:表示依赖库在编译和测试时可用,但在运行时不可用,因为它会由JDK或容器提供。
•runtime
:表示依赖库在测试和运行时可用,但在编译时不可用。
•test
:表示依赖库只在测试时可用,不会被打包到最终的应用程序中。
•system
:表示依赖库在编译和测试时可用,但不会从Maven仓库中下载,需要手动指定路径。
•import
:表示依赖库的作用类似于<dependencyManagement>
元素,用来管理依赖库的版本号。
在pom.xml文件中,我们可以使用<scope>
元素来声明依赖库的范围,例如JUnit测试相关的依赖范围是<scope>runtime</scope>
,合理使用依赖范围可以避免不必要的依赖库加载,减少应用程序的体积,提高应用程序的性能。
4、显式排除特定传递依赖
排除依赖是使用 exclusions
元素实现的,该元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖。
注意:1)exclusions
中可以指定多个排除依赖;2)exclusion
中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖,无需指定版本 version。
<dependency>
<groupId>some.groupId</groupId>
<artifactId>A</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>some.groupId</groupId>
<artifactId>B</artifactId>
</exclusion>
</exclusions>
</dependency>
5、可选依赖 optional
依赖声明中使用optional
元素,将其设置成可选依赖,可选依赖用来控制当前依赖是否向下传递成为间接依赖。示例配置如下:
<dependencies>
<dependency>
<groupId>com.mytest.common</groupId>
<artifactId>test-zxy</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
</dependencies>
关于optional
元素及可选依赖说明如下:
•optional
默认值为false
,表示可以向下传递称为间接依赖;
•optional
取值为true
,则表示当前依赖不能向下传递成为间接依赖。
三、依赖冲突解决工具
1、maven 自带工具 dependency tree,构建依赖树分析jar包之间依赖关系。但缺点不够直观和便捷。
mvn dependency:tree
2、三方 IDE 冲突检测工具 maven helpler,优点是直观便捷可以快速定位冲突依赖。
检查到依赖冲突后我们可以根据以上学习的maven依赖管理知识,进行依赖管理调整,常用的调整方法是通过依赖配置相关冲突依赖。
四、实践问题分析
程序在运行日志中告警总出现 ClassNotFoundException异常,提示没有找到cglib下面的BeanMap$Generator内部类
1、解决思路:
- 通过maven helper工具进行排查检查jar冲突,但是并未发现cglib冲突
- 通过mvn dependency:tree 工具继续查看依赖树,过滤cglib后发现存在一个不同artifactId的依赖包,但是看包名可推断出是基于cglib二次加工的一个自定包。
2、解决方案
将冲突包进行手动排除
<dependency>
<groupId>some.groupId</groupId>
<artifactId>A</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>some.groupId</groupId>
<artifactId>B</artifactId>
</exclusion>
</exclusions>
</dependency>
参考资料:
•Maven – Introduction to the Dependency Mechanism
•Maven – Introduction to the Dependency Mechanism
•Solving Dependency Conflicts in Maven - DZone
打一波福利:
有需要内推JD的同学,可以私信或留言,我帮您内推,流程快!!!
有需要内推JD的同学,可以私信或留言,我帮您内推,流程快!!!
有需要内推JD的同学,可以私信或留言,我帮您内推,流程快!!!