依赖调停

Dependency Mediation | 依赖调停

传递性依赖是在 Maven 中添加的新特征,这个特征的作用就是你不需要考虑你依赖的库文件所需要依赖的库文件,能够将依赖模块的依赖自动的引入。例如我们依赖于 Spring 的库文件,但是 Spring 本身也有依赖,如果没有传递性依赖那就需要我们了解 Spring 项目依赖,然后也要添加到我们的项目中。

由于没有限制依赖的数量,如果出现循环依赖的时候会出现问题,这个时候有两种方式处理,一种是通过 build-helper-maven-plugin 插件来规避,另一种就是重构两个相互依赖的项目。通过传递性依赖,项目的依赖结构能够很快生成。但是因为这个新的特性会有一些其他的特性被添加进来来限制由于传递性依赖所引入的包。

依赖调节:如果在一个项目里面出现不同的模块,依赖了一个项目的不同版本的时候判断依赖的版本。Maven 的时候仅仅支持最近原则也就是在依赖树中的最靠近项目的版本作为依赖版本。到了 Maven 的时候又提出了一个最先声明原则,也就是在项目中被最早声明的被判断为依赖的版本。

同一个 jar 包,又会有不同的版本,当你所依赖的 jar 有不同的版本的时候,比如你的 A 项目: A -> B -> C -> D 2.0 and A -> E -> D 1.0。这就出现了冲突,你当然也可以再 A 里面配置指定依赖 D2.0。如果你没在 A 的 pom 里面配置指定用哪一个。maven 会替你找一个近的,即 D1.0。

Excluded & Optional Dependencies | 排除依赖于可选依赖

可选依赖使用的情况是对于某个依赖来说系统只有在某个特定的情况下使用到它。例如数据库驱动,有 mysql 的,oracle 的。只有在我们使用到 mysql 的时候才会被使用。

排包

再次回顾下 Maven 中的包依赖规则如下:

  • compile(默认使用):编译依赖范围,编译、测试、运行都生效;
  • test:测试依赖范围,只在测试的 classpath 有效,比如我们常用的 JUnit;
  • provided:只对编译和测试有效,对运行无效,常用于容器提供了的运行环境;
  • runtime:运行时依赖范围,比如 JDBC 驱动,编译和测试时不需要,只需要使用 JDK 提供的 JDBC 接口;
  • system:系统依赖范围,依赖 Maven 仓库以外的依赖。

仲裁规则

  • 第一原则:路径最短优先选中,将 pom 文件默认为一个树形结构,路径节点从根出发。

假设:路径 1 是:A->B->C->D(version1.0),路径 2 是:A->B->D(version2.0)。由于路径 2 的长度小于路径 1,所以结果就是 A 会传递性依赖 D(version2.0),当路径长度相等的时候,使用第二原则

  • 第二原则:先声明者优先原则

当路径长度相同时候,就要使用第二原则。假设:路径 1 不变依然是:A->B->C->D(version1.0),路径 2 是:A->B->E->D(version2.0),由于路径 1 在路径 2 前面声明,所以结果就是 A 会传递性依赖 D(version1.0)。

  • 第三原则:冲突 jar 句柄顺序随机原则

如果系统自己已经在 target 目录中加载了有冲突的 jar 包,并且机器有一定概率可以启动成功,主要是当打包的时候包会打印到 target 目录中,编译机器和目标机器执行了打包上传解压,在这个过程中就会引起互相冲突的 jar 在目标机器上排序,系统启动的时候,加载包的顺序就是随机的。

项目排包

mvn dependency:tree>tree.log 直接执行这个命令,基本上只能先优先看一下总体的依赖状况,做整体评估,然后根据系统日志报错信息反查。

  • 启动时直接报错,java.lang.ClassNotFoundException ,或者方法不存在:这种比较简单,直接查找该类在哪个包中,相互冲突的基本上在两个 jar 包内就可以确定,基本手段:排除低版本,选择和自己 Java 代码一致使用的,排除和业务代码无关的。

  • 类路径长度统计,版本就高不就低。这点需要找到有疑似冲突的,做一个简单的统计,常常新版本的会解决之前的问题,但是也有一个潜在的风险,高版本的可能引入了更多有可能的冲突,因此,高版本的尽量遵循业务无关的类库,如果和业务有关,尽量把两个版本的依赖树都保存以做比对,或者和具体业务负责人确认改动点。

  • 隐含错误加上调试参数:mvn +X dependency:tree>tree.log,这样根据报错信息可以更精确的定位到错误或者冲突。

上一页
下一页