【Maven实战】——Jar包冲突解决方案

基础知识回顾

你是否在运行程序的时候经常会遇到 类似NoSuchMethodError,ClassNotFoundException这样的错误?而明明引入了这个依赖,还是找不到,我引入的是2.0.0版本的,为什么使用的确是另一个版本呢?其实,如果我们理解了maven的依赖原理,这些问题就能够轻松的解决啦!

maven依赖范围与传递性依赖

依赖范围

maven中有五种依赖范围,分别是:

  • compile 编译依赖范围(默认)
  • test 测试依赖范围,例如:JUnit
  • provide 已提供的依赖范围,对于编译和测试有效,但运行无效,例如servlet-api,编译和测试项目的时候需要此依赖,但运行时,容器已经提供,无需此依赖。
  • runtime 运行时依赖范围。对于测试和运行时需要,但编译时不需要。例如:JDBC驱动,项目编译时,使用的是JDK提供的JDBC接口,在执行测试和运行代码时需要实现JDBC接口的驱动。
  • system 系统依赖范围。使用时,必须通过systemPath元素显示的指定依赖文件路径。而不通过maven仓库所解析。

传递性依赖和依赖范围

【Maven实战】——Jar包冲突解决方案
最左边一行为第一直接依赖,最上边一行为第二直接依赖范围。

  • 当第二直接依赖范围为compile,最终依赖为第一直接依赖范围
  • 当第二直接依赖范围为test时,依赖不传递
  • 当第二直接依赖范围为provided时,只有第一直接依赖同为provided时,才会传递依赖,且最终依赖的范围为provided
  • 当第二直接依赖范围为runtime时,除第一直接依赖范围为compile的,传递依赖为runtime,其他的均为第一直接依赖范围。

maven依赖调解

maven引入了传递性依赖,简化和方便了依赖声明,使得我们大部分情况下只需要考虑项目直接依赖了什么?而不用考虑这些直接依赖会引入什么传递性依赖。但有时,传递性依赖会引发一系列问题,此时,我们要搞清楚传递性依赖是从哪里引入进来的。
maven自带的依赖调解原则,能够解决大部分的问题:

  • 优先路径最短原则 例如A有如下依赖关系:A->B->C(0.2.0),A->B->D->C(0.2.1),那么项目A最终依赖项目C的版本为0.2.0,因为此条路径最短。
  • 优先第一声明原则 有的时候当依赖关系路径一致的时候,优先路径最短原则不能够解决问题。这个时候就需要依靠第一声明原则,POM依赖中声明的顺序,决定了谁会被解析使用。例如:A->B->C(0.2.0),A->D->C(0.2.1),则项目A最终依赖项目C的版本为0.2.0。

最佳实践

理解了maven依赖的基本原理知识之后,最重要的是如何使用了,如何在使用的过程中避坑,那就需要站在巨人的肩膀上,方便避免和处理常见问题。

  • 排除依赖 如果A->B->C,但是A不想依赖于C,那就需要对依赖进行排除(使用exclusions元素声明排除依赖。)
  • 归类依赖 同一项目不同模块的依赖,版本是相同的,方便升级时统一修改版本号,我们需要将版本号抽象成常量,避免重复,方便管理。(通过properties元素来定义版本号)
  • 优化依赖 去除多余依赖,显示声明必要依赖
    可以通过如下命令进行查看优化:
    查看maven依赖列表:mvn:dependence list
    查看maven依赖树:mvn:dependence tree
    maven依赖分析:mvn:dependence analyze

实战-jar包冲突如何解决?

了解了依赖原理知识,站在巨人的肩膀上使用最佳实践规范能够帮助我们规避大部分问题,但不能解决全部问题,如果遇到了jar包冲突,我们又该如何解决呢?

日常工作中,我们使用IDEA来编码的话,可以使用idea的maven helper插件来帮助我们定位和分析问题(插件安装和使用方法自行百度):
【Maven实战】——Jar包冲突解决方案
这个插件能展示出来项目POM的依赖树,以及相关冲突会标红展示。我们可以通过两个方式来解决提示的冲突。

  • 排除指定依赖 在显示的冲突中,右键exclude排除掉不想使用的版本号。
  • 版本锁定(第一声明优先原则)如果使用的版本太多,一个个的排除太麻烦,我们可以再父POM中定义最终使用的版本号,依据第一声明优先原则,项目最终依赖的版本号即在父POM中定义的版本号。

总结

首先,我们要依照最佳实践原则,去写更清晰的POM文件,这样能够避免大部分的问题。其次,遇到了jar包冲突的问题,我们要使用相关工具快速对问题进行定位分析,最终,使用maven依赖的原则进行解决问题。