天天看点

「后端」详解 maven 面对冲突 Jar 包的加载规则

作者:架构思考

一、问题背景

相信大家在日常的开发过程中都遇到过 Jar 包冲突的问题,emm,在最近处理业务需求时我也遇到了不同版本 jar 包冲突导致项目加载出错的问题。主要是一个完整的项目会不可避免的使用第三方的 Jar 包来实现功能开发,各种第三方包之间可能会存在依赖关系,不同版本的依赖就会可能导致依赖间的相互冲突,进而导致整个项目加载的失败。

这篇文章主要记录了本次遇到的问题:即 maven 在面对不同版本的 jar 包在 pom 文件中同时声明会存在加载覆盖的问题,于是通过查询网上相关资料对 maven 包的加载规则介绍,并通过实际场景对其进行分析验证。

二、maven 加载原则

  1. 最短路径原则:面对多级(两级及以上)的不同依赖,会优先选择路径最短的依赖。
  2. 声明优先原则:面对多级(两级及以上)的同级依赖,先声明的依赖会覆盖后声明的依赖。
  3. 同级依赖中,后声明的依赖会覆盖先声明的依赖。

三、本地验证 maven 加载原则

3.1 最短路径原则:使用最短路径加载的前提是,项目中存在两级以上的不同依赖 jar 包,此时项目会优先加载路径最短的 jar 包;

「后端」详解 maven 面对冲突 Jar 包的加载规则

实例验证:分别在 common 模块和 service 模块中间接和直接的引入不同版本的 elasticsearch-rest-client,观察项目中面对不同路径长度情况下实际加载时所使用的版本情况。

common 模块:在 common 模块中引入 elasticsearch-rest-high-level-client 依赖包, 而该依赖包它引入了 elasticsearch-rest-client 7.4.2,从而实现在 common 模块中间接引用该包;

common 的 pom 文件:

<dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>
    </dependencies>           

service 模块:为了验证不同路径长度下 maven 的包加载顺序,我们在 service 模块中直接引入 elasticsearch-rest-client 6.8.13;

service 的 pom 文件:

<dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>
    </dependencies>           

实际加载结果:在 IDEA 中加载 pom 文件时,可以在 maven 管理中看到已经提示 jar 包冲突;

「后端」详解 maven 面对冲突 Jar 包的加载规则

mvn dependency:tree: 我们可以通过 mvn dependency :tree 命令来查看该项目的依赖树,观察发现实际加载的版本是 elasticsearch-rest-client 6.8.13,符合 maven 中的最短路径优先原则;

「后端」详解 maven 面对冲突 Jar 包的加载规则

3.2 声明优先原则:声明优先原则的前提是对于两级以上的同级依赖,先声明的依赖会覆盖后声明的依赖包;

「后端」详解 maven 面对冲突 Jar 包的加载规则

实例验证:针对该原则的验证场景构造不再关注模块是否直接或者间接引用不同版本的 es,我们在 common 模块和 service 模块中都直接引用不同版本的 es,然后通过改变两个模块在 pom 文件中声明的先后顺序来观察项目启动后实际加载的 jar 包;

common 模块:在 common 模块中直接引入依赖包 elasticsearch-rest-client 7.4.2

<dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.4.2</version>
        </dependency>
    </dependencies>           

service 模块:在 service 模块中引入依赖包 elasticsearch-rest-client 6.8.13

<dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>
    </dependencies>           

实际加载结果:

场景 1:我们将 common 模块在 pom 文件中先引入,然后将在 service 模块置于 common 模块后面引入,观察项目实际加载情况;

<dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>           

观察加载结果图,发现实际加载的是 es-rest-client 7.4.2, 即确实是 common 模块的声明生效,service 模块后声明导致其中的 es 未被加载。符合声明优先原则;

「后端」详解 maven 面对冲突 Jar 包的加载规则

场景 2:我们将 service 模块在 pom 文件中先引入,然后将在 common 模块置于 service 模块后面引入,观察项目实际加载情况;;

<dependencies>
         <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>           

观察项目实际加载结果图,发现实际加载的是 es-rest-client 6.8.13, 即确实是模块的声明生效,common 模块后声明导致其中的 es 未被加载。发现符合声明优先原则;

「后端」详解 maven 面对冲突 Jar 包的加载规则

声明优先原则场景验证结束;

3.3 同级依赖中后加载覆盖先加载原则;

「后端」详解 maven 面对冲突 Jar 包的加载规则

实例验证:为了构造在同级依赖中的加载场景, 我们在项目中直接引入两个不同 es 版本的依赖,然后同样通过改变两个 es 版本在 pom 中的声明顺序来观察项目实际加载的 es 版本。

场景 1:我们首先验证 client 7.4.2 依赖包在 client 6.8.13 之前声明的情况;

<dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>
    </dependencies>           

观察 maven 的实际加载结果如下,发现项目中实际加载的 es-rest-client 版本是 6.8.13,先声明的 7.4.2 版本并未实际加载到项目中。符合同级依赖中后加载覆盖先加载原则。

「后端」详解 maven 面对冲突 Jar 包的加载规则

场景 2:然后我们改变声明顺序,将 client 6.8.13 依赖包在 client 7.4.2 之前声明;

<dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.4.2</version>
        </dependency>
    </dependencies>           

观察 maven 实际加载结果如下,发现项目中实际加载的 es-rest-client 版本是 7.4.2,先声明的 6.8.13 版本并未实际加载到项目中。符合同级依赖中后加载覆盖先加载原则。

「后端」详解 maven 面对冲突 Jar 包的加载规则

四、常见异常

Jar 发生冲突后在程序启动时常见异常报错,下面四种异常是能够直观表征 Jar 包加载冲突:

  • 程序抛出 java.lang.ClassNotFoundException 异常。
  • 程序抛出 java.lang.NoSuchMethodError 异常。
  • 程序抛出 java.lang.NoClassDefFoundError 异常。
  • 程序抛出 java.lang.LinkageError 异常等。

五、总结

之前只是浅层的了解 maven 包的加载,没有结合具体的加载原则进行系统的学习验证,正好通过需求开发中遇到依赖冲突相关问题对 maven 的加载原则进行探究。

文章来源:https://zhuanlan.zhihu.com/p/644494551