全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-23 12 分钟 ✍️ juanwangdev

传递性依赖机制

传递性依赖是 Maven 最强大的特性之一。传统项目需要手动下载每个 jar 及其依赖的 jar,一个库可能依赖十几个其他库,遗漏任何一个都会导致编译或运行失败。Maven 的传递性依赖机制自动处理这些依赖链,只需声明直接依赖,间接依赖自动引入。

为什么需要传递性依赖

传统方式的痛点

Bash
场景:项目需要使用 Spring框架

手动管理依赖:
1. 下载 spring-context-5.3.20.jar
2. 启动报错:ClassNotFoundException: SpringCore
3. 查文档发现需要 spring-core
4. 下载 spring-core-5.3.20.jar
5. 启动报错:ClassNotFoundException: SpringJcl
6. 再下载 spring-jcl-5.3.20.jar
7. 再下载 spring-beans、spring-aop、spring-expression...

问题:
- 不知道一个库依赖哪些其他库
- 需要反复尝试、查文档
- 遗漏依赖导致运行时错误
- 版本不匹配导致兼容问题
- 团队成员可能配置不同

Maven 解决方案

Bash
你只需声明:
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.20</version>
</dependency>

Maven 自动完成:
1. 下载 spring-context-5.3.20.jar
2. 解析 spring-context 的 pom.xml
3. 发现 spring-context 依赖:
   - spring-core:5.3.20
   - spring-beans:5.3.20
   - spring-aop:5.3.20
   - spring-expression:5.3.20
   - spring-jcl:5.3.20
4. 自动下载所有传递依赖
5. 自动管理版本一致性

传递性依赖工作原理

Maven 如何解析传递依赖

Bash
传递依赖解析流程:

步骤1:解析项目 pom.xml
      获取直接依赖列表

步骤2:下载直接依赖的 jar 和 pom
      每个 jar 都有自己的 pom.xml

步骤3:解析每个依赖的 pom.xml
      获取该依赖的依赖列表(即传递依赖)

步骤4:下载传递依赖
      同样获取其 pom.xml,继续解析

步骤5:递归解析
      直到所有依赖链解析完成

步骤6:构建依赖树
      记录每个依赖的来源路径

步骤7:应用调解规则
      解决版本冲突(后面详细讲)

依赖的 pom.xml 如何描述其依赖

XML
spring-context-5.3.20.jar 包含的 pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.20</version>

  <dependencies>
    <!-- Spring Context 声明它依赖的其他 Spring 模块 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.3.20</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.3.20</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.3.20</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>5.3.20</version>
    </dependency>
  </dependencies>
</project>

Maven 解析这个 pom.xml,自动获取 spring-context 的依赖

依赖树结构

XML
你的项目 pom.xml:
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.20</version>
</dependency>

生成的依赖树:

my-app
└── spring-context:5.3.20← 你声明
    ├── spring-aop:5.3.20← 自动传递
    ├── spring-beans:5.3.20← 自动传递
    │   └── spring-core:5.3.20 ← 二级传递
    ├── spring-core:5.3.20    ← 自动传递
    │   └── spring-jcl:5.3.20 ← 二级传递
    └── spring-expression:5.3.20 ← 自动传递

你获得的所有依赖:
- spring-context:5.3.20(直接)
- spring-aop:5.3.20(一级传递)
- spring-beans:5.3.20(一级传递)
- spring-core:5.3.20(一级传递)
- spring-expression:5.3.20(一级传递)
- spring-jcl:5.3.20(二级传递)

查看传递依赖

使用 dependency:tree 命令

XML
# 查看完整依赖树
mvn dependency:tree

输出示例:

