maven定义了许多dependency,每个dependency内部也会定义它的dependency。
首先我们来看一下依赖冲突产生的原因:
- 如果项目的依赖A和依赖B同时引入了依赖C。
- 如果依赖C在A和B中的版本不一致就可能依赖冲突。
- 比如 项目 <- A, B, A <- C(
1.0
),B <- C(1.1
)。 - 那么maven如果选择高版本C(
1.1
)来导入(这个选择maven会根据不等路径短路径原则和同等路径第一声明原则选取),C(1.0
)中的类c在C(1.1
)中被修改而不存在了。 - 在编译期可能并不会报错,因为编译的目的只是把业务源代码编译成class文件,所以如果项目源代码中没有引入共有依赖C因升级而缺失的类c,就不会出现编译失败。除非源代码就引入了共有依赖C因升级而缺失的类c则会直接编译失败。
- 在运行期,很有可能出现依赖A在执行过程中调用C(
1.0
)以前有但是升级到C(1.1
)就缺失的类c,导致运行期失败,出现很典型的依赖冲突时的NoClassDefFoundError
错误。 - 如果是升级后出现原有的方法被修改而不存在的情况时,就会抛出
NoSuchMethodError
错误。
那么怎么来解决依赖冲突呢?
- 首先可以借助Maven查看依赖的依赖树来分析一下:
mvn dependency:tree
,或者使用IDEA的插件Dependency Analyzer插件来可视化地分析依赖关系。这个过程后可以明确哪些dependency引入了可能会冲突的依赖。 - 比如我们的项目引入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)。 - 如果这时候打war包出来启动很有可能会遇到因依赖冲突而出现的
NoClassDefFoundError
或NoSuchMethodError
,导致编译期正常的代码无法在运行期跑起来。 - 由于A引入的C的版本高而B依赖的C版本低,我们优先会选择兼容高版本C的方案,即试图把B的版本调高以使得引入的依赖C可以和A引入的依赖A达到一致的版本,以此来解决依赖冲突。当然这是一个理想状况。
- 如果找到了目前已有的所有的B的版本,均发现其依赖的C没有与A一致的
1.1
版本,比如B是一个许久未升级的旧项目,那么也可以考虑把A的版本拉低以使得C的版本降到与B一致的1.0
版本,当然这也可能会反过来导致A不能正常工作。 - 上面已经可以看出来解决依赖冲突这件事情并不简单,很难顾及两边,很多情况下引入不同版本依赖的很可能超过两方而是更多方。
- 那么来考虑一下妥协的方案,如果A引入的C使用的功能并不跟被抛弃的类或方法有关,而是其他在
1.1
版本中仍然没有改变的类或方法,那么可以考虑直接使用旧的1.0
版本,那么可以使用exclusion
标签来在A中排除掉对C的依赖,那么A在使用到C的功能时会使用B引入的1.0
旧版本C。即A其实向B妥协使用了B依赖的C。
来看个实例:
- 项目引入了
4.2.5.RELEASE
的spring-webmvc
,同时引入了3.2.1.RELEASE
的spring-security-web
。 - 用Dependency Analyzer插件分析得到的依赖关系是这样的:
- 为什么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
,注意的是引入时并不是优先引入高版本的,同时如果依赖的路径长相等则取第一个出现的版本。 - 可以看到
spring-security-web
引入的一众spring依赖是3.X版本的,同spring-webmvc
的4.2.5.RELEASE
版本不一致。 - 果不其然,打好的war包启动的时候即会报错终止。
- 那么在我们的问题在于兼容高版本的
spring-webmvc
的4.2.5.RELEASE
的情形下寻找spring依赖版本一致的spring-security-web
,在www.mvnrepository.com寻找合适版本依赖的spring-security-web
,最终我们看到了4.1.0.RELEASE
版本似乎还不错,其依赖的spring版本在4.2.5.RELEASE
: - 因此我们将这个版本的
spring-security-web
填入pom.xml看一下效果,确实已经没有依赖冲突产生了: - 如果我们找不到完美匹配
4.2.5.RELEASE
的spring-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: