Maven源码-依赖解析
一、依赖解析流程说明
1)通过DefaultModelBuilder#build
生成当前pom的Model,也就是effective pom
。
2)递归解析(深度遍历)effective pom
的child dependency,生成effective pom
。
二、生成effective pom
参考:https://maven.apache.org/ref/3.8.4/maven-model-builder/
源码:org.apache.maven.model.building.DefaultModelBuilder#build
阶段1
1、profile activation
从命令行指定激活的profile
2、raw model validation
当前pom的Raw model校验
3、model normalization
当前pom的model规范化,合并重复声明的依赖
4、profile injection
把激活态profile(命令行指定激活的或者POM中默认激活的)内容合并注入到model中,如配置、依赖等。
5、parent resolution until super-pom
解析当前pom的父POM的model直到SuperPOM。解析完后,lineage内的对象如下。
源码如下:
1 | Collection<String> parentIds = new LinkedHashSet<>(); // 用来判断循环解析 |
6、inheritance assembly
当前pom的model继承合并父POM的配置和依赖等内容。遍历lineage对model进行聚合,从后往前聚合,如下图:
参考源码:org.apache.maven.model.merge.ModelMerger#mergeModel
1 | protected void mergeModel( Model target, Model source, boolean sourceDominant, Map<Object, Object> context ) { |
7、model interpolation
当前pom的model进行变量解释。变量替换时遵循一定顺序(valueSources内的ValurSource顺序)从配置上下文里找。可参考:Maven属性替换
参考:org.apache.maven.model.interpolation.StringVisitorModelInterpolator#interpolateModel
8、url normallization
Url规范化,如一些相对目录的替换。
阶段2:可选的插件处理
1、model path translation
模型中路径(如main路径、target路径等)解析,替换为当前操作系统的文件分隔符、路径分割。
2、plugin management injection
插件约束注入。
3、(optional) lifecyle bindings injection
生命周期绑定注入。合并默认插件至model,默认插件通过*lifecycle.getPluginsBoundByDefaultToAllLifecycles( packaging )*获取。
4、dependency management import
解析model中dependencyManagment中的import依赖,并注入model。会触发import依赖的effective pom解析。
如下所示:
1 | <dependencyManagement> |
从org.apache.maven.model.composition.DefaultDependencyManagementImporter源码可看出是先声明的有效。
顺序:当前pom的dependencyManagment > 当前pom的dependency的import > 父pom中的dependencyManagement > 父pom中的dependencyManagement的import。
5、dependency management injection
根据上一步解析出的依赖版本约束,修改model中依赖的版本。
源码:org.apache.maven.model.management.DefaultDependencyManagementInjector#injectManagement
6、model normalization - inject default values
给model中的dependency和plugin设置默认的scope — compile。
7、(optional) report configuration
8、(optional) report convesion to decoupled site plugin
9、(optional) plugins configuration
10、effective model validation
模型校验,如进行model的groupId,artifactId,version非空校验。
三、递归解析子依赖
MojoExecutor执行Mojo前会调用org.apache.maven.lifecycle.internal.MojoExecutor#ensureDependenciesAreResolved进行依赖的递归解析。
Model是一个多叉数模型,递归解析的过程也是多叉树遍历的过程,Maven再进行解析时通过深度遍历来解析。
观察上面堆栈,发现递归解析发生时逻辑主要在以下两个方法内:
1)org.eclipse.aether.internal.impl.collect.DefaultDependencyCollector#processDependency
2)org.eclipse.aether.internal.impl.collect.DefaultDependencyCollector#doRecurse
递归时需要的参数主要有以下内容:
1 | /** |
1、DefaultDependencyCollector#doRecurse
1)对depSelector、depManager、depTraverser、verFilter进行下钻式的合并,即子从父继承合并属性。
2)把当前处理的依赖放入栈:Args.nodes,该依赖解析完后弹出。可通过此栈观察当前遍历的路径和深度。
2、DefaultDependencyCollector#processDependency
1)根据depSelector
对依赖进行判断是否忽略,如ScopeDependencySelector、OptionalDependencySelector、ExclusionDependencySelector。
2)根据depManager
对依赖进行约束,也就是dependencyManagement标签里的内容。
3)根据depTraverser
判断是否需要递归解析。
4)使用成员属性descriptorReader
解析当前依赖的effective pom。其会调用ModelBuilder#build生成effective pom,执行两个phase。
5)判断是否是relocation
,如果是,则进行重定向解析;如果不是,则进行下一步。
6)根据depTraverser
结果,判断是否进行递归处理。如果需要递归处理则遍历effective pom的依赖,调用DefaultDependencyCollector#doRecurse方法
3、递归解析图示
四、依赖调停
递归解析完子依赖后,会获取一个依赖全集。下一步会调用ChainedDependencyGraphTransformer#transformGraph对依赖进行调停。
1 | /** |
ConflictResolver
参考:org.eclipse.aether.util.graph.transformer.ConflictResolver#transformGraph
1)ConflictIdSorter#transformGraph计算冲突的依赖,并设置到context内。
org.eclipse.aether.util.graph.transformer.ConflictMarker#analyze
深度遍历依赖树,把依赖按gourp:artifact:classifier:extension
进行分组。
2)遍历依赖全集,根据上面的分组判断是否冲突;冲突的加入conflictIds。
3)遍历conflictIds,针对每个冲突都深度遍历依赖全集,获取所有冲突的依赖。
4)对冲突的依赖进行调停,依次调用:VersionSelector(默认实现NearestVersionSelector)、ScopeSelector、OptionalitySelector进行依赖调停。
五、Maven和Aether
以上分析是基于Maven3.X,其依赖解析内容使用了Aether。该项目来自eclipse社区,后引入为maven-resolver项目,edi-core内也有部门实现类。
Aether依赖解析说明参考文章,内容为以上流程的概要说明。