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

    • 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章 常见问题与最佳实践

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

依赖传递

本章承接 dependencies,深入讲解 Maven 依赖管理中最具魔力的特性——传递依赖。理解传递机制,是排查"为什么项目里出现了我没声明的 JAR"、"为什么打包体积突然膨胀"等问题的关键。


核心机制

当你声明了对项目 A 的依赖,而项目 A 又声明了对项目 B 的依赖,Maven 会自动将项目 B 引入你的项目,无需你在 POM 中显式声明 B。

这句话的潜台词是:依赖关系具有传染性。你只需关心你的"直接朋友"(直接依赖),Maven 帮你处理"朋友的朋友"(传递依赖)。

什么是传递依赖?

假设你的项目 employee-system 依赖了 spring-context,而 spring-context 内部又依赖了 spring-core、spring-beans、spring-aop。在 Maven 中,你只需声明 spring-context,其余三个会自动进入你的 classpath——这就是传递依赖。

传递规则:哪些 scope 会传递?

并非所有依赖范围都会传递。Maven 的传递规则如下:

直接依赖 scope传递依赖的 scope是否传递
compilecompile✅ 传递
compiletest❌ 不传递
test任何❌ 不传递
provided任何❌ 不传递
runtimeruntime✅ 传递
system任何❌ 不传递

核心原则:

  • compile 和 runtime 范围的依赖会传递
  • test、provided、system 范围的依赖不会传递
  • 传递后的 scope 可能发生变化(如 compile → runtime 的传递结果是 runtime)

传递的利与弊

利:

  1. 减少重复声明:你不需要知道 spring-context 内部依赖了什么,只需关心它本身
  2. 版本一致性:spring-context 依赖的 spring-core 版本由 Spring 团队锁定,避免你自己拼错版本
  3. 升级原子性:升级 spring-context 时,其整个传递树一起升级,不会出现版本碎片

弊:

  1. 依赖膨胀:一个 spring-boot-starter-web 可能引入 50+ 个传递依赖,打包体积剧增
  2. 隐式冲突:两个直接依赖分别传递了不同版本的同一个库,导致 classpath 冲突
  3. 黑盒风险:你不知道项目里某个 JAR 从哪来,排查问题时需要 mvn dependency:tree

生活类比:搬家时的"附带物品"

想象你租了一套带家具的公寓(依赖 spring-context):

  • 利:房东已经配好了床、沙发、冰箱(传递依赖 spring-core、spring-beans)。你拎包入住,不需要自己去家具城一件件买。
  • 弊:你发现公寓里有一台你不需要的跑步机(多余的传递依赖),占用了客厅空间(打包体积膨胀)。更糟的是,房东配的冰箱和你自己买的冰箱(另一个依赖传递的冲突版本)同时存在,导致电路过载(classpath 冲突)。

Maven 的传递依赖就像这套带家具的公寓——方便,但你需要定期检查"家具清单",确保没有多余或冲突的物品。


图示

传递依赖示意图

上图展示了 spring-context 的传递树:你的项目只声明了 spring-context,但 Maven 自动引入了 spring-core、spring-beans、spring-aop、spring-expression,而 spring-core 又进一步传递了 spring-jcl。这个树状结构完全由 Maven 自动推导,你无需在 POM 中写任何额外配置。


完整示例

场景

飞翔科技的 employee-system 引入了 spring-context 和 mybatis。后端小崔发现项目里出现了一些他没声明的 JAR,怀疑是传递依赖所致。架构师白歌要求他验证传递前后的 classpath 变化,并理解传递规则。

操作前:无传递依赖的 classpath

假设 Maven 没有传递机制,小崔的 pom.xml 只能声明直接依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.21</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
</dependencies>

执行编译:

mvn compile

结果(假设无传递机制):编译失败,报错 ClassNotFoundException: org.springframework.core.io.Resource。

原因:spring-context 的代码里 import 了 spring-core 的类,但 classpath 里没有 spring-core。小崔必须手动追踪 spring-context 依赖了哪些库,逐一声明——这正是 Maven 试图消灭的重复劳动。

操作后:传递依赖自动补齐

在真实的 Maven 中,执行同样的 pom.xml 和命令:

mvn dependency:tree

输出:

