Maven Jar包冲突?看看高手是怎么解决的

简介: Maven Jar包冲突?看看高手是怎么解决的

接手了一套比较有年代感的系统,计划把重构及遇到的问题写成系列文章,老树发新枝,重温一些实战技术,分享给大家。【重构02篇】:Maven项目Jar包管理机制、冲突解决。


知识背景

Jar包冲突在软件开发过程中是不可避免的,因此,如何快速定位冲突源,理解冲突导致的过程及底层原理,是每个程序员的必修课。也是提升工作效率、应对面试、在团队中脱颖而出的机会。


实践中能够直观感受到的Jar包冲突表现往往有这几种:


程序抛出java.lang.ClassNotFoundException异常;

程序抛出java.lang.NoSuchMethodError异常;

程序抛出java.lang.NoClassDefFoundError异常;

程序抛出java.lang.LinkageError异常等;

这是能够直观呈现的,当然还有隐性的异常,比如程序执行结果与预期不符等。下面,我们就分析一下Maven项目中Jar包的处理机制及引起冲突的原因。


Maven Jar包管理机制

在Maven项目中,想要了解Jar冲突必须得先了解一下Maven是如何管理的Jar包的。这涉及到Maven的一些特性,比如依赖传递、最短路径优先原则、最先声明原则等。


依赖传递原则

当在Maven项目中引入A的依赖,A的依赖通常又会引入B的jar包,B可能还会引入C的jar包。这样,当你在pom.xml文件中添加了A的依赖,Maven会自动的帮你把所有相关的依赖都添加进来。


比如,在Spring Boot项中,当引入了spring-boot-starter-web:


<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-web</artifactId>

</dependency>

1

2

3

4

此时Maven的依赖结构可能是这样的:


image.png上面这种关系,我们就可以理解为依赖的传递性。即当一个依赖需要另外一个依赖支撑时,Maven会帮我们把相应的依赖依次添加到项目当中。


这样的好处是,使用起来就非常方便,不用自己挨个去找依赖Jar包了。坏处是会引起Jar包冲突,我们后面会讲到。


最短路径优先原则

依赖链路一:主要根据依赖的路径长短来决定引入哪个依赖(两个冲突的依赖)。


举例说明:


依赖链路一:A -> X -> Y -> Z(21.0)

依赖链路二:B -> Q -> Z(20.0)

1

2

项目中同时引入了A和B两个依赖,它们间接都引入了Z依赖,但由于B的依赖链路比较短,因此最终生效的是Z(20.0)版本。这就是最短路径优先原则。


此时如果Z的21.0版本和20.0版本区别较大,那么就会发生Jar包冲突的表现。


最先声明优先原则

如果两个依赖的路径一样,最短路径优先原则是无法进行判断的,此时需要使用最先声明优先原则,也就是说,谁的声明在前则优先选择。


举例说明:


依赖链路一:A -> X -> Z(21.0)

依赖链路二:B -> Q -> Z(20.0)

1

2

A和B最终都依赖Z,此时A的声明(pom中引入的顺序)优先于B,则针对冲突的Z会优先引入Z(21.0)。


如果Z(21.0)向下兼容Z(20.0),则不会出现Jar包冲突问题。但如果将B声明放前面,则有可能会发生Jar包冲突。


Jar包冲突产生的原因

上面讲了Maven维护Jar包的三个原则,其实每个原则会发生什么样的Jar包冲突,已经大概了解了。这里再来一个综合示例。


举例说明:


依赖链路一:A -> B -> C -> G21(guava 21.0)

依赖链路二:D -> F -> G20(guava 20.0)

1

2

假设项目中同时引入了A和D的依赖,按照依赖传递机制和默认依赖调节机制(第一:路径最近者优先;第二:第一声明优先),默认会引入G20版本的Jar包,而G21的Jar包不会被引用。


如果C中的方法使用了G21版本中的某个新方法(或类),由于Maven的处理,导致G21并未被引入。此时,程序在调用对应类时便会抛出ClassNotFoundException异常,调用对应方法时便会抛出NoSuchMethodError异常。


排查定位Jar包冲突

在高版本的IDEA中已经自带了Maven依赖管理插件,依次执行:打开pom.xml文件,在文件内右击,选择Maven,选择Show Dependencies即可查看Maven的依赖层级结构:


image.png执行之后展示的效果便是最开始的spring-boot-web那样效果,在图中可以清楚的看到都使用了哪些依赖,它们的层级,是否有冲突的jar包等。冲突部分会用红色标出,同时标出Maven默认选择了哪个版本。


如果你的IDEA版本中默认没有Maven管理插件,也可安装Maven Helper,通过这块插件来帮你分析Jar包冲突。


image.png安装完插件,重启之后,打开pom.xml文件,在文件下面的Dependency Analyzer视图中便可以看到Jar包冲突的结果分析:

image.png此时,关于哪些Jar包冲突了,便一目了然。同时,可以右击冲突的Jar包,执行”Exclude“进行排除,在pom.xml中便会自动添加排除jar包的属性。


对于本地环境可以利用Maven Helper等插件来解决,但在预生产或生成环境中就没那么方便了。此时可以通过mvn命令来定位突出的细节。


执行如下mvn命令:


mvn dependency:tree -Dverbose

1

注意不要省略-Dverbose,要不然不会显示被忽略的包。


执行结果如下:



image.png通过这种形式,也可以清晰的看出哪些Jar包发生了冲突。


如何统一Jar包依赖

像上面截图所示,如果一个项目有N多个子项目构成,项目之间可能还有依赖关系,Jar包冲突不可避免,此时可采用父pom,统一对版本进行管理,一劳永逸。


通常做法,是在parent模块的pom文件中尽可能地声明所有相关依赖Jar包的版本,并在子pom中简单引用(不再指定版本)该构件即可。


比如在父pom.xml中定义Lombok的版本:


<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
    </dependencies>
</dependencyManagement>

在子module中便可定义如下:

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

通过这种方式,所有的子module中都采用了统一的版本。


解决Jar包冲突的方法

这里基于Maven项目介绍几种场景下解决Jar冲突的方法:


Maven默认处理:采用此种方法,要牢记Maven依赖调节机制的基本原则,路径最近者优先和第一声明优先;

排除法:上面Maven Helper的实例中已经讲到,可以将冲突的Jar包在pom.xml中通过exclude来进行排除;

版本锁定法:如果项目中依赖同一Jar包的很多版本,一个个排除非常麻烦,此时可用版本锁定法,即直接明确引入指定版本的依赖。根据前面介绍Maven处理Jar包基本原则,此种方式的优先级最高。这种方法一般采用上面我们讲到的如何统一Jar包依赖的方式。

Jar包冲突的本质

上面讲了Maven对项目中Jar包冲突的解决原则和实战层面的解决方案,但并未涉及到Jar包冲突的本质。关于Jar包冲突的本质在《从Jar包冲突搞到类加载机制,就是这么霸气》一文中已经进行详细的讲解了。这里再针对其中的几个关键点进行概述一下。


Jar包冲突的本质:Java应用程序因某种因素,加载不到正确的类而导致其行为跟预期不一致。


具体分两种情况:


情况一:项目依赖了同一Jar包的多个版本,并且选错了版本;

情况二:同样的类在不同的Jar包中出现,导致JVM加载了错误的类;

情况一,也是本文重点讨论的场景,也就是说引入了多个Jar包版本,不同的Jar包版本有不同的类和方法。由于(不懂)Maven依赖树的仲裁机制导致Maven加载了错误的Jar包,从而导致Jar包冲突;


情况二,同一类在不同的Jar包中出现(上篇文章中有详细描述)。这种情况是由于JVM的同一个类加载器对于同一个类只会加载一次,现在加载一个类之后,同全限定名的类便不会进行加载,从而出现Jar包冲突的问题。


针对第二种情况,如果不是类冲突抛出了异常,你可能根本意识不到,所以就显得更为棘手。这种情况就可以采用前文所述的通过分析不同类加载器的优先级及加载路径、文件系统的文件加载顺序等进行调整来解决。


小结

除了上述的方法,还很多小技巧来排查类冲突,比如通过IDE提供的搜索功能,直接搜索抛异常的类,看看是否存在多个,是否使用的是预期的版本等。这些技巧需要在实践的过程中不断的摸索和积累。


总之,无论项目多么庞大,依赖多么复杂,只要牢记导致冲突的原因,及解决冲突的几个方式,细心分析,总会有迹可循的。看完这篇文章,实践一下,你可能就会在团队中脱颖而出,成为Jar包冲突终结者。



目录
相关文章
|
开发者
这款酷似飞书文档的工具开源了,支持私有部署!!!
这个酷似飞书文档的工具真的非常好用,体验和飞书差不多,同样支持 Markdown 语法,经常使用 Markdown 写文档的同学一定要试试了,相信你一定会喜欢上它,这个工具是我用差不多20天左右的时间开发的,而且它的前后端项目现在都已经开源了,如果你能帮我推广那我更是感激不尽。所以,如果你想有一个可以私有部署的文档工具,而且体验还特别棒的的,那就快来试试这个吧,相信它不会让你失望的。
1812 0
|
2月前
|
机器学习/深度学习 人工智能 数据可视化
基于YOLOv8的100种中药分类识别项目|完整源码数据集+PyQt5界面+完整训练流程+开箱即用!
本项目基于YOLOv8实现100种中药材分类识别,配备完整数据集、训练代码与PyQt5图形界面,支持图片、视频及摄像头检测,提供开箱即用的中药智能识别系统。
基于YOLOv8的100种中药分类识别项目|完整源码数据集+PyQt5界面+完整训练流程+开箱即用!
|
2月前
|
机器学习/深度学习 JSON 并行计算
10分钟微调,让0.6B模型媲美235B模型!免费体验进行中
本方案介绍如何通过模型蒸馏技术,利用大参数模型生成数据并微调小参数模型(如 Qwen3-0.6B),使其在特定任务(如从一句话中提取结构化信息)中达到接近大模型的效果。通过 GPU 云服务器进行高效微调,结合魔搭社区的 ms-swift 框架,用户可快速完成模型训练与部署,显著提升推理速度并降低成本。方案包含详细步骤:数据准备、模型微调、效果验证及部署建议,并提供免费试用资源,助力开发者快速上手实践。
10分钟微调,让0.6B模型媲美235B模型!免费体验进行中
|
11月前
|
运维 安全 网络性能优化
|
12月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理
|
存储 Kubernetes 负载均衡
深入探讨Docker生态系统,Docker Compose vs. Docker Swarm vs. Kubernetes:深入比较
Kubernetes适用于大规模、复杂应用程序和多云部署,具有高度可定制的部署配置和广泛的生态系统。 在选择时,还可以考虑将它们组合使用,以满足不同环境和需求。无论选择哪个工具,容器编排都将成为现代应用程序开发和部署的不可或缺的一部分。
1484 0
|
SQL 安全 数据可视化
代码审计神器:Fortify SCA 保姆级教程
代码审计神器:Fortify SCA 保姆级教程
代码审计神器:Fortify SCA 保姆级教程
|
Kubernetes 网络安全 API
在K8S中,集群内有个节点not ready,如何排查?
在K8S中,集群内有个节点not ready,如何排查?
|
BI 数据安全/隐私保护
基于SSM的汽车销售管理系统
基于SSM的汽车销售管理系统

热门文章

最新文章