安全同学讲Maven间接依赖场景的仲裁机制
一 背景
- 这样使用dependency的时候,可以缺省version。
- 另外<dependencyManagement> 还可以管控所有的间接依赖,即使间接依赖声明了version,也要被覆盖掉。
3.<parent>
声明自己的父亲,Maven的继承哲学跟Java很类似,因为Maven本身也是用Java实现的,满足单继承。- 一旦子pom继承了父pom,那么会把父pom里的 <dependencies> ,<dependencyManagement>等等属性都继承过来的。当然如果在继承的过程中,出现一样的元素,也是子去覆盖父亲,和Java类似。
- 继承时,会分类继承。dependencies继承dependencies,dependencyManagement里的依赖管理只能继承dependencyManagement范围内的依赖管理。
- 每一个pom文件都会有一个父亲,即使不声明Parent,也会默认有一个父亲。和Java的Object设计哲学类似。后面在源码分析中我们还会提到。
- compile和runtime会参与最后的打包环节,其余的都不会。compile可以不写。
- test只会对 src/test目录下的测试代码起作用。
- provided是指线上已经提供了这个Jar包,打包的时候不需要在考虑他了,一般像servlet的包很多都是provided。
- system和provided没什么太大的区别。
- import只会出现在dependencyManagement标签内的依赖中,是为了解决Maven的单继承。引入了这个作用域的话,maven会把此依赖的所有的dependencyManagement内的元素加载到当前pom中的,但不会引入当前节点。如下图,并不会引入fastjson作为依赖管理的元素,只是会把fastjson文件定义的依赖管理引入进来。
<dependencyManagement><dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.24</version><scope>import</scope></dependency><dependencies><dependencyManagement>
二 单个Pom树的依赖竞争
场景一 难度(*)
场景描述
主POM里有<fastjson.version> 这个属性为1.2.24。 父亲是spring-boot-starter-parent-3.13.0。父亲里的<fastjson.version>是1.2.77。 并且在主pom中,消费了这个属性。 那么针对主POM这颗树,他最终会是使用哪一个fastjson呢?场景示例
结构图
场景二 难度(**)
在同一个主POM或者子POM中的dependencies中同时使用了Fastjson,第一个声明了1.2.24的版本,第二个声明了1.2.25版本。那么针对主POM或者子pom这棵树,最终会选择fastjson 1.2.24还是1.2.25呢?场景示例
结构图
场景三 难度(***)
下图中左图为主POM文件内的dependencyManagement里的fastjson为1.2.77,这个时候子POM中显示声明自己的版本1.2.78。那么针对子POM这颗树,子POM会选择听从父命还是遵从内心呢?场景示例
结构图
场景四 难度(****)
主POM的dependencies Fastjson:1.2.24 主POM的dependencymanagent Fastjson:1.2.77 主POM的父亲(springboot)的dependencies Fastjson 1.2.78 子POM里的dependencies Fastjson 1.2.25 这种情况下针对子pom来说,他会选择4个版本中的哪一个呢?场景示例
结构图
场景五 难度(*****)
主POM的dependencies Fastjson:1.2.24 主POM的dependencymanagent Fastjson:1.2.77 主POM的父亲(springboot)的dependencies Fastjson 1.2.78 子POM里的dependencies 不写version 场景五跟场景四整体没有差别,只是将子pom的dependencies的版本进行缺省。 这种情况下针对子pom来说,针对子pom,他会选择3个版本中的哪一个呢?场景示例
结构图
场景一
1.2.24会最终生效。 因为子会继承父亲的属性,但是由于自己有这个属性,那么则覆盖! 继承一定会伴随着覆盖的,这个设计在编程语言中还是比较普遍的。场景二
1.2.25会最终生效。 参考 单颗树在依赖在竞争时:当deep=1,即直接依赖。同级是靠后优先。 满足Maven的核心竞争依赖策略!场景三
1.2.78最终会生效。 一个项目里的dependencyManagement只能对不声明version的dependency和间接依赖有效!场景四
1.2.25会最终生效。这个比较复杂。 〇: 首先根据父子的继承关系,1.2.24会覆盖掉1.2.78。所以78版本淘汰 一: 由于一个项目里的dependencyManagement只能对不声明version的dependency和间接依赖有效,所以 1.2.77无法对1.2.25起作用。 二: 由于父子的继承关系,1.2.25会覆盖掉1.2.24. 所以最终1.2.25胜出!场景五
1.2.77会最终生效。 〇: 首先根据父子的继承关系,1.2.24会覆盖掉1.2.78。所以78版本淘汰 一: 由于一个项目里的dependencyManagement是可以对不声明的version起作用,所以子pom的版本为1.2.77 二: 由于父子的继承关系,1.2.77会覆盖掉1.2.24. 所以最终1.2.77胜出!三 多个Pom树合并打包
多棵树构建顺序原则
四 仲裁机制在Maven源码中的实现
以Maven的3.6.3版本的源码进行分析,我们尝试分析Maven中对依赖处理的几处原则,方能从源码的层面上正向的证明仲裁机制的准确性。另外从源码上也可以看出一些Maven上的机制为什么是这样,而不是单单的他的机制是什么样。因为笔者相信,任何机制都无法保证与时俱进下的先进性,所以笔者认为上文中提到的所有的仲裁机制有一天可能会发生变化,这些结论并非最重要,而是如何调研这些结论更为重要!
五 安全视角应如何避免间接依赖
1.子pom声明版本在安全视角是非常危险的,子pom不应该显示声明版本。
由于子pom会继承主pom的元素,并且在继承的时候会出现覆盖的场景。那么针对CE或者SpringBoot打包时,有可能出现子pom的build的顺序位置天然非常有优势,容易造成子pom的版本进入最终的打包产物。2.主POM的dependencyManagent可以管控到 间接依赖 和 不显示声明version的直接依赖。
六 最后
Maven的源码地址
https://archive.apache.org/dist/maven/maven-3/我是怎么分析的
本人在本地针对SpringBoot,做多轮测试。在根目录下执行mvn clean package即可! mvn clean org.apache.maven.plugins:maven-dependency-plugin:3.3.0:tree -Dverbose=true 会帮助分析到具体的节点。 另外就是尝试在源码中找到这里的实现,这样更能加深理解!常用的分析命令
0. mvn clean package -DSkipTest 直接进行打包,进行结果分析 1. mvn dependency:tree 会把整个的maven的树形结构输出 2.mvn help:effective-pom -Dverbose 这个命令输出的信息更加完整,输出的是effectivepom 3.mvn clean org.apache.maven.plugins:maven-dependency-plugin:3.3.0:tree -Dverbose=true 4.mvn -D maven.repo.local =你的目录 compile阶段用到的依赖。推荐阅读
3. 如何做好“防御性编码”?
体验阿里云自主研发的云原生关系型数据库产品,100% 兼容 PostgreSQL,高度兼容Oracle语法;采用基于 Shared-Storage 的存储计算分离架构,具有极致弹性、毫秒级延迟、HTAP 的能力和高可靠、高可用、弹性扩展等企业级数据库特性。发布评测,写下你的感受与评价即可获得多重福利。
点击阅读原文查看详情。