之前我们讲解了Maven的配置,那么这次我们就讲讲一些更贴近日常维护的东西,就是maven的版本控制,在一个工程的正常生命周期内。
我们可能会因为功能变更、性能提升、修复漏洞等多种原因,去更换第三方库或框架。此时就容易发生版本冲突,本期我们就介绍一下相关知识,以及如何解决版本冲突。
一、版本冲突的原因
内部冲突: 当一个项目直接依赖了不同的版本号,可能会导致冲突。

图片
模块间冲突: 一个库内部不同模块之间使用了不同的版本号,或互相引用时,也可能导致冲突。

图片

图片
二、查看与分析冲突
1. 依赖树
① 原生命令
使用dependency:tree命令查看依赖树:maven提供了一个命令mvn dependency:tree,可以查看项目的依赖树,从而帮助我们分析版本冲突的原因。例如:
mvn dependency:tree
运行上述命令,maven会打印出项目的依赖树,我们可以根据这个依赖树找到冲突的版本并解决它。

图片
② Maven Helper
原生命令的可视化成都有限,所以对于开发者来说,最常用的还是在IDEA上安装maven helper插件了,我们可以在IDEA插件市场里将其安装上

图片
安装并启用成功后,我们打开某pom文件,就能看到该pom下的依赖情况了

图片
2. 冲突分析
① 查看冲突
在安装完Maven Helper后, 我们其实可以直接看到有哪些冲突,如下图,项目上就存在如下的jar包冲突,当我们选中poi-oomxl后,右边则具体显示了造成该冲突的具体情况

图片
不难看出,项目中用了多个不同的组件,而这些组件又使用了不同版本的 poi-oomxl,最终导致在项目中引用了三个不同版本的 poi-oomxl
② maven的版本规则
不难看出,尽管项目中依赖了三个不同的版本,但最后我们实际在项目中存在的却只会有一个 poi-oomxl 组件。那么在发生冲突时,maven 到底会取用哪个版本的组件呢?这就涉及到maven的版本规则
就近原则(最短路径)
多条路径时,选择最短的路径(依赖的层级小),如下,就会选用第二条路径,最后选择的版本为 version 0.0.2
-
A ---> C ---> D ---> E ---> X(version 0.0.1)
-
A ---> F ---> X(version 0.0.2)
声明顺序
在路径相同的情况下,Maven会选择最先声明的版本。如下,就会选用第一条路径,最后选择的版本为 version 0.0.1
-
A ---> C ---> X(version 0.0.1)
-
A ---> F ---> X(version 0.0.2)
我们来看一个例子,这里maven为我们选择了4.1.2,就是因为我们直接在pom文件里指定了版本4.1.2,所以它就只有一层,是最短路径。

图片
如果我们把pom里的直接引用内容注释掉

图片
那么就会用新的最短路径了,最终选取的版本为4.1.1

图片
③ 版本选择
一般来说,我们相信组件都具有"向下兼容"的能力,即低版本组件的功能,在高版本上应该也能使用。所以当出现组件冲突时,我们往往选择保留目前的最高版本。
三、maven解决版本冲突的方法
1. 排除依赖
当我们发现某个依赖引起了冲突,可以使用 maven 的exclude标签排除它。例如:
xml
<dependencies>
<dependency>
<groupId>example.group</groupId>
<artifactId>example.artifact</artifactId>
<version>1.0</version>
<!-- <exclusions> 元素用于排除指定的依赖 -->
<exclusions>
<exclusion>
<groupId>conflict.group</groupId>
<artifactId>conflict.artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
上述代码中,假如 example.artifact 引用了某个版本的 conflict.artifact ,与其他地方引用的conflict.artifact 发生冲突,我们就可以这样,把example.artifact 里的 conflict.artifact 剔除掉。如果是使用插件的话,则更方便,右键选中组件,即可以快速进行 exclude

图片
2. 依赖管理
但是使用 exclusions 也有比较麻烦的地方,exclusions 只对当前依赖有效,并不会影响其他依赖。因此,如果项目中有多个依赖引入了相同的冲突依赖,需要在每个依赖中都使用 exclusions 元素进行排除。所以,有时候我们希望明确指定某个依赖的版本号,可以使用maven的dependencyManagement标签来达到目的
① 单模块
如下 在pom文件中,我们加入dependencyManagement,并在其中指定了poi-ooxml的版本号为4.1.2(注意dependencyManagement只有管理信息的功能,并没有真实引用poi-ooxml,所以后面的引用段落仍然要保留)

图片
然后在引用的段落里,把版本号清除掉

图片
此时我们再去看引用情况,就会发现所有的引用全部变成了 4.1.2,不再有冲突提示

图片
② 跨模块处理
多模块的管理,更加需要使用 dependencyManagement 来确保各子模块在使用相同版本的组件。所以此时需要在父POM文件中加入dependencyManagement
xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>../child1</module>
<module>../child2</module>
<module>../child3</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
然后在所有子模块的pom文件里只保留引用,不再指定版本号,如下
xml
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
</dependencies>
此时,所有子模块凡是引用了poi-ooxml的, 就都会指定使用 4.1.2 版本,而不会再产生冲突了。当然,如果某个子项目需要指定一个特殊的版本号时,只需要在自己项目的pom.xml中显示声明一个版本号即可,因为就近原则的关系,该模块会使用自己指定的版本号
四、结论
在软件开发过程中,版本冲突是一个常见的问题。我们本次就了解maven在发生版本冲突时,该如何查看冲突情况,并知道maven选择哪个版本是遵循就近原则、与声明顺序的。而在处理时我们可以使用排包(exclusive)法,或者显示的使用 dependencyManagement 来指定版本号。