Maven 依赖调解源码解析(六):dependencyManagement 版本锁定
本文是系列文章《Maven 源码解析:依赖调解是如何实现的?》第六篇,主要介绍 dependencyManagement 版本锁定原则。请按顺序阅读其他系列文章,系列文章总目录参见:https://www.cnblogs.com/xiaoxi666/p/15583241.html。
场景
我们在根模块 mavenDependencyDemo 中,用 dependencyManagement 的形式直接指定 X 的版本为 2.0。同时,A 依赖 C,而 C 依赖了 X(1.0)。我们观察下,最终 A 会依赖 X(1.0)还是 X(2.0)。
根模块 mavenDependencyDemo 的 pom.xml 文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>mavenDependencyDemo</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<modules>
<module>A</module>
<module>B</module>
<module>C</module>
<module>D</module>
<module>X</module>
</modules> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>X</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
</dependencyManagement> </project>
A 的 pom.xml 内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>mavenDependencyDemo</artifactId>
<groupId>org.example</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion> <artifactId>A</artifactId>
<version>1.0</version> <dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>C</artifactId>
<version>1.0</version>
</dependency>
</dependencies> </project>
使用命令 mvn dependency:tree -Dverbose 命令,看看依赖树:
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------------< org.example:A >----------------------------
[INFO] Building A 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ A ---
[INFO] org.example:A:jar:1.0
[INFO] \- org.example:C:jar:1.0:compile
[INFO] \- org.example:X:jar:2.0:compile (version managed from 1.0)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.410 s
[INFO] Finished at: 2021-11-20T16:31:03+08:00
[INFO] ------------------------------------------------------------------------
可以看到使用了 X(2.0),也就是 dependencyManagement 中指定的 X(2.0)。
另外输出了一行关键信息:
(version managed from 1.0)
稍后,我们将根据这个关键信息去找一下对应的源码。
阅读源码之前,我们先到 Maven 官网 https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html探索一下,发现有一句话:
Dependency management - this allows project authors to directly specify the versions of artifacts to be used when they are encountered in transitive dependencies or in dependencies where no version has been specified.
简单翻译一下,使用 dependencyManagement 声明的依赖若指定了版本,可以:
指定传递依赖的版本(即使传递依赖有自定义版本,也会被覆盖掉);
当直接依赖没有指定版本时,指定其版本。
当然,如果使用 dependencyManagement 声明的依赖没有指定版本,传递依赖的自定义版本就会生效了。
我们的场景,显然对应着第一种描述:dependencyManagement 可以指定传递依赖的版本(即使传递依赖有自定义版本,也会被覆盖掉),其实就是版本锁定的概念了。
源码
顺着关键信息,我们找到了 maven-dependency-tree 的源码段:

可以看到是由 getPremanagedVersion 这个方法控制的,那我们进去看看它干了啥:

可以看到,该方法的作用就是:获取「被 dependencyManagement 中定义的版本号更新之前」的版本号,顺藤摸瓜,我们看看 premanagedVersion 是在哪里被赋值的:

可以看到有两个赋值的地方(this.xxx 那个只是个简单的 set 方法),那我们都打上断点,重新调试:


可以看到,是 DependencyTreeResolutionListener 实现了 Maven 核心项目中的 org.apache.maven.artifact.resolver.ResolutionListener#includeArtifact 方法。

那回到 Maven 核心项目,看看 includeArtifact 方法被哪里调用了:

继续顺着调用链往上找,

看到很奇怪的现象,C 依赖的 X 变成 2.0 版本了,但是 C 中明明是 X(1.0)啊,到底是哪里把它改了呢?
同时,我们会发现 managedVersions 也有值了(以前是没有的),而且正好是 dependencyManagement 中定义的 X(2.0):

我们重新调试,继续探索,看看是不是 managedVersions 动了什么手脚。还是进入递归方法。
可以看到,解析 C 依赖的 X(1.0)时,被“manage”了,也即:版本被改成了 2.0。
很明显,版本号是以 managedVersions 为准的。


现在我们知道了 X(1.0)是如何被改成了 X(2.0),那么问题来了,上面说的 managedVersions 又是从哪里来的呢?
让我们继续顺着调用链寻找:

可以看到,dependency:tree 插件是 magenedVersions 的「出生地」,这也是这个插件被称作核心插件的原因。因此我们根据调用栈的提示,再到这个插件项目的 org.apache.maven.shared.dependency.tree.DefaultDependencyTreeBuilder#buildDependencyTree 看看:

可以看到是从 project 中获取到的:

那么我们需要继续看 project 是在哪里出生的:

看到这里,已经发现 project 是从 maven-dependency-plugin 中传过来的,那么我们转到这个 TreeMojo 中继续调试:

可以看出,project 其实是从 Maven 核心项目传入的,那我们继续回到 Maven 核心项目中调试:

往上翻翻,发现 project 是从 session 中获取到的:

到此,我们发现一种包含关系:session -> currentProject -> managedVersionMap。赋值的地方比较多,在不熟悉源码的情况下,我们把所有 setCurrentProject 和 setManagedVersionMap 的地方都打上断点,看看哪里进行了赋值。最终我们找到了这里:



