本文是系列文章《Maven 源码解析:依赖调解是如何实现的?》第六篇,主要介绍 dependencyManagement 版本锁定原则。请按顺序阅读其他系列文章,系列文章总目录参见:https://www.cnblogs.com/xiaoxi666/p/15583241.html。@H_404_1@
场景
我们在根模块 mavendependencyDemo 中,用 dependencyManagement 的形式直接指定 X 的版本为 2.0。同时,A 依赖 C,而 C 依赖了 X(1.0)。我们观察下,最终 A 会依赖 X(1.0)还是 X(2.0)。@H_404_1@
@H_404_1@
根模块 mavendependencyDemo 的 pom.xml 文件内容:@H_404_1@
<?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>
@H_404_1@
<?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>
@H_404_1@
使用命令 mvn dependency:tree -Dverbose 命令,看看依赖树:@H_404_1@
[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)。@H_404_1@
(version managed from 1.0)
@H_404_1@
稍后,我们将根据这个关键信息去找一下对应的源码。@H_404_1@
@H_404_1@
阅读源码之前,我们先到 Maven 官网 https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html 探索一下,发现有一句话:@H_404_1@
@H_404_1@
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.@H_404_1@
@H_404_1@
简单翻译一下,使用 dependencyManagement 声明的依赖若指定了版本,可以:@H_404_1@
当然,如果使用 dependencyManagement 声明的依赖没有指定版本,传递依赖的自定义版本就会生效了。@H_404_1@
我们的场景,显然对应着第一种描述:dependencyManagement 可以指定传递依赖的版本(即使传递依赖有自定义版本,也会被覆盖掉),其实就是版本锁定的概念了。@H_404_1@
@H_404_1@
源码
顺着关键信息,我们找到了 maven-dependency-tree 的源码段:@H_404_1@
@H_404_1@
@H_404_1@
可以看到是由 getPremanagedVersion 这个方法控制的,那我们进去看看它干了啥:@H_404_1@
@H_404_1@
@H_404_1@
可以看到,该方法的作用就是:获取「被 dependencyManagement 中定义的版本号更新之前」的版本号,顺藤摸瓜,我们看看 premanagedVersion 是在哪里被赋值的:@H_404_1@
@H_404_1@
@H_404_1@
可以看到有两个赋值的地方(this.xxx 那个只是个简单的 set 方法),那我们都打上断点,重新调试:@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
可以看到,是 DependencyTreeResolutionListener 实现了 Maven 核心项目中的 org.apache.maven.artifact.resolver.ResolutionListener#includeArtifact 方法。@H_404_1@
@H_404_1@
@H_404_1@
那回到 Maven 核心项目,看看 includeArtifact 方法被哪里调用了:@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
看到很奇怪的现象,C 依赖的 X 变成 2.0 版本了,但是 C 中明明是 X(1.0)啊,到底是哪里把它改了呢?@H_404_1@
@H_404_1@
同时,我们会发现 managedVersions 也有值了(以前是没有的),而且正好是 dependencyManagement 中定义的 X(2.0):@H_404_1@
@H_404_1@
@H_404_1@
我们重新调试,继续探索,看看是不是 managedVersions 动了什么手脚。还是进入递归方法。@H_404_1@
可以看到,解析 C 依赖的 X(1.0)时,被“manage”了,也即:版本被改成了 2.0。@H_404_1@
很明显,版本号是以 managedVersions 为准的。@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
现在我们知道了 X(1.0)是如何被改成了 X(2.0),那么问题来了,上面说的 managedVersions 又是从哪里来的呢?@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
可以看到,dependency:tree 插件是 magenedVersions 的「出生地」,这也是这个插件被称作核心插件的原因。因此我们根据调用栈的提示,再到这个插件项目的 org.apache.maven.shared.dependency.tree.DefaultDependencyTreeBuilder#buildDependencyTree 看看:@H_404_1@
@H_404_1@
@H_404_1@
可以看到是从 project 中获取到的:@H_404_1@
@H_404_1@
@H_404_1@
那么我们需要继续看 project 是在哪里出生的:@H_404_1@
@H_404_1@
@H_404_1@
看到这里,已经发现 project 是从 maven-dependency-plugin 中传过来的,那么我们转到这个 TreeMojo 中继续调试:@H_404_1@
@H_404_1@
@H_404_1@
可以看出,project 其实是从 Maven 核心项目传入的,那我们继续回到 Maven 核心项目中调试:@H_404_1@
@H_404_1@
@H_404_1@
往上翻翻,发现 project 是从 session 中获取到的:@H_404_1@
@H_404_1@
@H_404_1@
到此,我们发现一种包含关系:session -> currentProject -> managedVersionMap。赋值的地方比较多,在不熟悉源码的情况下,我们把所有 setCurrentProject 和 setManagedVersionMap 的地方都打上断点,看看哪里进行了赋值。最终我们找到了这里:@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
@H_404_1@
至此,我们找到了「解析 dependencyManagement 中定义的依赖版本」的源码。@H_404_1@
@H_404_1@
小结
依赖解析过程中,会解析 dependencyManagement 定义的依赖版本,如果解析到了,会以 dependencyManagement 重定义的依赖版本为准,也就是我们常提到的版本锁定。@H_404_1@
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。