【Maven】jar包冲突原因与最优解决方案

2023-11-13

【Maven】jar包冲突原因与最优解决方案

前言

你是否经常遇到这样的报错:

java.lang.NoSuchMethodError
java.lang.ClassNotFoundException
java.lang.NoClassDefFoundError

以上报错就有可能是jar包冲突造成的,Maven中jar包冲突是开发过程中比较常见而又令人头疼的问题,我们需要知道 jar包冲突的原理,才能更好的去解决jar包冲突的问题。本文将从jar包冲突的原理和解决jar包冲突两个方面阐述Maven中jar包问题。

jar包冲突原因

当我们在maven项目中引入第三方组件时,三方组件中的依赖可能会与项目已有组件发生冲突。
比如三方组件中依赖httpclient的版本是4.5.x,而项目中已有的httpclient版本是3.1.x,那么此时就会产生一下两种情况:

如果用三方组件的高版本httpclient覆盖原有的低版本httpclient,有可能会导致原来项目启动运行失败。即使高版本兼容低版本,这样高风险的操作也是很危险的;

如果在三方maven依赖中对其对依赖的httpclient在引入时使用进行排除,使三方组件使用项目中的低版本httpclient,此时可能会因为版本不一致导致三方组件无法使用

在这样的情况下我们应当如何保证不影响项目原有依赖版本的情况下正常使用三方组件呢?此时可以考虑使用maven-shade-plugin插件,jar包冲突解决方案最后介绍。

依赖传递

首先我们需要了解jar包依赖的传递性。

关于依赖作用范围详解见:【Maven】属性scope依赖作用范围详解。

当我们需要A的依赖的时候,就会在pom.xml中引入A的jar包;而引入的A的jar包中可能又依赖B的jar包,这样Maven在解析pom.xml的时候,会依次将A、B 的jar包全部都引入进来。

举个例子:
在Spring Boot应用中导入Hystrix和原生Guava的jar包:

com.google.guava guava 20.0 org.springframework.cloud spring-cloud-starter-netflix-hystrix 1.4.4.RELEASE

利用Maven Helper插件得到项目导入的jar包依赖树:
在这里插入图片描述
从图中可以看出Hystrix包含对Guava jar包依赖的引用: Hystrix -> Guava,所以在引入Hystrix的依赖的时候,会将Guava的依赖也引入进来。

冲突原因

假设有如下依赖关系:

A->B->C->D1(log 15.0):A中包含对B的依赖,B中包含对C的依赖,C中包含对D1的依赖,假设是D1是日志jar包,version为15.0

E->F->D2(log 16.0):E中包含对F的依赖,F包含对D2的依赖,假设是D2是同一个日志jar包,version为16.0

当pom.xml文件中引入A、E两个依赖后,根据Maven传递依赖的原则,D1、D2都会被引入,而D1、D2是同一个依赖D的不同版本。
当我们在调用D2中的method1()方法,而D1中是15.0版本(method1可能是D升级后增加的方法),可能没有这个方法,这样JVM在加载A中D1依赖的时候,找不到method1方法,就会报NoSuchMethodError的错误,此时就产生了jar包冲突。

注:
如果在调用method2()方法的时候,D1、D2都含有这个方法(且升级的版本D2没有改动这个方法,这样即使D有多个版本,也不会产生版本冲突的问题。)

举个例子:
在这里插入图片描述
利用Maven Helper插件分析得出:Guava这个依赖包产生冲突。
我们之前导入了Guava的原生jar包,版本号是20.0;而现在提示Guava产生冲突,且冲突发生位置是Hystrix所在的jar包,所以可以猜测Hystrix中包含了对Guava不同版本的jar包的引用。

为了验证我们的猜想,使用Maven Helper插件打印出Hystrix依赖的jar tree:
在这里插入图片描述
可以看到:Hystrix jar中所依赖的Guava jar包是15.0版本的,而我们之前在pom.xml中引入的原生Guava jar包是20.0版本的,这样Guava就有15.0 与20.0这两个版本,因此发生了jar包冲突。

