2

解决maven依赖冲突,这篇就够了! - 雨点的名字

 1 year ago
source link: https://www.cnblogs.com/qdhxhz/p/16363532.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

解决maven依赖冲突,这篇就够了!

什么是依赖冲突

依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成了包版本冲突。

依赖冲突的原因

我们在maven项目的pom中 一般会引用许许多多的dependency。例如,项目A有这样的依赖关系:

A -> C -> X(1.0)
B -> D -> X(2.0)

X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢? 两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。

至于怎么选肯定有它的规则(下面会讲),这里我们先假设最终引用的X(1.0)版本,这样会不会有问题呢?

当然会有的

1、你想如果B引用X(2.0)的新创建的类,但因为最终被解析的是X(1.0),所以就会出现很典型的NoClassDefFoundErrorClassNotFoundException依赖冲突报错。

2、如果B引用X(2.0)的新创建的方法,但因为最终被解析的是X(1.0),所以就会抛出 NoSuchMethodError系统异常。

但换种角度,如果最终解析的是X(2.0),就没问题了吗?

那也不一定

1、如果X(2.0)删掉了X(1.0)的一些类,但A已经引用了,同样也会报NoClassDefFoundError或者ClassNotFoundException错误。

2、如果X(2.0)删掉了X(1.0)的一些方法,但A已经引用了,同样也会报NoSuchMethodError错误。

所以说具体问题还需具体分析,到底采用哪个版本还需要看实际项目。也可能我们需要升级对应的A或者B的版本才能解决问题。

如果有两个版本的X,Maven是依据什么原则来选择解析哪个X呢?

二、maven依赖原则

maven依赖主要有两大原则

1、路径最近者优先

相同jar不同版本,根据依赖的路径长短来决定引入哪个依赖。

依赖链路一:A -> B -> C -> X(1.0)
依赖链路二:F -> D -> X(2.0)

该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。依赖调解第一原则不能解决所有问题,比如这样的依赖关系:

A -> B -> Y(1.0)
c -> D -> Y(2.0)

Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。Maven定义了依赖调解的第二原则:

2、第一声明者优先

在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最前的那个依赖优胜。该例中,如果A的依赖声明在C之前,那么Y (1.0)就会被解析使用.

注意 子pom内声明的优先于父pom中的依赖。

三、如何排除依赖

我们先来解释下什么是传递性依赖

1、什么是传递性依赖

比如当我们项目中,引用了A的依赖,A的依赖通常又会引入B的jar包,B可能还会引入C的jar包。

这样,当你在pom.xml文件中添加了A的依赖,Maven会自动的帮你把所有相关的依赖都添加进来。

这里随便举一个例子,比如我们为了实现导入导出功能我们可能会引入poi

  <dependency>
     <groupId>org.apache.poi</groupId>
     <artifactId>poi-ooxml</artifactId>
     <version>3.10.1</version>
  </dependency>

当你引入它的时候,它其实还会映入其它jar包

  <dependencies>
    <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi</artifactId>
       <version>3.10.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml-schemas</artifactId>
      <version>3.10.1</version>
    </dependency>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
 </dependencies>

就这样一层层的,Maven会自动的帮你把所有相关的依赖都添加进来。传递性依赖会给项目引入很多依赖,简化项目依赖管理,但是也会带来问题。

最明显的就是容易发生依赖冲突。

2、如何排除依赖

关键字:exclusions

exclusions可以包含一个或者多exclusion子元素,因此可以排除一个或者多个传递性依赖。

需要注意的是,声明exclusion的时候只需要groupldartifactld,而不需要要version元素。

   <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.10.1</version>
            <!--排除poi依赖-->
        <exclusions>
            <exclusion>
                <artifactId>poi</artifactId>
                <groupId>org.apache.poi</groupId>
             </exclusion>
        </exclusions>
   </dependency>

四、实例演示如何解决依赖冲突

我们先来复现因为依赖冲突导致项目报错的。

1、制造依赖冲突

在当前项目中,我即引用了org.apache.poi,又引用了com.alibaba的easyexcel。

pom文件如下:

   <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.10.1</version>
   </dependency>
   <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.2.8</version>
   </dependency>

