您现在的位置是:首页 >技术杂谈 >maven依赖选择策略(依赖调解)网站首页技术杂谈

maven依赖选择策略(依赖调解)

SYKMI 2024-07-01 11:59:37
简介maven依赖选择策略(依赖调解)

这里先抛出结论

  • 最短路径原则: 不同级依赖, 选择路径最短(对于传递性依赖和一级依赖)
  • 声明优先原则 : 同级依赖,先声明的覆盖后声明的(对于传递性依赖)
  • 同级依赖后加载覆盖先加载原则(不属于传递性依赖的情况,就是pom里的一级依赖)

我们都知道maven具有传递性依赖的机制,就是当你在pom中引用依赖A时,如果这个依赖A同时依赖B的话,那么maven会自动引入B的依赖,以传递性依赖的形式引入到项目中。

虽然这样做很方便,但是有时也会导致一些问题的产生,比如依赖冲突。
A -> C -> X(1.0)
B -> D -> X(2.0)
如果你在项目中引入了A和B,那么最终哪个依赖会被解析呢?按照上面写出的结论,其实是X(1.0)会被解析,你可能会想,既然maven已经按照规则帮我们选定了依赖,为什么会出现依赖冲突的情况呢?我们举个例子

1、你想如果依赖B的代码中引用了X(2.0)的新创建的类,但因为最终被解析的是X(1.0),所以运行时就会出现很典型的NoClassDefFoundError或ClassNotFoundException依赖冲突报错。
2、如果依赖B的代码中引用X(2.0)的新创建的方法,但因为最终被解析的是X(1.0),所以就会抛出 NoSuchMethodError系统异常。

所以还是得具体情况具体分析,如果我们手动排除依赖X(1.0),引入X(2.0)就不会出现问题了吗?也不一定的,有可能2.0版本的依赖删去了1.0版本中的一些方法和类,这种情况下同样会导致这些问题。

如何知道传递性依赖究竟是从哪条依赖中被引入的?

我们可以通过mvn dependency:tree命令来进行查看,如下图,我在pom文件中引入了两个版本的easyExcel的依赖,由于它们属于一级依赖,按照规则,应该引入后加载的版本
在这里插入图片描述
打开pom.xml所在文件夹,打开cmd命令行,输入mvn dependency:tree,我们可以从图片中看到,引入的确实是2.1.6版本的依赖
在这里插入图片描述

这里再举几个例子,我们进入2.1.6版本的easyexcel依赖中可以看到,该依赖还依赖于slf4j-api-1.7.26的依赖,这里我从中央仓库拿了一份其他版本的slf4j的依赖进行引入,我们刷新pom文件后再看看最终引入的slf4j究竟是什么版本的
重新引入其他版本的slf4j
其实由于上面的结论我们已经知道,最终引入的是1.7.30版本的slf4j-api,因为它们属于不同级的依赖,maven会按照最短路径进行引入
在这里插入图片描述
这里我抛出一个问题,如果slf4j-api-1.7.30版本的依赖比slf4j-api-1.7.26多出了一些新方法和类(这里只是举个例子,假设),并且在项目中我不是直接引入的slf4j-api-1.7.30版本,而是通过传递性依赖的方式引入,比如我引入了A依赖,A->依赖于slf4j-api-1.7.30版本,并且A中的代码引用了slf4j-api-1.7.30版本的新方法和类,如下图所示,这种情况下maven根据- 声明优先原则 : 同级依赖,先声明的覆盖后声明的(对于传递性依赖)会引入slf4j-api-1.7.26版本的依赖,那么问题就来了,当项目运行起来时,A依赖中的代码执行时就会报错了,因为它找不到某些方法和类,这就是典型的依赖冲突!
在这里插入图片描述
这种情况下我们可以通过排除掉easyExcel中的依赖来让maven选择使用A的子依赖,就像下面这样

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>easyexcel</artifactId>
      <version>2.1.6</version>
      <exclusions>
        <exclusion><!--手动排除-->
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

这样就可以正确的引入slf4j-api-1.7.30了!

tips

可能有的小伙伴会问,编译器在编译代码的时候不会检查我们引入的依赖的的代码中的方法和类是否存在的吗?
其实是不会的,因为那是已经编译好的class字节码文件,所以依赖冲突一般只在运行时产生。
GPT回答如下:(问题:Java编译代码的时候只会编译我们写的代码吗?)
是的,Java编译器在编译Java源代码时,只会编译我们自己编写的Java类。它并不会自动编译我们所依赖的的第三方库,因为这些库已经编译好了,生成了对应的.class字节码文件。
当我们在Java源代码中调用依赖库中的类或方法时,编译器会检查这些依赖库中所引用的类是否存在,如果存在,则会将它们的字节码文件打包到生成的class文件中,一并输出到编译后的目标代码中。
但如果我们修改了依赖库的源代码,那么需要重新对依赖库进行编译生成新的.class文件,然后再对主程序进行编译和运行。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。