依赖管理
依赖管理是 Maven 最核心的功能之一。本章将详细介绍如何声明依赖、理解依赖范围、解决依赖冲突等关键知识。
依赖声明
在 Maven 中,通过在 pom.xml 的 <dependencies> 元素中声明项目所需的依赖。
基本依赖声明
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.23</version>
</dependency>
</dependencies>
每个依赖声明包含以下核心元素:
- groupId:依赖的组织标识
- artifactId:依赖的项目标识
- version:依赖的版本号
这三个元素组成了依赖的坐标,Maven 根据坐标从仓库中查找并下载依赖。
查找依赖坐标
如何找到需要的依赖坐标?可以使用以下方式:
- Maven 中央仓库搜索:访问 MVN Repository 搜索
- IDE 提示:IntelliJ IDEA 等 IDE 提供依赖搜索功能
- 官方文档:框架官方文档通常会提供 Maven 依赖配置
依赖范围
依赖范围决定了依赖在哪些阶段可用。Maven 提供了 6 种依赖范围:
compile(编译范围)
compile 是默认的依赖范围,如果没有指定 scope,则使用此范围。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.23</version>
<scope>compile</scope>
</dependency>
特点:
- 在编译、测试、运行时都可用
- 会被打包到最终的构件中
- 可以传递给依赖此项目的其他项目
provided(已提供范围)
provided 表示依赖在编译和测试时需要,但运行时由 JDK 或容器提供。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
特点:
- 在编译和测试时可用
- 运行时期望 JDK 或容器提供
- 不会被打包到最终构件中
- 不会传递给依赖项目
典型使用场景:
- Servlet API(由 Tomcat 等 Web 容器提供)
- Lombok(编译时使用)
runtime(运行时范围)
runtime 表示依赖只在运行和测试时需要,编译时不需要。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
特点:
- 在测试和运行时可用
- 编译时不可用
- 会被打包到最终构件中
典型使用场景:
- JDBC 驱动(编译时只需要 JDBC API,运行时需要具体驱动实现)
test(测试范围)
test 表示依赖只在测试编译和运行时需要。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
特点:
- 只在测试编译和运行时可用
- 主代码编译和运行时不可用
- 不会被打包到最终构件中
- 不会传递给依赖项目
典型使用场景:
- 测试框架(JUnit、TestNG)
- Mock 框架(Mockito)
system(系统范围)
system 表示依赖不从 Maven 仓库获取,而是从本地文件系统获取。
<dependency>
<groupId>com.example</groupId>
<artifactId>local-jar</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/local-jar-1.0.jar</systemPath>
</dependency>
特点:
- 必须配合 systemPath 指定本地 jar 路径
- 不会从 Maven 仓库查找
- 不推荐使用,可移植性差
import 导入范围
Maven 的 import 范围只能在 dependencyManagement 中使用,用于导入另一个 POM 的依赖管理配置。
<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>
特点:
- 只能用于 dependencyManagement 中
- 导入目标 POM 的 dependencyManagement 配置
- 常用于导入 BOM(Bill of Materials)
依赖范围对比
| 范围 | 编译 | 测试编译 | 测试运行 | 运行 | 打包 | 传递 |
|---|---|---|---|---|---|---|
| compile | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| provided | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| runtime | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |
| test | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ |
| system | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
传递性依赖
什么是传递性依赖
当项目 A 依赖项目 B,而项目 B 又依赖项目 C 时,Maven 会自动将项目 C 作为项目 A 的依赖引入。这种自动处理依赖关系的特性称为传递性依赖。
例如,项目依赖 spring-webmvc:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
Maven 会自动引入 spring-webmvc 所依赖的其他构件:
- spring-core
- spring-beans
- spring-context
- spring-expression
- spring-web
- 等等...
传递性依赖的范围
传递性依赖的范围会受到原始依赖范围的影响:
| 直接依赖范围 | 传递依赖范围 | 最终范围 |
|---|---|---|
| compile | compile | compile |
| compile | runtime | runtime |
| compile | provided | 不传递 |
| compile | test | 不传递 |
| provided | compile | 不传递 |
| provided | runtime | 不传递 |
| runtime | compile | runtime |
| runtime | runtime | runtime |
| test | * | 不传递 |
依赖冲突解决
当项目中存在多个版本的同一依赖时,就会发生依赖冲突。Maven 使用以下策略解决冲突:
最短路径优先
Maven 优先选择依赖树中路径最短的版本。
项目 A
├── 依赖 B (路径长度: 1)
│ └── 依赖 C 1.0 (路径长度: 2)
└── 依赖 D (路径长度: 1)
└── 依赖 E (路径长度: 2)
└── 依赖 C 2.0 (路径长度: 3)
在这个例子中,Maven 会选择 C 1.0,因为它的路径更短(2 < 3)。
声明顺序优先
当路径长度相同时,Maven 选择在 pom.xml 中先声明的版本。
项目 A
├── 依赖 B (先声明)
│ └── 依赖 C 1.0
└── 依赖 D (后声明)
└── 依赖 C 2.0
Maven 会选择 C 1.0,因为它在依赖树中先出现。
查看依赖树
使用 mvn dependency:tree 命令可以查看完整的依赖树:
mvn dependency:tree
输出示例:
[INFO] com.example:my-app:jar:1.0.0
[INFO] \- org.springframework:spring-webmvc:jar:5.3.23:compile
[INFO] +- org.springframework:spring-aop:jar:5.3.23:compile
[INFO] +- org.springframework:spring-beans:jar:5.3.23:compile
[INFO] +- org.springframework:spring-context:jar:5.3.23:compile
[INFO] +- org.springframework:spring-core:jar:5.3.23:compile
[INFO] | \- org.springframework:spring-jcl:jar:5.3.23:compile
[INFO] +- org.springframework:spring-expression:jar:5.3.23:compile
[INFO] \- org.springframework:spring-web:jar:5.3.23:compile
查看特定依赖的来源:
mvn dependency:tree -Dincludes=org.springframework:spring-core
排除依赖
当传递性依赖导致问题(如版本冲突、不需要的依赖)时,可以使用 <exclusions> 排除特定依赖。
排除单个依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 显式引入需要的版本 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.24</version>
</dependency>
排除所有传递性依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>example-lib</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
可选依赖
当一个依赖只被项目的部分功能使用时,可以将其标记为可选依赖。可选依赖不会传递给依赖此项目的其他项目。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<optional>true</optional>
</dependency>
使用场景:
- 项目支持多种数据库,但用户只需要其中一种
- 项目提供可选功能,需要额外依赖
依赖管理
dependencyManagement 元素
dependencyManagement 用于统一管理依赖版本,常用于多模块项目:
<!-- 父 POM -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.23</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
</dependencyManagement>
子项目使用时不需要指定版本:
<!-- 子 POM -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<!-- 版本由父 POM 管理 -->
</dependency>
</dependencies>
dependencyManagement 与 dependencies 的区别
| 特性 | dependencyManagement | dependencies |
|---|---|---|
| 是否引入依赖 | 否,只管理版本 | 是,实际引入依赖 |
| 子模块是否继承 | 是 | 是 |
| 版本覆盖 | 子模块可覆盖 | 子模块可覆盖 |
| 使用场景 | 统一版本管理 | 声明项目依赖 |
使用 BOM(Bill of Materials)
BOM 是一种特殊的 POM,用于定义一组相关依赖的版本:
<dependencyManagement>
<dependencies>
<!-- Spring Boot BOM -->
<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>
使用 BOM 后,引入相关依赖时不需要指定版本:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本由 BOM 管理 -->
</dependency>
</dependencies>
常用的 BOM:
spring-boot-dependenciesspring-framework-bomjunit-bomjackson-bom
依赖分析
分析依赖
Maven 提供了依赖分析工具,帮助识别潜在问题:
# 分析依赖
mvn dependency:analyze
# 查看已解析的依赖
mvn dependency:resolved
# 查看依赖信息
mvn dependency:list
dependency:analyze 输出示例:
[INFO] --- maven-dependency-plugin:3.3.0:analyze ---
[INFO] Used declared dependencies found:
[INFO] org.springframework:spring-webmvc:jar:5.3.23:compile
[INFO] Used undeclared dependencies found:
[WARNING] org.springframework:spring-core:jar:5.3.23:compile
[INFO] Unused declared dependencies found:
[WARNING] commons-logging:commons-logging:jar:1.2:compile
输出解释:
- Used declared dependencies:已使用且已声明的依赖(正常)
- Used undeclared dependencies:已使用但未声明的依赖(警告:应该显式声明)
- Unused declared dependencies:已声明但未使用的依赖(警告:可能可以移除)
查看依赖冲突
# 查看依赖冲突
mvn dependency:tree -Dverbose
# 查看被排除的依赖
mvn dependency:tree -Dverbose | grep "omitted for conflict"
依赖管理最佳实践
1. 显式声明所有使用的依赖
不要依赖传递性依赖,显式声明项目直接使用的所有依赖:
<!-- 即使 spring-webmvc 会传递引入 spring-core,也应该显式声明 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.23</version>
</dependency>
</dependencies>
2. 使用 dependencyManagement 统一版本
在多模块项目中,使用父 POM 的 dependencyManagement 统一管理依赖版本。
3. 使用 BOM 管理框架依赖
对于 Spring Boot、Jackson 等框架,使用官方提供的 BOM 确保版本兼容。
4. 定期更新依赖
定期检查并更新依赖版本,获取安全补丁和新特性:
# 检查可更新的依赖
mvn versions:display-dependency-updates
5. 排除不需要的传递性依赖
如果传递性依赖导致冲突或不需要,使用 exclusions 排除。
6. 使用正确的依赖范围
根据依赖的实际用途选择合适的 scope,避免将测试依赖或 provided 依赖打包到最终构件中。
小结
本章我们学习了:
- 如何声明依赖和查找依赖坐标
- 六种依赖范围及其使用场景
- 传递性依赖的概念和范围传递规则
- 依赖冲突的解决策略
- 如何排除依赖和使用可选依赖
- dependencyManagement 的使用方法
- 依赖分析工具的使用
- 依赖管理的最佳实践
在下一章中,我们将学习 Maven 的构建生命周期。