jar包冲突解决方案

Maven 解析 pom.xml 文件时,同一个 jar 包只会保留一个,那么面对多个版本的jar包,需要怎么解决呢?

Maven默认处理策略

最短路径优先

Maven 面对 D1 和 D2 时,会默认选择最短路径的那个 jar 包,
即 D2。E->F->D2 比 A->B->C->D1 路径短1。

最先声明优先

如果路径一样的话,如: A->B->C1, E->F->C2 ,两个依赖路径长度都是 2,那么就选择最先声明。

排除依赖

移除依赖:用于排除某项依赖的依赖jar包

1.我们可以借助Maven Helper插件中的Dependency Analyzer分析冲突的jar包,然后在对应标红版本的jar包上面点击execlude,就可以将该jar包排除出去。
在这里插入图片描述
再刷新以后冲突就会消失。

2.手动排除

手动在pom.xml中使用<exclusion>标签去排除冲突的jar包(上面利用插件Maven Helper中的execlude方法其实等同于方法1):

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		<version>1.4.4.RELEASE</version>
		<exclusions>
			<exclusion>
				<groupId>com.google.guava</groupId>
				<artifactId>guava</artifactId>
			</exclusion>
	</exclusions>
</dependency>

mvn分析包冲突命令:

mvn dependency:tree

版本锁定

版本锁定原则:一般用在继承项目的父项目中
正常项目都是多模块的项目,如moduleA和moduleB共同依赖X这个依赖的话,那么可以将X抽取出来,同时设置其版本号,这样X依赖在升级的时候,不需要分别对moduleA和moduleB模块中的依赖X进行升级,避免太多地方(moduleC、moduleD…)引用X依赖的时候忘记升级造成jar包冲突,这也是实际项目开发中比较常见的方法。

首先定义一个父pom.xml,将公共依赖放在该pom.xml中进行声明:

<properties>
    <spring.version>spring4.2.4</spring.version>
<properties>

<dependencyManagement>
    <dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.versio}</version>
		</dependency>
	</dependencies>
</dependencyManagement>

这样如moduleA和moduleB在引用Spring-beans jar包的时候,直接使用父pom.xml中定义的公共依赖就可以:
moduleA在其pom.xml使用spring-bean的jar包(不用再定义版本):

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-beans</artifactId>
	</dependency>
</dependencies>

moduleB在其pom.xml使用spring-bean的jar包如上类似:

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-beans</artifactId>
	</dependency>
</dependencies>

maven-shade-plugin插件

作用是将依赖的包在package阶段一起打入jar包中,以及对依赖的jar包进行重命名从而达到隔离的作用。这里为了解决上面的问题我们主要使用第二个功能特性,使得相同依赖不同版本达到共存的目的。

1.环境准备
这里用fastjson来模拟使用maven-shade-plugin解决项目中不同版本共存问题。原项目此时使用的是1.1.15版本的fastjson

<!-- 原项目 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.1.15</version>
</dependency>

假引入一个三方依赖,该依赖使用1.2.75版本的fastjson
```xml
<!-- 将引入依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>

2.解决方案
搭建一个新的模块rename-dependencies,专门用于存放1.2.75依赖。在pom文件中添加1.2.75的依赖,然后添加maven-shade-plugin插件。rename-dependencies的pom如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <groupId>com.sk</groupId>
    <artifactId>rename-dependencies</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modelVersion>4.0.0</modelVersion>

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <relocations>
                                <relocation>
                                    <pattern>com.alibaba</pattern>
                                    <shadedPattern>shade.com.alibaba</shadedPattern>
                                </relocation>
                            </relocations>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

从配置文件中可以看到,由于maven-shade-plugin插件在解决这个问题上其实是通过对依赖进行重命名而达到隔离的目的,所以配置主要是集中在relocations中。这里将以com.alibaba开头的包全部重命名为以shade.com.alibaba开头。

3.引入依赖
将rename-dependencies进行打包,打包好之后在原项目中引入rename-dependencies的依赖。此时在引入rename-dependencies之后,可以在项目下看到该依赖中的fastjson包名发生了变化
在这里插入图片描述

此时在代码中调用fastjson相关方法,会提示选择所需要包,如下图,此时问题解决,两个版本的fastjson可同时使用已经兼容。

在这里插入图片描述

一些需要注意的坑

描述: 引入依赖找不到重命名的shade包

原因:重命名的模块和需要引入依赖的模块在一个项目中,idea优先找本项目,所以没有走仓库

解决方案:
将模块从项目maven中移除,右键项目-maven-unlink maven projects
新建一个项目专门来做依赖

总结

本文从jar包冲突的原理和解决jar包冲突两个方面阐述Maven引入jar包依赖的问题;

其中在解决方案选择方面:

如果Maven不能根据默认处理策略解决掉,就需要从移除依赖或者升级现有依赖处理;

但是升级现有依赖风险比较大,有时会对原项目不兼容的代码进行大量修改,就比如有次项目引入封装的工作流组件时,其中组件内使用的mybatis-plus为3.5.1版本,原项目使用的3.3.1,先是对组件排除掉mybatis-plus,但是后面发现mybatis-plus中有一个反射工具类无法使用,只有高版本才能使用,于是就对原项目做依赖升级,但是需要对原项目不兼容的代码进行大量修改,比如mybatisconfig类和大量接口返回类型修改;

最优选择:maven-shade-plugin 保证不影响项目原有依赖版本的情况下正常使用三方组件。

参考:

https://blog.csdn.net/qq_38550836/article/details/111567355

https://blog.csdn.net/noaman_wgs/article/details/81137893

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Maven】jar包冲突原因与最优解决方案 的相关文章

随机推荐

  • VC6.0使用教程

    使用之前我们先准备一段代码 include
  • C#将依赖的DLL文件集成到EXE内部

    使用场景 C 写的一些小程序 为了方便传播 减少传播文件数量 将依赖的DLL文件集成到EXE内部是必要的 解决方案 打开 管理NuGet程序包 在浏览中搜索 Costura Fody 点击 安装 按钮 等待下载依赖及安装完成 重新编译软件
  • 操作系统7-信号量与管程

    回顾一下 并发问题 多线程并发导致资源竞争 同步概念 1 协调多线程对共享数据的访问 2 任何时刻只能由一个线程执行临界区代码 确保同步正确的方法 底层硬件支持 高层次的编程抽象 锁 信号量是锁机制在同一层上的高层抽象编程方法 一 信号量s
  • html如何设置网页的背景图片

    div div
  • Web安全面试题之-信息搜集(1)

    1 信息收集如何处理子域名爆破的泛解析问题 根据一个不存在的子域名的解析IP 来记录获取黑名单 IP 在爆破字典时 如果解析的IP在这个黑名单中 则默认跳过 如果不存在 我们则入库处理 还有一种泛解析的爆破处理方式是根据TTL来做判断 我们
  • 【Linux基础及shell脚本】Shell脚本中变量的使用

    文章目录 1 Shell变量基础 1 1 什么是变量 1 2 如何在Shell中定义和使用变量 2 Shell环境变量 2 1 什么是环境变量 2 2 环境变量与普通变量的区别 2 3 如何查看 设置和删除环境变量 3 Shell位置参数
  • 串口通信及中断

    异步通信 发送和接收数据的双方用各自的时钟控制数据的发送和接收 为降低数据传输的错误率要求双方时钟尽可能一致 异步通信以帧为单位传送数据 由于每帧数据都具有起始位和停止位所以两帧数据之间的间隔时间不影响数据传送和接收的准确率 但是每帧数据内
  • Git 笔记 - git commit

    文章目录 01 git commit 02 git commit m 03 git commit a 04 git commit p 05 git commit C 06 git commit c 07 git commit n 08 gi
  • 关于Map、WeakMap、Set 、WeakSet

    在计算机程序中 弱引用与强引用相对 是指不能确保其引用的对象不会被垃圾回收器回收的引用 一个对象若只被弱引用所引用 则被认为是不可访问的 或弱访问的 并因此可能在任何时刻被回收 Map Map 它类似于对象 也是键值对的集合 并且能够记住键
  • 基于长短期记忆神经网络LSTM的预测模型(matlab实现)

    希望是附丽于存在的 有存在 便有希望 有希望 便是光明 鲁迅 1 普通循环神经网络 循环神经网络 Recurrent Neural Networks 简称RNN 是一种能够处理时间序列数据的神经网络模型 可以自然的拟合时间和数据之间的关系
  • go 接口作为方法参数传递 

    接口作为方法参数传递 在方法内部修改结构体 示例 type IUserService interface GenId type UserService struct id string func u UserService GenId ge
  • git:分支管理策略

    主分支Master 首先 代码库应该有一个 而且仅有一个主分支 所有提供给用户使用的正式版本 都在这个主分支上发布 Git主分支的名字 默认叫做Master 它是自动建立的 版本库初始化以后 默认就是在主分支在进行开发 主分支 也是用于部署
  • 微信订阅号和公众号的区别

    第一 定位不同 订阅号为用户提供信息和资讯 服务号主要为用户提供服务 第二 群发信息量不同 订阅号每天 24小时内 可以发送1条群发消息 最新公众平台 服务号1个月 30天 内仅可以发送4条群发消息 第三 用户收到信息提醒方式不同 群发信息
  • Golang如何配置国内镜像

    1 打开国内镜像官网 GOPROXY IO 一个全球代理 为 Go 模块而生 2 您只需通过简单设置 PowerShell Windows 配置 GOPROXY 环境变量 env GOPROXY https goproxy io direc
  • Django 快速搭建博客 第六节

    上节我们用模板弄出来第一个hello world 这节课 我们把数据库里面真正的数据跟单篇文章的详情页显示出来 一 模板的下载 这里的模板下载指的是 下载js和css文件 一个网站想要变得漂亮 变得可以稍微好看点 这里我们使用是bootst
  • vue 使用nuxt创建工程

    1 按回车确定项目名称 2 选择语法 3 选择npm 4 选择框架 5 选择请求方式 6 7 8 9 10 11 回车 12 显示下面提示则创建成功 13
  • 数字电路与系统学习笔记(戚金清)

    第一章 数字逻辑基础 1 1模拟信号 模拟电路 数字信号 数字电路 连续变化的物理量是模拟量 表示模拟量的信号是模拟信号 字符数量无限 离散变化的物理量是数字量 表示数字量的信号是数字信号 字符数量有限 构成模拟电路的元件是电子管 模拟计算
  • stm32F4修改时钟频率,更换为8MHz晶振

    stm32F4修改时钟的方法和stm32F103修改时钟的方法不大一样 毕竟库都换了嘛 一个F1的库一个F4的库 而且F1的库默认晶振时钟就是8MHz 大多数开发板也用的8MHz时钟 给我们提供了很多的方便 F1的库关于PLL的写法也很直观
  • access_token

    access token是公众号的全局唯一接口调用凭据 公众号调用各接口时都需使用access token 开发者需要进行妥善保存 access token的存储至少要保留512个字符空间 access token的有效期目前为2个小时 需
  • 【Maven】jar包冲突原因与最优解决方案

    Maven jar包冲突原因与最优解决方案 文章目录 前言 jar包冲突原因 依赖传递 冲突原因 jar包冲突解决方案 Maven默认处理策略 排除依赖 版本锁定 maven shade plugin插件 总结 前言 你是否经常遇到这样的报