com.feixiang:employee-system:jar:1.0.0
+- org.springframework:spring-context:jar:5.3.21:compile
|  +- org.springframework:spring-aop:jar:5.3.21:compile
|  +- org.springframework:spring-beans:jar:5.3.21:compile
|  +- org.springframework:spring-core:jar:5.3.21:compile
|  |  \- org.springframework:spring-jcl:jar:5.3.21:compile
|  \- org.springframework:spring-expression:jar:5.3.21:compile
\- org.mybatis:mybatis:jar:3.5.9:compile
   +- org.slf4j:slf4j-api:jar:1.7.32:compile
   \- org.javassist:javassist:jar:3.27.0-GA:compile

classpath 变化对比:

场景classpath 中的 JAR 数量小崔需要声明的依赖数
无传递机制(假设)28+(需手动追踪所有间接依赖)
Maven 传递依赖(真实)82(只需声明直接依赖)

变化分析:

  • 小崔只声明了 2 个依赖,Maven 自动解析出 8 个 JAR 进入 classpath
  • spring-core 的 spring-jcl 是第二层传递,同样自动引入
  • mybatis 传递了 slf4j-api 和 javassist,小崔完全不需要知道它们的存在
  • 李眉部署时,这 8 个 JAR 都会被打包进最终产物(因为 scope 都是 compile)

反例:test 依赖不传递

小崔在 pom.xml 中加一个 test 范围的依赖:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>

执行:

mvn dependency:tree

输出:

com.feixiang:employee-system:jar:1.0.0
+- org.springframework:spring-context:jar:5.3.21:compile
|  +- ...
\- org.mockito:mockito-core:jar:4.6.1:test
   +- net.bytebuddy:byte-buddy:jar:1.12.10:test
   +- net.bytebuddy:byte-buddy-agent:jar:1.12.10:test
   \- org.objenesis:objenesis:jar:3.2:test

注意 mockito-core 及其传递依赖(byte-buddy、objenesis)的 scope 都是 test。如果另一个项目 payroll-service 依赖了 employee-system,这些 test 依赖不会传递过去——这是 Maven 的隔离机制,确保测试工具不会污染生产环境。


易错点与常见问题

误区一:"我没声明这个 JAR,所以可以删掉"

错误认知:"mvn dependency:tree 里出现了 javassist,我没声明它,把它从 pom.xml 里删掉。"

纠正:javassist 是 mybatis 的传递依赖,不是你直接声明的。你"删掉"它的唯一方式是排除 mybatis 对 javassist 的依赖(用 exclusions),或者不依赖 mybatis。如果你直接删除 mybatis,javassist 自然消失;但如果你需要 mybatis,就必须接受它的传递树(或精确排除某些分支)。

误区二:传递依赖的版本我可以随意覆盖

错误认知:"spring-context 传递了 spring-core 5.3.21,我在 dependencies 里直接声明 spring-core 5.3.22,就能单独升级它。"

纠正:这种做法技术上可行,但逻辑上危险。spring-context 5.3.21 是在 spring-core 5.3.21 上测试的,强行把 spring-core 升到 5.3.22 可能导致二进制不兼容。正确的做法是整体升级 spring-context 到 5.3.22,让它的传递树一起升级。传递依赖的价值之一就是"版本锁定的一致性"。

误区三:所有传递依赖都会被打包

错误认知:"mvn package 会把 dependency:tree 里看到的所有 JAR 都打进最终产物。"

纠正:只有 compile 和 runtime 范围的依赖会进入打包产物。test、provided 范围的依赖不会。此外,如果你使用 optional 或 exclusions,传递树会被修剪。打包内容取决于有效依赖树,而非 dependency:tree 的原始输出。


小结

传递依赖是 Maven 依赖管理的核心机制。它通过自动解析"朋友的朋友",将开发者从繁琐的间接依赖追踪中解放出来。compile 和 runtime 范围的依赖会传递,test 和 provided 不会。传递带来便利的同时,也可能导致依赖膨胀和隐式冲突,需要配合 mvn dependency:tree 定期审查依赖树。

本章与全局的关系:本章解释了"依赖为什么会自动传播"。下一章"scope"将讲解"依赖在什么时候、什么场景下生效"——这是控制传递边界的关键工具。

上一页
scope
下一页
依赖冲突与调解