Maven项目运行过程中,如果报以下错误, 极有可能是依赖冲突造成的。
-
Caused by:java.lang.NoSuchMethodError
-
Caused by: java.lang.ClassNotFoundException
依赖冲突原理
依赖冲突指的就是我们在引入不同的依赖时,不同的依赖又引入了相同的依赖,这些相同的依赖之间就会产生版本冲突问题。举个例子来说明一下:
A->B->C->D1(log 15.0):A 中包含对 B 的依赖,B 中包含对 C 的依赖,C 中包含对 D1 的依赖,假设是 D1 是日志 jar 包,version 为 15.0。
E->F->D2(log 16.0):E 中包含对 F 的依赖,F 包含对 D2 的依赖,假设是 D2 是同一个日志 jar 包,version 为16.0。
当 pom.xml 文件中引入 A、E 两个依赖后,根据 Maven 传递依赖的原则,D1、D2 都会被引入,而 D1、D2 是同一个依赖 D 的不同版本。当我们在调用 D2 中的 method1() 方法,而 D1 中是 15.0 版本(method1可能是版本升级后增加的方法),可能没有这个方法,这样 JVM 在加载 A 中 D1 依赖的时候,找不到 method1 方法,就会报 NoSuchMethodError 的错误,此时就产生了 jar 包冲突。
依赖冲突解决方法
Maven 解析 pom.xml 文件时,同一个 jar 包只会保留一个,那么面对多个版本的 jar 包,需要怎么解决呢?
1、Maven默认处理策略
Maven的默认处理策略,如果选择了正确的版本,我们就无需再做处理,但有时候会选择错误的那个版本,导致会报错。比如选择了低版本的依赖,而这个低版本依赖中是不存在某个类的,我们去访问这个类就会报错。
- 最短路径优先:Maven 面对 D1 和 D2 时,会默认选择最短路径的那个 jar 包,即 D2。E->F->D2 比 A->B->C->D1 路径短 1。
- 最先声明优先:如果路径一样的话,如: A->B->C1, E->F->C2 ,两个依赖路径长度都是 2,那么就选择最先声明。
2、手动移除依赖:
用于排除某项依赖的依赖jar包,有如下二种方式:
借助Maven Helper插件移除
我们可以借助Maven Helper插件中的Dependency Analyzer分析冲突的jar包,然后在对应标红版本的jar包上面点击execlude,就可以将该jar包排除出去。再刷新以后冲突就会消失。
手动排除
或者手动在pom.xml中使用<exclusion>标签去排除冲突的jar包(上面利用插件Maven Helper中的execlude方法其实等同于该方法):
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId><version>1.4.4.RELEASE</version><exclusions><exclusion><groupId>com.google.guava</groupId><artifactId>guava</artifactId></exclusion></exclusions>
</dependency>
3、版本锁定原则
版本锁定原则一般用在继承项目的父项目中。正常项目都是多模块的项目,如 moduleA 和 moduleB 共同依赖 X这个依赖的话,那么可以将 X 抽取出来,同时设置其版本号,这样 X 依赖在升级的时候,不需要分别对 moduleA 和 moduleB 模块中的依赖 X 进行升级,避免太多地方(moduleC、moduleD….)引用 X 依赖的时候忘记升级造成 jar 包冲突,这也是实际项目开发中比较常见的方法。
首先定义一个父 pom.xml,将公共依赖放在该 pom.xml 中进行声明:
<properties><spring.version>spring4.2.4</spring.version>
<properties><dependencyManagement><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.versio}</version></dependency></dependencies>
</dependencyManagement>
这样如 moduleA 和 moduleB 在引用 Spring-beans jar 包的时候,直接使用父 pom.xml 中定义的公共依赖就可以。moduleA 在其 pom.xml 使用 spring-bean 的 jar 包时就不用再定义版本:
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></dependency>
</dependencies><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></dependency>
</dependencies>
以上就是日常开发中解决 Maven 冲突的几个小方案,当然实际开发中 jar 包冲突的问题可能远远比这个更复杂,需要具体问题具体处理。