XML
[INFO] com.example:my-app:jar:1.0.0
[INFO] +- org.springframework:spring-context:jar:5.3.20:compile
[INFO] |  +- org.springframework:spring-aop:jar:5.3.20:compile
[INFO] |  +- org.springframework:spring-beans:jar:5.3.20:compile
[INFO] |  |  \- org.springframework:spring-core:jar:5.3.20:compile
[INFO] |  +- org.springframework:spring-core:jar:5.3.20:compile
[INFO] |  |  \- org.springframework:spring-jcl:jar:5.3.20:compile
[INFO] |  \- org.springframework:spring-expression:jar:5.3.20:compile
[INFO] \- junit:junit:jar:4.13.2:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.3:test

输出符号含义

符号含义说明
+-直接依赖你的 pom.xml 声明的依赖
\-最后依赖某路径上最后一个依赖
``层级分隔

过滤查看特定依赖

XML
# 只看 Spring 相关依赖
mvn dependency:tree -Dincludes=org.springframework:*

# 只看某个依赖的来源
mvn dependency:tree -Dincludes=*:spring-core

# 只看 compile scope 依赖
mvn dependency:tree -Dscope=compile

# 只看 test scope 依赖
mvn dependency:tree -Dscope=test

verbose模式显示冲突

Bash
# 显示被排除的依赖(冲突详情)
mvn dependency:tree -Dverbose

输出:
[INFO] +- spring-context:5.3.20
[INFO] |  +- spring-core:5.3.20
[INFO] +- some-lib:1.0.0
[INFO] |  +- spring-core:4.3.0 (omitted for conflict: 5.3.20)
                          ↑ 显示被排除版本和原因

传递范围规则—— 重要

scope 对传递的影响

关键理解:直接依赖的 scope 影响传递依赖的 scope。

直接依赖 scope传递依赖 scope说明
compilecompile正常传递,保持原 scope
provided不传递只在本项目有效
runtimeruntime传递但保持 runtime
test不传递只在本项目有效

传递范围变化详解

XML
规则:传递依赖的 scope = min(直接依赖 scope, 传递依赖原 scope)

示例1:compile → compile → compile
A (compile) → B (compile)
结果:A 获得 B (compile)

示例2:compile → runtime → runtime
A (compile) → B (runtime)
结果:A 获得 B (runtime)

示例3:runtime → compile → runtime
A (runtime) → B (compile)
结果:A 获得 B (runtime) ← 受 A 的 runtime限制

示例4:provided → compile → 不传递
A (provided) → B (compile)
结果:A 不获得 B ← provided 不传递

示例5:test → compile → 不传递
A (test) → B (compile)
结果:A 不获得 B ← test 不传递

provided 和 test 不传递的原因

XML
为什么 provided 不传递?

provided 含义:运行时由容器/外部提供
- Servlet API:Tomcat 提供
- 在你的项目中有效(编译时可用)
- 但依赖你的项目的其他项目:
  - 可能用不同的容器
  - 容器版本可能不同
  - 不应继承你的 provided 配置

示例:
你的项目 (Web项目)
└── servlet-api:4.0.1 (provided) ← Tomcat 9 提供

依赖你的项目的其他项目(可能是非Web项目)
└── 不应继承 servlet-api ← 因为它没有 Tomcat

为什么 test 不传递?

test 含义:仅测试使用
- 只在你的 src/test/java 中使用
- 其他项目不需要你的测试依赖
- 测试依赖通常是私有的

示例:
你的项目
└── junit:4.13.2 (test)

依赖你的项目的其他项目
└── 不应继承 junit ← 它有自己的测试依赖

传递依赖的实际场景

场景1:使用 Spring Boot Starter

XML
<!-- 你只声明一个 starter -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.7.0</version>
</dependency>

传递依赖树:

XML
spring-boot-starter-web
├── spring-boot-starter
│   ├── spring-boot
│   ├── spring-boot-autoconfigure
│   ├── spring-boot-starter-logging
│   │   ├── logback-classic
│   │   ├── log4j-to-slf4j
│   │   └── jul-to-slf4j
│   └── snakeyaml
├── spring-boot-starter-tomcat
│   ├── tomcat-embed-core
│   ├── tomcat-embed-el
│   └── tomcat-embed-websocket
├── spring-web
├── spring-webmvc

