1.问题的出现

在maven项目中,经常会出现导入的包出现冲突的问题,并且通常报错不会直接提示冲突,而是类似于NoSuchMethodError等类似的报错,说明在加载的类中没有这个方法,很大概率是加载了另外一个版本的包中的类。这时候就需要依赖一些方法去寻找这些问题。
我最近一次解决maven依赖的冲突是遇到了这样一个问题:

Initialization of bean failed; nested exception is java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.isCandidateClass(Ljava/lang/Class;Ljava/lang/Class;)Z  

说明在AnnotationUtils类中没有找到isCandidateClass这个方法,除了没有导入包(几乎不可能)之外一般有两种情况,导入的版本错了或者导入了多个版本而load的class没有这个类。通常会是后一种情况,也是比较难排查和解决的一种。
image.png
遇到上面这个问题,先快速排查一下,双击回车,查找没有这个方法(没有下载源码要先下载源码,如下。因为字节码不能搜索方法)

# 只下载源码
mvn dependency:sources
# 只下载java doc
mvn dependency:resolve -Dclassifier=javadoc
# 源码和java doc都下载
mvn dependency:sources dependency:resolve -Dclassifier=javadoc

发现存在这个方法,但是有两个依赖都引入了这一个包,因此就定位到了并且由于先加载的(pom文件中靠前)是错误版本的jar包,后面引入的就会被省略。因此需要手动进行排除,这里不建议调换两个pom坐标的位置,因为这样不稳定,并且两个冲突版本的包本身就是不合理的,通过手动exclude就可以啦~

2.maven依赖冲突的通用解决

上面主要是一个问题的复盘解决,因为在公司遇到的,没有说明具体的包和版本。主要记录下解决maven冲突的通用方法。

2.1 maven依赖的规则

在遇到相同的依赖时,maven在解决冲突时遵循以下两个原则:
1.路径最短(A和B同时依赖了X,依赖路径最短的会被使用)
2.路径相同时在前者优先(依赖路径相同,在pom中先导入依赖版本被使用)
如果导入了两个相同的包,但是版本一样,maven在解决的时候在后面的包会提示omitted for duplicate,如果版本不同,也会在后面的包提示omitted for conflict with xxx,表明有版本冲突,并使用了某一个版本。
image.png

2.2 maven依赖树

解决依赖最常用的方式是依赖树。

mvn dependency:tree

# 可以将依赖树输出到文件中
mvn -Dverbose  dependency:tree -Doutput=/Users/liujie/output.txt

# 只看某个包的依赖关系或者不看某个包的依赖关系
mvn dependency:tree -Dverbose -Dincludes=被依赖的包
mvn dependency:tree -Dverbose -Dexcludes=被依赖的包

搜索比较方便,具体的依赖可以在IDEA插件里面看到相应的依赖,定位到冲突的依赖之后就可以通过更改版本或者依赖排除的方法进行依赖的修复了。

3. maven的scope

maven中scope主要有6种,分别对应着不同的情况。

3.1.compile

不声明scope元素的情况下的默认值;compile表示被依赖包需要参与当前项目的编译,包括后续的测试,运行周期也参与其中,是一个比较强的依赖;打包的时候通常需要包含进去。

3.2 provided

provided 类型的scope只会在项目的编译、测试阶段起作用;可以认为在目标容器中已经提供了这个依赖,无需在提供,但是在编写代码或者编译时可能会用到这个依赖;依赖不会被打入到项目jar包中。
另外,下有一个子标签 ,如果一个依赖的 设置为true,则该依赖在打包的时候不会被打进jar包,同时不会通过依赖传递传递到依赖该项目的工程;例如:x依赖B,B由依赖于A(x->B->A),则A中设置 为true的依赖不会被传递到x中。
provided和optional的区别在于:

  • 为true 表示某个依赖可选,该依赖是否使用都不会影响服务运行;
  • provided的在目标容器中已经提供了这个依赖,无需在提供。

3.3 runtime

runtime与compile比较相似,区别在于runtime 跳过了编译阶段,打包的时候通常需要包含进去。

3.4 test

在一般的编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用,不会被打包到项目jar包中,同时如果项目A依赖于项目B,项目B中的test作用域下的依赖不会被继承。

3.5 system

表示使用本地系统路径下的jar包,需要和一个systemPath一起使用,例如:

<!--引用-->
<dependency>
    <groupId>xxxx</groupId>
    <artifactId>xxx</artifactId>
    <systemPath>${basedir}/lib/xxxxx.jar</systemPath>
    <scope>system</scope>
    <version>1.4.12</version>
</dependency>

3.6 import

import 只能在pom文件的中使用,从而引入其他的pom文件中引入依赖,如:在Spring boot 项目的POM文件中,我们可以通过在POM文件中继承 Spring-boot-starter-parent来引用Srping boot默认依赖的jar包,如下:

<!-- Inherit defaults from Spring Boot -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.BUILD-SNAPSHOT</version>
</parent>

但是,通过上面的parent继承的方法,只能继承一个 spring-boot-start-parent。实际开发中,用户很可能需要继承自己公司的标准parent配置,这个时候可以使用 scope=import 来实现多继承。代码如下:

<dependencyManagement>
    <dependencies>
        <dependency>
             <!-- Import dependency management from Spring Boot -->
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-dependencies</artifactId>
             <version>2.0.1.BUILD-SNAPSHOT</version>
             <type>pom</type>
             <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

通过上面方式,就可以获取spring-boot-dependencies.2.0.1.BUILD-SNAPSHOT.pom文件中dependencyManagement配置的jar包依赖。如果要继承多个,可以在dependencyManagement中添加,如:

<dependencyManagement>
    <dependencies>
    <!-- Override Spring Data release train provided by Spring Boot -->
         <dependency>
             <groupId>org.springframework.data</groupId>
             <artifactId>spring-data-releasetrain</artifactId>
             <version>Fowler-SR2</version>
             <type>pom</type>
             <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.0.1.BUILD-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

参考文章

1.Maven中的scope总结_野生开发者的博客-CSDN博客_maven scope

最后修改:2022 年 09 月 04 日
如果觉得我的文章对你有用,请随意赞赏