然后在项目中添加如下代码:

@SpringBootApplication
public class MainApplication {

	public static void main(String[] args) {
		SpringApplication.run(MainApplication.class, args);
		test();

	}
	public static void test() {
		//1.获取文件地址
		String fileName = "/excel/test.xlsx";
		//2、调用easyExcel里面的方法实现写操作
		EasyExcel.write(fileName, UserDto.class).sheet("某某报表").doWrite(new ArrayList<>());
	}
}	

我们发现在SpringBoot启动的时候就报NoClassDefFoundError异常了。

1090617-20220610154656255-1453028930.jpg

这就是依赖冲突导致的异常报错。

为什么会这样呢,我们可以通过idea看下,打开maven菜单,点击show dependencies

1090617-20220610154758772-1512354831.jpg

看到这里你大概就已经明白,因为我当前的easyexcel引入的poi是3.17版本的,但是因为根据maven依赖原则,实际引入的poi版本确实是3.10.1版本的。

而DefaultTempFileCreationStrategy这个类在3.10.1这个版本并没有,只有3.17版本才有,所以报了这个错误,这么一解释是不是都通了。

然后我们再来思考一个问题,上面这个案例我们一眼就知道是最终应用哪个依赖里的哪个版本,但如果你的项目中依赖许许多多的jar,肉眼排查就没那么方便了,这里推荐一个

Maven管理插件

2、Maven Helper插件分析jar包冲突

选择并下载插件

1090617-20220610154855238-1355549376.jpg

在pom文件中看到 dependency analyzer标志,说明maven helper插件就安装成功了。

1090617-20220610154954775-1701444613.jpg

点击dependency analyzer之后就会进入到下面的页面

1090617-20220610155050180-103696186.jpg

从图中可以看出有哪些jar存在冲突,存在冲突的情况下最终采用了哪个依赖的版本。标红的就是冲突版本,白色的是当前的解析版本

如果我们想保留标红的版本,那我们可以标白区域右击选择排除(Exclude)

1090617-20220610155127183-14865086.jpg

然后我们再来看pom文件,发现在org.apache.poi中已经移除了poi了。

   <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi-ooxml</artifactId>
       <version>3.10.1</version>
       <exclusions>
          <exclusion>
             <artifactId>poi</artifactId>
             <groupId>org.apache.poi</groupId>
           </exclusion>
       </exclusions>
    </dependency>
    <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>easyexcel</artifactId>
       <version>2.2.6</version>
    </dependency>

我们再来看最终引用的poi版本是哪个?

1090617-20220610155127183-14865086.jpg

我们发现最终的版本最终已经变成3.17版本,现在再启动就不会报错了,成功解决依赖冲突问题。

一般我们在解决依赖冲突的时候,都会选择保留jar高的版本,因为大部分jar在升级的时候都会做到向下兼容,所以只要保留高的版本就不会有什么问题。

但是有些包,版本变化大没法去做向下兼容,高版本删了低版本的某些类或者某些方法,那么这个时候就不能一股脑的去选择高版本,但也不能选择低版本。

就比如下面这个案例

依赖链路一:A -> B -> C -> X(1.0)
依赖链路二:F -> D -> X(2.0)

X(2.0) 没有对 X(1.0) 做向下兼容也就是说可能存在排除哪个都不行,那怎么办我们只能考虑升级A的版本或者降低F的版本。比如A升级到A(2.0),使它依赖的X版本

变成X(2.0)这样的话就解决依赖冲突。

但话有说回来 A升级到A(2.0) 可能会影响许许多多的地方,比如自己项目中代码是否需要改变,或者因为 A升级到A(2.0) 导致 B和C的版本有所改变,这些影响点都需要我

们去考虑的。所以说为什么说一个大型项目稳定后,pom文件的升级是件繁琐的事情,那是因为考虑的东西是在太多了,稍有不慎就会因为依赖冲突而导致系统报错。

声明: 公众号如需转载该篇文章,发表文章的头部一定要 告知是转至公众号: 后端元宇宙。同时也可以问本人要markdown原稿和原图片。其它情况一律禁止转载!

1090617-20211125172727036-1937303215.gif

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK