前言
我们在使用maven构建项目时,可能会遇到很引用库依赖冲突的问题,而引用的库越多出现冲突的可能性就越大;本文将对此问题提供几个解决方案,并且帮助读者能够更好的理解什么时版本冲突,怎样更好的去避免它。
场景
首先,想象一下,你作为一个研发人员参与了一个大型且很有趣的项目,这个项目引用许多方便有用的第三方技术库。maven的好处就是非常方便高效的管理这些依赖库的下载、版本选择以及更新。某天,你发现了一个第三方库能够很好的解决在项目中的一个认证功能模块,并且可以减少大量的工作;当你引入之后,却发现程序中某一个地方出现的错误提示;而且这个地方之前从来没有被出错过,也没有人修改过;首先想到的就是自己的代码导致的,应该是自己做了什么不正确的改动;与此同时你也很确定,功能代码是没有问题的。通过调试,可能出现一些类似 NullPointerException
或者 NoSuchMethodException
的异常信息。最直接的操作可能是把依赖库更新到最新版本,但是这样解决不了任何问题;下一步可能是检查这个类库的代码,发现用到的发现或者类不一定都存在,更新到最新版本之后,依然可能出现这样的问题。
更进一步,你通过查看本地的maven依赖仓库,发现仓库下面有同一个依赖库的两个不同版本,你的项目却引用的是一个旧的版本,但是却又不知道为什么项目引用到旧的版本…
恭喜你,你发现了引用库的版本冲突导致项目挂掉的问题!是你在引用一个第三方库时导致。
问题
为什么会出现版本冲突问题呢?
因为引用库也可以引用其他的库,而其他的库却引用了另外的不同版本库。通过下面的依赖关系图可能更清楚的描述这个关系,图片中X表示项目:
通过上面的图片,可以清楚的看到,项目X总共引用了Y,G,Z三个库;显然,在这种情况下 Z 库会添加到项目引用当中,即便你不知道这个库的存在; 了解更多maven的传递依赖;因为Y,G两个库依赖于不同版本Z,从而导致了依赖版本冲突,因此不能同时引用Z库的两个版本;如果引用库的版本不匹配,则会导致程序出错或者直接崩溃;我们假设Z库就是导致项目出错的元凶,那么就需要明确是Z库的哪个版本导致的。
哪个版本库是项目正在引用的?
项目X是用的maven的默认机制Dependency Mechanism来处理如何引用以及引用哪个版本的库。
简单说就是 :层级不同,上层先用;层级相同,定义先行。
在pom.xml文件中,同一个库的哪一个版本先定义的,在引用时就会先引用哪个版本的库,库的定义层级离项目越近,则优先使用;相当于对依赖树进行广度遍历,先遍历到的先使用。
如何解决版本冲突呢?
至少有两种以上的方式能够解决上面描述的版本冲突问题;通过上面的描述,最快也是最简的一种方式就是在项目X的pom.xml文件中引用G库时要先于引用Y库。更好点是直接在项目X的pom.xml文件中引用Z库的2.0版本,这样有一个前提就是Z的版本是向后兼容的,能够兼容Y库的引用;如果不能兼容,则这样处理就存在稳定性风险。
提示:
依赖库的版本冲突并不总是导致项目崩溃,但是也要特别注意哪些没有导致程序崩溃的版本冲突;尤其是项目中引用了大量的依赖库的情况。
如何快速的发现版本冲突问题?
上面给出的例子很简单,也可以很快的解决掉;但是在实际项目过程中,找到版本冲突,并不是一项简单的工作;即便你早就确定并不是代码逻辑问题。
这时可能就需要一个好用的工具或者插件可以快速的帮助我们在项目中筛选出有版本冲突的库 。Enforcer – the loving iron fist of Maven 就是一个非常适合的maven插件,这个插件可能添加很多规则,但我们最感兴趣的一个就是dependencyConvergence 。
在项目的pom.xml文件中添加如下插件
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<configuration>
<rules><dependencyConvergence/></rules>
</configuration>
</plugin>
</plugins>
添加之后,你就可以执行enforcer:enforce 命令,查找出有版本冲突的库;
mvn enforcer:enforce
一旦执行之后就会以树的形式显示出项目中所有版本冲突的依赖信息
Dependency convergence error for log4j:log4j:1.2.17 paths to dependency are:
+-com.ricston.conflict:conflict-info:2.1.3-SNAPSHOT
+-org.slf4j:slf4j-log4j12:1.7.6
+-log4j:log4j:1.2.17
and
+-com.ricston.conflict:conflict-info:2.1.3-SNAPSHOT
+-log4j:log4j:1.2.16
其中conflict-info就是我们的项目名称;在上面的例子中,log4j库引用了两个版本,分别是1.2.17版本和1.2.16版本;1.2.16版本有项目conflict-info直接引用,1.2.17版本是依赖于slf4j-log4j12引用库;在编译过程中1.2.16版本的log4j库会优先引用 。
需要注意的时,并不是所有冲突都会导致项目崩溃,因为Maven的Dependency Mechanism已经帮我们处理了。
最后,一个很好的方式来避免版本冲突导致的错误,就是把依赖库直接提取到项目中,定义在最上层级别,确保下面所有引用的库都是同样的版本。
总结
上面提供的只不过是相对简单的一种处理方法,还有许多其他的处理方式,甚至有专门讲解maven版本冲突的书籍,大家可以自行研究
- https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
- http://maven.apache.org/plugins/maven-dependency-plugin/examples/resolving-conflicts-using-the-dependency-tree.html
- http://maven.apache.org/enforcer/maven-enforcer-plugin/
书籍:
Apache Maven Dependency Management by Jonathan Lalou.
原文链接:https://dzone.com/articles/solving-dependency-conflicts-in-maven