至此,我们找到了「解析 dependencyManagement 中定义的依赖版本」的源码。
小结
依赖解析过程中,会解析 dependencyManagement 定义的依赖版本,如果解析到了,会以 dependencyManagement 重定义的依赖版本为准,也就是我们常提到的版本锁定。
Maven 依赖调解源码解析(六):dependencyManagement 版本锁定的更多相关文章
- Maven 依赖调解源码解析(一):开篇
本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第一篇,主要做个开头介绍.并为后续的实验做一些准备.系列文章总目录参见:https://www.cnblogs.com/xia ...
- Maven 依赖调解源码解析(七):总结
本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第七篇,也是最后一篇,主要做个总结.请按顺序阅读其他系列文章,系列文章总目录参见:hhttps://www.cnblogs.c ...
- Maven 依赖调解源码解析(二):如何调试 Maven 源码和插件源码
本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第二篇,主要介绍如何调试 Maven 源码和插件源码.系列文章总目录参见:https://www.cnblogs.com/xi ...
- Maven 依赖调解源码解析(三):传递依赖,路径最近者优先
本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第三篇,主要介绍依赖调解的第一条原则:传递依赖,路径最近者优先.本篇内容较多,也是开始源码分析的第一篇,请务必仔细阅读,否则后 ...
- Maven 依赖调解源码解析(四):传递依赖,第一声明者优先
本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第四篇,主要介绍依赖调解的第二条原则:传递依赖,第一声明者优先.请按顺序阅读其他系列文章,系列文章总目录参见:https:// ...
- Maven 依赖调解源码解析(五):同一个文件内声明,后者覆盖前者
本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第五篇,主要介绍同一个文件内声明,后者覆盖前者的原则.请按顺序阅读其他系列文章,系列文章总目录参见:https://www.c ...
- Celery 源码解析六:Events 的实现
在 Celery 中,除了远程控制之外,还有一个元素可以让我们对分布式中的任务的状态有所掌控,而且从实际意义上来说,这个元素对 Celery 更为重要,这就是在本文中将要说到的 Event. 在 Ce ...
- AFNetworking (3.1.0) 源码解析 <六>
这次继续介绍文件夹Serialization下的类AFURLResponseSerialization.这次介绍就不拆分了,整体来看一下.h和.m文件. 协议AFURLResponseSerializ ...
- ReactiveCocoa源码解析(六) SignalProtocol的take(first)与collect()延展实现
上篇博客我们聊了observe().map().filter()延展函数的具体实现方式以及使用方式.我们在之前的博客中已经聊过,Signal的主要功能是位于SignalProtocol的协议延展中的, ...
随机推荐
- k8s deployment controller源码分析
deployment controller简介 deployment controller是kube-controller-manager组件中众多控制器中的一个,是 deployment 资源对象的 ...
- 🚴♂️全套MySQL数据库教程_Mysql基础入门教程,零基础小白自学MySQL数据库必备教程☔ #002 # 第二单元 MySQL数据类型、操作表#
二.本单元知识点概述 (Ⅰ)知识点概述 二.本单元教学目标 (Ⅰ)重点知识目标 1.Mysql的数据类型2.如何选择数据类型3.创建表4.修改表5.删除表 (Ⅱ)能力目标 1.熟练创建数据库及删除数据 ...
- uniapp小程序迁移到TS
uniapp小程序迁移到TS 我一直在做的小程序就是 山科小站 也已经做了两年了,目前是用uniapp构建的,在这期间也重构好几次了,这次在鹅厂实习感觉受益良多,这又得来一次很大的重构,虽然小程序功能 ...
- 活动回顾|ShardingSphere X openGauss,将会产生怎样的化学反应?
"ShardingSphere 作为 openGauss 生态的开源分布式数据库解决方案,将持续助力于 openGauss,满足千行百业广大客户分布式场景需求." 5月29日,由 ...
- MyBatis的框架设计
1.MyBatis的框架设计 2.整体设计 2.1 总体流程 (1)加载配置并初始化 触发条件:加载配置文件 配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信 ...
- airtext初始化(一)
- 灵光一闪!帮你使用Vue,搞定无法解决的“动态挂载”
在一些特殊场景下,使用组件的时机无法确定,或者无法在Vue的template中确定要我们要使用的组件,这时就需要动态的挂载组件,或者使用运行时编译动态创建组件并挂载. 今天我们将带大家从实际项目出发, ...
- [no code][scrum meeting] Beta 7
$( "#cnblogs_post_body" ).catalog() 例会时间:5月21日15:30,主持者:彭毛小民 下次例会时间:5月22日15:30,主持者:赵涛 昨日为5 ...
- Noip模拟13 2021.7.13:再刚题,就剁手&&生日祭
T1 工业题 这波行列看反就非常尴尬.....口糊出所有正解想到的唯独行列看反全盘炸列(因为和T1斗智斗勇两个半小时...) 这题就是肯定是个O(n+m)的,那就往哪里想,a,b和前面的系数分开求,前 ...
- 《手把手教你》系列技巧篇(三十五)-java+ selenium自动化测试-单选和多选按钮操作-下篇(详解教程)
1.简介 今天这一篇宏哥主要是讲解一下,如何使用list容器来遍历多选按钮.大致两部分内容:一部分是宏哥在本地弄的一个小demo,另一部分,宏哥是利用JQueryUI网站里的多选按钮进行实战. 2.d ...