# Maven 依赖安全升级规范指引
> **核心原则:先定位源头,再采集基线,改最少的地方,验证最全面的效果**
本规范适用于多模块 Maven 项目的依赖版本升级工作,确保升级过程安全、不遗漏、不多改。
---
## 阶段一:信息收集与分析
### 1. 明确升级目标
- 确定要升级哪些依赖(groupId:artifactId)
- 确定目标版本号
- 查阅官方 Release Notes 确认兼容性(尤其是大版本跳跃)
- 确认目标版本与当前 JDK 版本的兼容性
### 2. 定位版本源头(最关键)
版本声明可能分布在以下位置,必须**从源头修改**,否则会遗漏或多改:
| 优先级 | 位置 | 说明 |
|-------|------|------|
| 1 | `<properties>` 变量 | 父 POM 的 properties 区块中定义的版本变量,修改一处全局生效 |
| 2 | `<dependencyManagement>` 中直接声明的版本 | 父 POM 或子模块 POM 的 dependencyManagement 中硬编码的 version |
| 3 | BOM/import 引入的版本 | parent POM 或 import scope 的 BOM 管理的版本(影响面大) |
| 4 | 子模块 POM 中硬编码的版本 | 子模块 pom.xml 中直接写死的版本号,需逐一扫描修改 |
**定位规则:**
- 如果一个依赖的版本由 `<properties>` 变量控制 → 只改变量
- 如果一个依赖在 `<dependencyManagement>` 中硬编码 → 改对应的 `<version>` 标签
- 如果一个依赖由 BOM(如 spring-boot-starter-parent)管理 → 升级 BOM 版本本身
- 如果子模块绕过了父 POM 直接写版本号 → 逐一修改子模块
### 3. 扫描所有引用点
- 全文搜索该依赖在所有 pom.xml 中的出现位置
- 检查 `<exclusions>` 中是否排除了相关传递依赖
- 检查是否存在**硬编码版本号**绕过 properties 变量的情况
- 检查是否有同名依赖在多处声明但版本不一致
---
## 阶段二:升级前快照(基线采集)
### 4. 采集升级前的 dependency:tree
```bash
# 项目根目录执行,所有模块都会输出
mvn dependency:tree -DoutputFile=dep-tree-before.txt -DoutputType=text
```
- 每个模块都要采集,作为升级前的基线
- 这是后续**差异对比**的唯一依据
- 建议将基线文件保存在临时目录,避免误提交
### 5. 确保当前可编译通过
```bash
mvn clean compile -q
```
- 如果升级前就编译失败,先修复再升级,避免问题叠加
- 记录编译通过的状态作为起点
---
## 阶段三:执行修改
### 6. 按优先级修改版本
修改顺序:
1. **properties 变量优先**:如果版本通过 `<properties>` 管理,只改变量即可全局生效
2. **dependencyManagement 次之**:如果版本在 `<dependencyManagement>` 中硬编码,修改对应 `<version>` 标签
3. **BOM/parent 升级**:如果依赖由 BOM 管理,需升级 BOM 版本本身(影响面大,需格外谨慎)
4. **子模块硬编码最后**:如果子模块 POM 中直接写了版本号,逐一修改
### 7. 修改纪律 — 只改必要的地方
- **不要** 顺手格式化、重排、删除注释
- **不要** 修改非目标依赖的版本
- **不要** "顺便"重构 pom 结构
- **不要** 修改 `<exclusions>` 除非升级后确认需要调整
- 每次只升级一个或一组强关联的依赖,避免多个无关依赖同时升级导致问题难以定位
---
## 阶段四:升级后验证
### 8. 采集升级后的 dependency:tree
```bash
mvn dependency:tree -DoutputFile=dep-tree-after.txt -DoutputType=text
```
### 9. 对比前后差异
对比 `dep-tree-before.txt` 与 `dep-tree-after.txt`,关注四类变化:
| 变化类型 | 关注点 |
|---------|--------|
| 版本变更 | 目标依赖是否升到了预期版本 |
| 新增传递依赖 | 升级是否引入了意外的新 jar(可能带来冲突或体积膨胀) |
| 删除传递依赖 | 升级是否丢失了必要的传递依赖(可能导致运行时 ClassNotFoundException) |
| 版本冲突 | 是否出现了 `omitted for conflict` 警告(可能需要添加 exclusion 或统一版本) |
### 10. 编译验证
```bash
# 第一步:验证 POM 合法性
mvn clean validate
# 第二步:验证代码编译兼容性
mvn clean compile
```
### 11. 按需执行测试和打包
```bash
# 如果项目有测试用例
mvn test
# 验证完整打包流程
mvn package
```
- test 和 package 按项目实际情况选择性执行
- 如果项目配置了 `<skipTests>true</skipTests>`,需评估是否需要临时开启
---
## 阶段五:风险审查
### 12. 关键风险检查清单
| 风险类型 | 检查要点 |
|---------|---------|
| API 不兼容 | 目标依赖大版本升级是否有 breaking changes,是否有废弃 API 被移除 |
| 传递依赖冲突 | 升级后是否有 jar 包版本被覆盖,导致运行时行为异常 |
| exclusion 失效 | 原先排除的传递依赖是否因升级而"复活"(新版本可能改变了传递依赖结构) |
| Spring 生态版本矩阵 | 升级某个组件是否需要同步升级 Spring Boot/Cloud 版本(存在严格的版本对应关系) |
| JDK 兼容性 | 新版本是否仍支持项目配置的 JDK 版本 |
| 安全漏洞 | 升级后的版本是否存在已知 CVE 漏洞 |
| 配置变更 | 新版本是否需要调整配置文件(如 application.yml 中的配置项变更) |
### 13. 输出变更报告
升级完成后应输出以下报告:
```markdown
## 依赖升级报告
### 升级概要
| 依赖 | 旧版本 | 新版本 | 修改位置 |
|------|--------|--------|---------|
| xxx:yyy | 1.0 | 2.0 | 父POM properties |
### 依赖变化
- **新增依赖**: (列出新增的传递依赖)
- **删除依赖**: (列出消失的传递依赖)
- **版本变更**: (列出非目标的版本变化)
### 验证结果
- validate: ✅/❌
- compile: ✅/❌
- test: ✅/❌ (如执行)
- package: ✅/❌ (如执行)
### 风险提示
- (列出已识别的风险项及应对建议)
```
---
## 完整执行流程图
```
┌─────────────┐
│ 明确升级目标 │ 确定依赖 + 目标版本 + 兼容性确认
└──────┬──────┘
▼
┌─────────────┐
│ 定位版本源头 │ properties → dependencyManagement → BOM → 子模块
└──────┬──────┘
▼
┌─────────────┐
│ 扫描所有引用点 │ 全量搜索所有 pom.xml 中的引用位置
└──────┬──────┘
▼
┌─────────────┐
│ 采集前树基线 │ mvn dependency:tree → dep-tree-before.txt
└──────┬──────┘
▼
┌─────────────┐
│ 确认编译通过 │ mvn clean compile(排除存量问题)
└──────┬──────┘
▼
┌─────────────┐
│ 精确修改版本 │ 只改源头,只改目标,不动其他
└──────┬──────┘
▼
┌─────────────┐
│ 采集后树快照 │ mvn dependency:tree → dep-tree-after.txt
└──────┬──────┘
▼
┌─────────────┐
│ 差异对比分析 │ 新增/删除/变更/冲突 四类变化
└──────┬──────┘
▼
┌─────────────┐
│ 编译+测试验证 │ validate → compile → test → package
└──────┬──────┘
▼
┌─────────────┐
│ 风险审查报告 │ 输出变更报告 + 风险提示
└─────────────┘
```
---
## 常见陷阱与注意事项
1. **properties 变量 vs 硬编码混用**:同一依赖在 properties 中定义了变量,但子模块中又硬编码了不同版本,导致修改 properties 无效
2. **BOM 传递版本覆盖**:升级 BOM 版本时,BOM 管理的数十个依赖版本都会变化,影响远超预期
3. **exclusion 依赖复活**:升级依赖后,原先被排除的传递依赖可能以新版本重新出现
4. **SNAPSHOT 版本风险**:避免在生产项目中使用 SNAPSHOT 版本,升级时应选择 Release 版本
5. **多模块版本不一致**:父子模块中同一依赖声明了不同版本,Maven 就近原则可能导致意外行为
6. **pluginManagement 中的版本**:不要遗漏插件版本的同步升级(如 maven-compiler-plugin 等)
版权归属:
天明
许可协议:
本文使用《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》协议授权
评论区