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

    • 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章 介绍与核心概念

    • Maven是什么
    • 约定优于配置
  • 第2章 安装与配置

    • 安装与验证
    • settings.xml
    • 本地仓库与镜像
  • 第3章 POM与项目坐标

    • POM
    • GAV坐标
    • packaging
  • 第4章 标准目录布局

    • 标准目录布局
  • 第5章 依赖机制

    • dependencies
    • scope
    • 依赖传递
    • 依赖冲突与调解
    • exclusions
    • optional
    • dependencyManagement
  • 第6章 仓库

    • 仓库体系
    • 本地仓库
    • 远程仓库与镜像
    • 私服
  • 第7章 构建生命周期

    • 生命周期概述
    • clean 生命周期
    • default 生命周期
    • site 生命周期
    • 生命周期与插件绑定
  • 第8章 插件

    • 插件概述
    • maven-compiler-plugin
    • maven-surefire-plugin
    • maven-war-plugin
  • 第9章 继承与聚合

    • parent继承
    • 聚合
    • BOM
    • properties
  • 第10章 属性与资源过滤

    • 资源过滤
    • Profile
  • 第11章 常用命令

    • mvn compile
    • mvn test
    • mvn package
    • mvn clean
    • mvn install
    • mvn dependency:tree
  • 第12章 常见问题与最佳实践

    • 依赖冲突排查
    • 最佳实践

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.springframework
  • artifactId:spring-core
  • packaging:jar
  • version:6.1.1
  • scope: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 等工具结合使用。

上一页
mvn install