你获得的所有依赖(30+个):
只需声明一个,自动获得整个 Web 开发栈

场景2:使用 MyBatis

Bash
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.10</version>
</dependency>

传递依赖:

XML
mybatis:3.5.10
└── 无传递依赖(MyBatis 设计简洁)

如果使用 MyBatis-Spring:
mybatis-spring:2.0.7
├── mybatis:3.5.10
└── spring-context:5.x
    └── spring的所有依赖...

场景3:使用 Apache Commons

XML
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.12.0</version>
</dependency>

传递依赖:

text
commons-lang3:3.12.0
└── 无传递依赖

commons-lang 设计原则:零依赖,轻量

场景4:使用 Hibernate

text
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>5.6.10.Final</version>
</dependency>

传递依赖:

text
hibernate-core:5.6.10.Final
├── jakarta.persistence-api
├── jakarta.transaction-api
├── jboss-logging
├── classmate
├── hibernate-commons-annotations
└── byte-buddy
    └── byte-buddy-agent

复杂依赖链:Hibernate依赖多个库

传递依赖的版本冲突

冲突产生原因

text
多条路径引入同一依赖的不同版本:

路径1:项目 → A → commons-lang:2.6
路径2:项目 → B → commons-lang:3.12.0

问题:两个版本的 commons-lang,Maven 只能选一个

Maven调解规则

规则1:最短路径优先

text
项目 → A → B → commons-lang:2.6(路径长度3)
项目 → C → commons-lang:3.12.0(路径长度2)

结果:选择 commons-lang:3.12.0

原因:C 路径更短,优先级更高

规则2:声明顺序优先

text
路径长度相同时:

项目 → A → commons-lang:2.6(路径长度2)
项目 → B → commons-lang:3.12.0(路径长度2)

pom.xml声明顺序决定:
<dependencies>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>A</artifactId>  <!-- 先声明 -->
  </dependency>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>B</artifactId>  <!-- 后声明 -->
  </dependency>
</dependencies>

结果:选择 commons-lang:2.6(A 先声明)

规则3:直接声明优先级最高

text
<!-- 在 pom.xml 直接声明版本 -->
<dependency>
  <groupId>commons-lang</groupId>
  <artifactId>commons-lang</artifactId>
  <version>3.12.0</version>  <!-- 强制版本 -->
</dependency>

<!-- 其他传递版本被覆盖 -->
<dependency>
  <groupId>com.example</groupId>
  <artifactId>A</artifactId>  <!-- 传递 commons-lang:2.6,被覆盖 -->
</dependency>

冲突排查方法

text
# 查看冲突详情
mvn dependency:tree -Dverbose | grep "omitted for conflict"

# 查看某个依赖的所有版本来源
mvn dependency:tree -Dverbose -Dincludes=groupId:artifactId

控制传递依赖

方式1:排除传递依赖(exclusions)

text
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.20</version>
  <exclusions>
    <!-- 排除不需要的传递依赖 -->
    <exclusion>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jcl</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<!-- 使用自己喜欢的日志库 -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.11</version>
</dependency>

适用场景

  • 不需要某个传递依赖
  • 要用其他版本或替代库
  • 避免冲突

方式2:可选依赖(optional)

text
<!-- 在依赖库的 pom.xml 中定义 -->
<dependency>
  <groupId>com.example</groupId>
  <artifactId>optional-feature</artifactId>
  <version>1.0.0</version>
  <optional>true</optional>  <!-- 不传递 -->
</dependency>

效果

  • 本项目可以使用 optional-feature
  • 依赖本项目的不获得 optional-feature

适用场景

  • 可选功能依赖
  • 用户可选择是否启用

方式3:直接声明覆盖

text
<!-- 直接声明需要的版本 -->
<dependency>
  <groupId>commons-lang</groupId>
  <artifactId>commons-lang</artifactId>
  <version>3.12.0</version>  <!-- 强制版本,覆盖传递版本 -->
