18

maven定义了许多dependency,每个dependency内部也会定义它的dependency。

首先我们来看一下依赖冲突产生的原因:

  1. 如果项目的依赖A和依赖B同时引入了依赖C。
  2. 如果依赖C在A和B中的版本不一致就可能依赖冲突。
  3. 比如 项目 <- A, B, A <- C(1.0),B <- C(1.1)。
  4. 那么maven如果选择高版本C(1.1)来导入(这个选择maven会根据不等路径短路径原则和同等路径第一声明原则选取),C(1.0)中的类c在C(1.1)中被修改而不存在了。
  5. 在编译期可能并不会报错,因为编译的目的只是把业务源代码编译成class文件,所以如果项目源代码中没有引入共有依赖C因升级而缺失的类c,就不会出现编译失败。除非源代码就引入了共有依赖C因升级而缺失的类c则会直接编译失败。
  6. 在运行期,很有可能出现依赖A在执行过程中调用C(1.0)以前有但是升级到C(1.1)就缺失的类c,导致运行期失败,出现很典型的依赖冲突时的NoClassDefFoundError错误。
  7. 如果是升级后出现原有的方法被修改而不存在的情况时,就会抛出NoSuchMethodError错误。

那么怎么来解决依赖冲突呢?

  1. 首先可以借助Maven查看依赖的依赖树来分析一下:mvn dependency:tree,或者使用IDEA的插件Dependency Analyzer插件来可视化地分析依赖关系。这个过程后可以明确哪些dependency引入了可能会冲突的依赖。
  2. 比如我们的项目引入A的依赖C为1.1版本,引入的B会在内部依赖C的1.0版本,那么Dependency Analyzer插件会出现依赖冲突提示,会提示B引入的C的1.0版本和当前选用的C的1.1版本冲突因而被忽略(1.0 omitted for conflict with 1.1)。
  3. 如果这时候打war包出来启动很有可能会遇到因依赖冲突而出现的NoClassDefFoundErrorNoSuchMethodError,导致编译期正常的代码无法在运行期跑起来。
  4. 由于A引入的C的版本高而B依赖的C版本低,我们优先会选择兼容高版本C的方案,即试图把B的版本调高以使得引入的依赖C可以和A引入的依赖A达到一致的版本,以此来解决依赖冲突。当然这是一个理想状况。
  5. 如果找到了目前已有的所有的B的版本,均发现其依赖的C没有与A一致的1.1版本,比如B是一个许久未升级的旧项目,那么也可以考虑把A的版本拉低以使得C的版本降到与B一致的1.0版本,当然这也可能会反过来导致A不能正常工作。
  6. 上面已经可以看出来解决依赖冲突这件事情并不简单,很难顾及两边,很多情况下引入不同版本依赖的很可能超过两方而是更多方。
  7. 那么来考虑一下妥协的方案,如果A引入的C使用的功能并不跟被抛弃的类或方法有关,而是其他在1.1版本中仍然没有改变的类或方法,那么可以考虑直接使用旧的1.0版本,那么可以使用exclusion标签来在A中排除掉对C的依赖,那么A在使用到C的功能时会使用B引入的1.0旧版本C。即A其实向B妥协使用了B依赖的C。

来看个实例:

  1. 项目引入了4.2.5.RELEASEspring-webmvc,同时引入了3.2.1.RELEASEspring-security-web
  2. 用Dependency Analyzer插件分析得到的依赖关系是这样的:

  3. 为什么maven会选择4.2.5.RELEASE引入呢,其实前文提到过:比如对spring-beans这个依赖来说spring-webmvc:4.2.5.RELEASE-spring-beans:4.2.5.RELEASE的路径是2,而spring-security-web:3.2.1.RELEASE-spring-security-core:3.2.1.RELEASE-spring-beans:3.2.8.RELEASE的路径是3,因此根据不等路径取短路径原则则选取了前者,即spring-beans:4.2.5.RELEASE,注意的是引入时并不是优先引入高版本的,同时如果依赖的路径长相等则取第一个出现的版本。
  4. 可以看到spring-security-web引入的一众spring依赖是3.X版本的,同spring-webmvc4.2.5.RELEASE版本不一致。
  5. 果不其然,打好的war包启动的时候即会报错终止。
  6. 那么在我们的问题在于兼容高版本的spring-webmvc4.2.5.RELEASE的情形下寻找spring依赖版本一致的spring-security-web,在www.mvnrepository.com寻找合适版本依赖的spring-security-web,最终我们看到了4.1.0.RELEASE版本似乎还不错,其依赖的spring版本在4.2.5.RELEASE

  7. 因此我们将这个版本的spring-security-web填入pom.xml看一下效果,确实已经没有依赖冲突产生了:

  8. 如果我们找不到完美匹配4.2.5.RELEASEspring-security-web怎么办,比如所有的spring-security-web版本就是在4.2.3.RELEASE或者4.2.6.RELEASE,那么我们就得想到妥协一下了:使用4.1.1.RELEASE版本的spring-security-web,它的spring依赖版本是4.3.1.RELEASE

那么我们试图使用exclusive标签来忽略spring版本:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.1.1.RELEASE</version>
    <exclusions>
        <exclusion>
            <artifactId>spring-aop</artifactId>
            <groupId>org.springframework</groupId>
        </exclusion>
        <exclusion>
            <artifactId>spring-beans</artifactId>
            <groupId>org.springframework</groupId>
        </exclusion>
        <exclusion>
            <artifactId>spring-context</artifactId>
            <groupId>org.springframework</groupId>
        </exclusion>
        <exclusion>
            <artifactId>spring-core</artifactId>
            <groupId>org.springframework</groupId>
        </exclusion>
        <exclusion>
            <artifactId>spring-expression</artifactId>
            <groupId>org.springframework</groupId>
        </exclusion>
        <exclusion>
            <artifactId>spring-web</artifactId>
            <groupId>org.springframework</groupId>
        </exclusion>
    </exclusions>
</dependency>

依赖冲突会消失,spring-security-web会使用4.2.5.RELEASE版本的spring:


JinhaoPlus
1.5k 声望92 粉丝

扎瓦程序员