乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 多模块项目深入

    • Reactor构建顺序
    • 继承与聚合组合实践
    • 模块间依赖
  • 第2章 插件体系深入

    • 插件目标goal
    • execution与自定义绑定
    • pluginManagement
  • 第3章 Profile高级应用

    • Profile激活机制
    • Profile与Spring Profile区别
  • 第4章 部署与分发

    • distributionManagement
    • mvn deploy
    • settings.xml认证配置
  • 第5章 CI/CD集成

    • Maven与持续集成
  • 第6章 自定义插件开发

    • 自定义插件开发
  • 第7章 高级依赖管理

    • 快照版本机制
    • 依赖分析工具
  • 第8章 仓库管理深入

    • 仓库组与路由

依赖分析工具

本章承接入门教程的依赖冲突排查,深入讲解 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 是最常用的核心命令,它的工作原理是:

  1. 读取 pom.xml 中所有 <dependencies> 声明
  2. 扫描项目编译后的 .class 文件,提取所有被引用的外部类全限定名
  3. 将外部类反向映射到对应的依赖坐标
  4. 对比"声明集合"与"实际使用集合",生成差异报告

生活类比:搬家打包行李

想象你要搬家,所有物品(依赖)都要装箱:

  • 未使用依赖:你打包了三年没穿过的旧衣服(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 的更新规则是团队协作中避免"我这边明明已经改了,你那边怎么还是旧的"这类问题的关键。

上一页
快照版本机制