依赖分析工具
本章承接入门教程的依赖冲突排查,深入讲解 Maven 依赖的健康度诊断。入门已学会用
mvn dependency:tree查看依赖树,本章将掌握如何发现"多引了"和"少引了"的依赖,让项目的依赖清单精确、干净、可维护。
核心机制
Maven 的依赖声明是静态文本,而实际代码对依赖的使用是动态行为。两者之间天然存在错位:
- 多引了:
pom.xml里声明了某个依赖,但代码中从未使用。它仍然会被下载、传递、打包,白白增加构建体积和攻击面。 - 少引了:代码中直接使用了某个类(如
org.apache.commons.lang3.StringUtils),但pom.xml里没有声明它,而是靠"依赖的依赖"间接传递进来。一旦上游升级或排除该传递依赖,你的代码就会编译失败。
maven-dependency-plugin 提供了一组静态分析工具,在不运行程序的前提下,扫描字节码和 POM 声明,找出上述两类问题。
三大分析命令
| 命令 | 作用 | 输出关键词 |
|---|---|---|
dependency:analyze | 检测未使用依赖(Unused)和缺失依赖(Used undeclared) | Used undeclared、Unused declared |
dependency:analyze-dep-mgt | 检测 dependencyManagement 与实际依赖的偏差 | Version mismatch、Overridden |
dependency:analyze-duplicate | 检测重复依赖(同一 GAV 多个版本) | Duplicate |
dependency:analyze 是最常用的核心命令,它的工作原理是:
- 读取
pom.xml中所有<dependencies>声明 - 扫描项目编译后的
.class文件,提取所有被引用的外部类全限定名 - 将外部类反向映射到对应的依赖坐标
- 对比"声明集合"与"实际使用集合",生成差异报告
生活类比:搬家打包行李
想象你要搬家,所有物品(依赖)都要装箱:
- 未使用依赖:你打包了三年没穿过的旧衣服(Unused declared),占用了箱子空间,到了新家还得找地方塞。
- 缺失依赖:你每天都在用笔记本电脑,但装箱清单上没写(Used undeclared),因为它是你室友的(传递依赖)。如果室友不搬了,你到了新家才发现电脑没了。
依赖分析工具就是帮你做"搬家前的清单核对":确保每一样东西都"确实需要",且"确实在清单上"。
图示
上图展示了依赖分析的两类问题与工具的工作原理。未使用依赖是"白占资源",缺失依赖是"隐式风险"。dependency:analyze 通过对比"声明"与"实际使用"两个集合,精确找出偏差项。
完整示例
场景
飞翔科技的 payment-gateway 项目经过半年迭代,pom.xml 已经膨胀到 40 多个依赖。CTO 大翔要求架构师白歌做一次"依赖瘦身",减少构建体积和潜在漏洞面。后端小崔负责执行,前端黄俪和运维李眉关注构建速度变化。
操作前:混乱的依赖现状
小崔查看 pom.xml,发现以下可疑依赖:
<dependencies>
<!-- 小崔3个月前尝试用Guava,后来改用Commons Lang3,但忘了删 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<!-- 代码里大量用 commons-lang3,但pom里没声明 -->
<!-- 它现在是通过 spring-boot-starter-web 传递进来的 -->
<!-- 黄俪前端联调时加的JSON库,后来统一用Jackson了 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
</dependency>
<!-- 其他正常依赖省略... -->
</dependencies>
执行分析
小崔运行分析命令:
mvn dependency:analyze
输出如下(节选):
[WARNING] Used undeclared dependencies found:
[WARNING] org.apache.commons:commons-lang3:jar:3.12.0:compile
[WARNING] Unused declared dependencies found:
[WARNING] com.google.guava:guava:jar:31.1-jre:compile
[WARNING] com.google.code.gson:gson:jar:2.9.1:compile
解读与修复
1. Used undeclared(缺失依赖)
org.apache.commons:commons-lang3 被代码直接引用,但 pom.xml 里没有声明。它目前靠 Spring Boot 传递进来,属于隐式依赖。
修复:显式声明,掌握主动权。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
2. Unused declared(未使用依赖)
guava 和 gson 在 pom.xml 里声明了,但字节码扫描显示没有任何 .class 文件引用它们。
修复:直接删除这两个 <dependency> 块。
进阶:分析 dependencyManagement 偏差
白歌在父 POM 中统一管控版本,想检查子模块是否"听话":
mvn dependency:analyze-dep-mgt
输出示例:
[WARNING] Version mismatch found:
[WARNING] com.mysql:mysql-connector-j:jar
[WARNING] depMgtVersion = 8.0.33
[WARNING] resolvedVersion = 8.0.32
这说明子模块实际解析的版本(8.0.32)与父 POM 规定的(8.0.33)不一致,通常是子模块自己硬编码了 <version> 导致的。修复方法是删除子模块中的版本号,完全交给父 POM 管理。
操作后:精简的依赖清单
<dependencies>
<!-- 显式声明代码直接使用的依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- 其他正常依赖... -->
</dependencies>
变化分析:
- 小崔删除了 2 个未使用依赖,构建产物 JAR 体积减少了约 3MB
commons-lang3从"隐式传递"变为"显式声明",后续升级 Spring Boot 时不会因为传递链变化而意外丢失- 李眉的 SAST 安全扫描结果中,已知漏洞依赖减少了 2 个
易错点与常见问题
误区一:Unused declared 可以直接无脑删除
错误认知:"分析工具说 Unused,我就直接删掉,肯定没问题。"
纠正:dependency:analyze 扫描的是编译后的字节码,它无法识别以下场景:
- 运行时反射加载的类(如
Class.forName("com.google.gson.Gson")) - 注解处理器(如 Lombok、MapStruct)在编译期使用但代码不直接引用
- 测试代码中使用的依赖(如果只在
src/test用,但依赖 scope 是compile) - SPI 机制(
META-INF/services中声明的服务实现类)
反例:小崔看到 com.google.code.gson:gson 被列为 Unused,直接删除。结果运行时发现某个第三方 SDK 内部用反射调用了 Gson,删除后抛出 ClassNotFoundException。正确做法是:删除前全局搜索代码和配置文件,确认没有反射、SPI、注解处理等间接使用。
误区二:Used undeclared 不用管,反正能编译
错误认知:"代码能编译、能运行,说明传递依赖已经把它带进来了,没必要多写一行声明。"
纠正:传递依赖是上游的 implementation detail(实现细节),不是你的契约。上游随时可能:
- 升级版本,排除该传递依赖
- 改变依赖 scope(如从
compile改为optional) - 整个依赖被替换为替代品
一旦上游变化,你的代码就会毫无征兆地编译失败。显式声明 Used undeclared 依赖,是把"隐式假设"变成"显式契约"。
误区三:只在项目收尾时跑一次分析
错误认知:"等项目做完了,我统一跑一次 dependency:analyze 清理一下就行。"
纠正:依赖腐败是渐进式的。每次需求变更、技术选型调整、代码重构,都可能引入新的未使用依赖或隐式依赖。建议将 dependency:analyze 集成到 CI 流水线中,设置失败阈值(如不允许存在 Used undeclared),在每次提交时自动检查。
小结
maven-dependency-plugin 的分析工具是项目依赖的"体检仪"。dependency:analyze 通过对比"POM 声明"与"字节码实际引用",找出未使用依赖(资源浪费)和缺失依赖(隐式风险)。dependency:analyze-dep-mgt 则确保父子模块的版本管控一致。定期运行这些工具,能让项目的依赖清单保持精确、干净、可维护。
本章与全局的关系:本章回答了"如何诊断依赖清单的健康度"。下一章"快照版本机制"将深入讲解开发阶段频繁迭代的版本管理策略,理解 SNAPSHOT 的更新规则是团队协作中避免"我这边明明已经改了,你那边怎么还是旧的"这类问题的关键。