跳到主要内容

依赖管理

依赖管理是 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 根据坐标从仓库中查找并下载依赖。

查找依赖坐标

如何找到需要的依赖坐标?可以使用以下方式:

  1. Maven 中央仓库搜索:访问 MVN Repository 搜索
  2. IDE 提示:IntelliJ IDEA 等 IDE 提供依赖搜索功能
  3. 官方文档:框架官方文档通常会提供 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
  • 等等...

传递性依赖的范围

传递性依赖的范围会受到原始依赖范围的影响:

直接依赖范围传递依赖范围最终范围
compilecompilecompile
compileruntimeruntime
compileprovided不传递
compiletest不传递
providedcompile不传递
providedruntime不传递
runtimecompileruntime
runtimeruntimeruntime
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 的区别

特性dependencyManagementdependencies
是否引入依赖否,只管理版本是,实际引入依赖
子模块是否继承
版本覆盖子模块可覆盖子模块可覆盖
使用场景统一版本管理声明项目依赖

使用 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-dependencies
  • spring-framework-bom
  • junit-bom
  • jackson-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 依赖打包到最终构件中。

小结

本章我们学习了:

  1. 如何声明依赖和查找依赖坐标
  2. 六种依赖范围及其使用场景
  3. 传递性依赖的概念和范围传递规则
  4. 依赖冲突的解决策略
  5. 如何排除依赖和使用可选依赖
  6. dependencyManagement 的使用方法
  7. 依赖分析工具的使用
  8. 依赖管理的最佳实践

在下一章中,我们将学习 Maven 的构建生命周期。