mvn dependency:tree
本章是"常用命令"系列的进阶篇。理解
mvn dependency:tree的作用和输出格式,是掌握 Maven 依赖排查的必备技能——依赖冲突是 Maven 项目中最常见的问题来源之一。
核心机制
dependency:tree 目标显示项目的依赖树,展示解析后的版本和作用域。它是诊断依赖冲突和理解传递依赖图的无价工具。
需要特别注意的是,"invaluable"(无价之宝)。在大型项目中,直接阅读 pom.xml 中的 <dependencies> 只能看到直接依赖,而 dependency:tree 能揭示完整的传递依赖网络,这是手动阅读无法做到的。
什么是依赖树?
Maven 的依赖机制支持传递依赖(transitive dependency):如果项目 A 依赖项目 B,项目 B 又依赖项目 C,那么项目 A 会自动获得项目 C,无需在 A 的 pom.xml 中显式声明。
这种传递关系形成了一棵树状结构:
employee-system
├── spring-boot-starter-web:3.2.0 (compile)
│ ├── spring-boot-starter:3.2.0 (compile)
│ │ └── spring-boot-autoconfigure:3.2.0 (compile)
│ └── spring-webmvc:6.1.1 (compile)
│ └── spring-web:6.1.1 (compile)
├── mybatis:3.5.14 (compile)
│ └── ognl:3.3.4 (compile)
└── junit-jupiter:5.10.0 (test)
└── junit-jupiter-engine:5.10.0 (test)
mvn dependency:tree 的作用就是将这棵树以文本形式打印出来。
依赖冲突的产生原因
当依赖树的不同分支引入了同一个 artifact 的不同版本时,就产生了依赖冲突。例如:
employee-system
├── spring-boot-starter-web:3.2.0
│ └── spring-core:6.1.1 ← 需要 6.1.1
└── mybatis:3.5.14
└── spring-core:5.3.21 ← 需要 5.3.21
Maven 的冲突解决原则是"最近优先"(nearest definition):路径更短的版本胜出。如果路径长度相同,则"先声明优先"(first declaration wins)。
-Dverbose 参数的作用
添加 -Dverbose 后,dependency:tree 会显示被省略的版本,帮助你发现哪些版本被 Maven 的冲突解决机制排除了:
mvn dependency:tree -Dverbose
输出中会出现 omitted for conflict with X.X.X 的标记,明确告诉你某个版本因为冲突而被忽略。
生活类比:家族族谱与遗产纠纷
想象一个大家族(项目)的族谱(依赖树):
- 爷爷(你的项目)有三个儿子(直接依赖):大儿子 A、二儿子 B、三儿子 C
- 大儿子 A 有两个孙子(传递依赖):A1、A2
- 二儿子 B 有一个孙子(传递依赖):B1
- 问题是:A1 和 B1 都声称继承同一套房产(同一个 JAR 的不同版本)
mvn dependency:tree就是家族律师,帮你理清族谱,标出每一处遗产纠纷(版本冲突)-Dverbose则像法庭的详细调查报告,不仅告诉你谁最终继承了房产,还告诉你谁被排除了以及为什么
图示
上图展示了依赖冲突的典型场景:两个直接依赖分别传递引入了同一个 artifact 的不同版本,Maven 根据"最近优先"原则选择其一,另一个被省略。dependency:tree 的使命就是让你看到这棵树,并识别出冲突点。
完整示例
场景
飞翔科技的 employee-system 项目运行时抛出 NoSuchMethodError: org.springframework.core.io.ResourceDescriptor...。架构师白歌怀疑是依赖冲突导致的版本不兼容,要求小崔排查。
操作前:项目状态
pom.xml 中的依赖声明:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.14</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
操作步骤
步骤一:查看基础依赖树
mvn dependency:tree
输出(节选):
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.feixiang:employee-system >------------------
[INFO] Building employee-system 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- dependency:3.6.1:tree (default-cli) @ employee-system ---
[INFO] com.feixiang:employee-system:jar:1.0.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.2.0:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.2.0:compile
[INFO] | | +- org.springframework.boot:spring-boot:jar:3.2.0:compile
[INFO] | | | \- org.springframework:spring-core:jar:6.1.1:compile
[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.2.0:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:3.2.0:compile
[INFO] | | \- org.springframework:spring-core:jar:6.1.1:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:3.2.0:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.2.0:compile
[INFO] | +- org.springframework:spring-web:jar:6.1.1:compile
[INFO] | | \- org.springframework:spring-core:jar:6.1.1:compile
[INFO] | \- org.springframework:spring-webmvc:jar:6.1.1:compile
[INFO] | \- org.springframework:spring-core:jar:6.1.1:compile
[INFO] +- org.mybatis:mybatis:jar:3.5.14:compile
[INFO] | +- ognl:ognl:jar:3.3.4:compile
[INFO] | | \- org.javassist:javassist:jar:3.29.2-GA:compile
[INFO] | \- org.slf4j:slf4j-api:jar:2.0.9:compile
[INFO] +- org.mybatis:mybatis-spring:jar:2.1.2:compile
[INFO] | \- org.springframework:spring-core:jar:5.3.21:compile
[INFO] \- org.junit.jupiter:junit-jupiter:jar:5.10.0:test
[INFO] \- org.junit.jupiter:junit-jupiter-engine:jar:5.10.0:test
步骤二:使用 -Dverbose 查看被省略的版本
mvn dependency:tree -Dverbose
输出中的冲突标记(节选):
[INFO] +- org.mybatis:mybatis-spring:jar:2.1.2:compile
[INFO] | \- org.springframework:spring-core:jar:5.3.21:compile
[INFO] | \- (org.springframework:spring-core:jar:6.1.1:compile - omitted for conflict with 6.1.1)
依赖树输出解读
树形符号含义:
| 符号 | 含义 |
|---|---|
+- | 该节点有后续兄弟节点(还有别的依赖在同一层级) |
\- | 该节点是最后一个兄弟节点(同一层级的最后一个) |
| ` | ` |
每行格式:
groupId:artifactId:packaging:version:scope
例如:org.springframework:spring-core:jar:6.1.1:compile
groupId:org.springframeworkartifactId:spring-corepackaging:jarversion:6.1.1scope:compile
冲突标记识别
在 -Dverbose 输出中,以下标记需要重点关注:
| 标记 | 含义 | 危险程度 |
|---|---|---|
omitted for conflict with X.X.X | 该版本因冲突被省略,另一个版本被选用 | 🔴 高 |
omitted for duplicate | 该依赖已在其他路径引入,此处重复 | 🟡 中 |
(version) —— 括号包裹 | 该版本被管理(managed)或覆盖 | 🟡 中 |
本例中的关键冲突:
mybatis-spring:2.1.2
└── spring-core:5.3.21 (compile)
└── (spring-core:6.1.1 - omitted for conflict with 6.1.1)
解读:mybatis-spring 想要 spring-core:5.3.21,但 spring-boot-starter-web 已经引入了 spring-core:6.1.1。由于 spring-core:6.1.1 的路径更短(更近),它胜出,5.3.21 被省略。
问题分析:mybatis-spring:2.1.2 是为 Spring 5.x 设计的,与 Spring 6.x(spring-core:6.1.1)可能存在兼容性问题。解决方案是升级 mybatis-spring 到兼容 Spring Boot 3.x 的版本:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version> <!-- 兼容 Spring 6.x -->
</dependency>
易错点与常见问题
误区一:dependency:tree 显示的是 pom.xml 中的直接依赖
错误认知:"mvn dependency:tree 的输出和我 pom.xml 里的 <dependencies> 列表一样。"
纠正:pom.xml 中的 <dependencies> 只包含直接依赖(你显式声明的)。dependency:tree 展示的是完整依赖树,包含所有传递依赖。一个只声明了 5 个直接依赖的项目,其依赖树可能包含 50+ 个节点。
误区二:看不到冲突就是没有冲突
错误认知:"我执行了 mvn dependency:tree,输出里没有 omitted for conflict,所以我的项目没有依赖冲突。"
纠正:不加 -Dverbose 时,被省略的版本不会显示。你可能看到树中只有一个 spring-core:6.1.1,却不知道另一个分支原本想要 5.3.21。排查冲突时,必须加 -Dverbose:
mvn dependency:tree -Dverbose
误区三:冲突解决后问题就消失了
错误认知:"我在 pom.xml 里用 <dependencyManagement> 锁定了版本,dependency:tree 也显示统一了,但运行时还是报错。"
纠正:dependency:tree 显示的是 Maven 的依赖解析结果,但运行时 classpath 还受以下因素影响:
- 打包插件(如
maven-shade-plugin)可能重新组织了依赖 - 容器(如 Tomcat)可能自带某些 JAR,与项目依赖冲突
- 某些依赖的
scope是provided,打包时未包含,但运行时容器提供了不同版本
排查这类问题时,需要结合 mvn dependency:tree 和运行时 -verbose:class 参数综合分析。
误区四:只关注 compile scope 的依赖
错误认知:"test scope 的依赖只在测试时用,不会影响生产环境,不用看。"
纠正:虽然 test scope 的依赖不会进入生产产物,但如果测试代码中使用了这些依赖的类,而它们与主代码的依赖存在冲突,可能导致测试通过但生产失败的诡异情况。完整的依赖树审查应覆盖所有 scope。
小结
mvn dependency:tree 是 Maven 依赖排查的"X 光机",它能将隐藏在传递依赖中的版本冲突暴露出来。核心要点:
dependency:tree展示完整的传递依赖树,远超pom.xml的直接依赖列表-Dverbose是排查冲突的必备参数,可显示被省略的版本- 冲突标记
omitted for conflict with X.X.X直接指出问题所在 - Maven 冲突解决原则是"最近优先、先声明优先"
- 发现冲突后,通常通过升级依赖版本或
<dependencyManagement>锁定版本来解决
本章与全局的关系:本章讲解了依赖树的查看和冲突识别。下一章"依赖冲突排查"将系统化地总结排查流程和解决方案,与 dependency:analyze 等工具结合使用。