</dependency>

方式4:dependencyManagement统一版本

text
<dependencyManagement>
  <dependencies>
    <!-- 统一管理版本 -->
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>3.12.0</version>
    </dependency>
  </dependencies>
</dependencyManagement>

<!-- 传递依赖版本会被覆盖 -->

传递依赖的优势与问题

优势

优势说明
简化依赖声明只需声明直接依赖,间接依赖自动引入
自动版本管理依赖的依赖使用正确版本
减少遗漏错误自动引入所有必需依赖
团队协作一致所有成员获得相同依赖
版本升级方便升级直接依赖,传递依赖自动升级

可能的问题

问题说明解决方式
版本冲突多路径引入不同版本直接声明版本、exclusions
不需要的依赖传递了不需要的库exclusions 排除
依赖臃肿依赖树过于庞大检查依赖树,清理多余
版本过旧传递依赖版本太旧直接声明新版本

最佳实践

实践1:定期检查依赖树

text
# 定期执行,保存依赖树
mvn dependency:tree > dependency-tree.txt

# 检查是否有意外的依赖
grep "unexpected" dependency-tree.txt

# 检查依赖数量
wc -l dependency-tree.txt

实践2:显式声明核心传递依赖

text
<!-- dependency:analyze 发现 Used undeclared -->
<!-- 代码直接使用传递依赖 -->

<!-- 建议:显式声明 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>${spring.version}</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${spring.version}</version>
</dependency>

原因

  • 代码直接使用的依赖应该显式声明
  • 明确控制版本
  • 防止上游依赖变化影响

实践3:使用 BOM统一管理复杂依赖

text
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.7.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

实践4:避免依赖地狱

text
症状:
- dependency:tree 输出几百行
- war 包超过 50MB
- 构建时间过长

解决:
1. 检查是否有不需要的依赖
2. 使用 exclusions 排除多余传递依赖
3. 考虑拆分项目,减少依赖
4. 使用轻量级替代库

常见问题

问题1:ClassNotFoundException 找不到类

text
报错:ClassNotFoundException: SomeClass

诊断:
mvn dependency:tree -Dverbose -Dincludes=*:lib-name

发现:
- lib-name 某版本被选中,但该版本没有 SomeClass
- 另一个版本有 SomeClass,但被排除

解决:
直接声明需要的版本

问题2:依赖版本不是期望的

text
期望:commons-lang:3.12.0
实际:commons-lang:2.6

诊断:
mvn dependency:tree -Dverbose -Dincludes=*:commons-lang

发现:
[INFO] +- A → commons-lang:2.6
[INFO] +- B → commons-lang:3.12.0 (omitted for conflict: 2.6)

解决:
直接声明 commons-lang:3.12.0

问题3:依赖太多,war包太大

text
诊断:
mvn dependency:tree > deps.txt
wc -l deps.txt  # 查看依赖数量

检查:
- 是否有不必要的依赖
- 是否有功能重复的库
- 是否可以拆分项目

解决:
exclusions 排除不需要的传递依赖

要点总结

  1. 传递依赖自动引入:只需声明直接依赖,间接依赖自动管理
  2. 解析依赖的 pom.xml:每个 jar 的 pom.xml 描述其依赖
  3. dependency:tree 查看:显示完整依赖树和层级关系
  4. scope 影响传递:compile/runtime 可传递,provided/test 不传递
  5. 调解规则:最短路径优先、声明顺序优先、直接声明最高
  6. exclusions 排除:精确排除不需要的传递依赖
  7. optional 不传递:可选依赖不会传递给下游
  8. 直接声明控制版本:显式声明版本覆盖所有传递版本
  9. 定期检查依赖树:了解依赖来源,发现问题

📝 发现内容有误?点击此处直接编辑

← 上一篇 项目信息配置
下一篇 → 依赖声明与坐标引用
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库