Maven依赖管理
# Maven依赖管理
# 依赖配置
依赖是指当前项目运行所需的 jar 包或其他资源。一个项目可以在 pom.xml 的 <dependencies> 标签中配置多个依赖。
# 基本配置格式
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.29</version>
<scope>compile</scope>
</dependency>
</dependencies>
2
3
4
5
6
7
8
# 核心配置项
| 标签 | 说明 | 是否必填 |
|---|---|---|
groupId | 依赖的组织 ID | 是 |
artifactId | 依赖的项目 ID | 是 |
version | 依赖的版本号 | 是 |
scope | 依赖的作用范围 | 否 |
optional | 是否为可选依赖 | 否 |
exclusions | 排除传递依赖 | 否 |
type | 依赖类型,默认为 jar | 否 |
classifier | 依赖的分类器(如 sources、javadoc) | 否 |
# 依赖传递
# 依赖的传递性
Maven 的依赖关系具有传递性,分为两种:
- 直接依赖:在当前项目的
pom.xml中通过<dependency>直接配置的依赖关系。 - 间接依赖(传递依赖):直接依赖所依赖的其他资源,会自动传递到当前项目中。
项目 A → 依赖 B(直接依赖)
项目 B → 依赖 C(直接依赖)
则:项目 A → 依赖 C(间接依赖)
2
3
# 依赖传递冲突问题
当项目中出现同一资源的不同版本时,Maven 按以下原则解决冲突:
| 原则 | 说明 |
|---|---|
| 路径优先 | 当依赖中出现相同资源时,层级越深,优先级越低;层级越浅,优先级越高 |
| 声明优先 | 当资源在相同层级被依赖时,pom.xml 中配置顺序靠前的覆盖配置顺序靠后的 |
| 特殊优先 | 当同级配置了相同资源的不同版本时,后配置的覆盖先配置的 |
# 可选依赖
可选依赖(Optional Dependency)用于指示某些依赖项不对外传递。
# 使用场景
- 当你的项目依赖于一个库,但只有在某些特定条件下才会使用它。
- 当你想对外隐藏某个依赖,不让下游项目自动继承。
# 配置方式
在 pom.xml 中使用 <optional>true</optional> 标签来定义可选依赖:
<dependencies>
<!-- 必需的依赖项 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>core-lib</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 可选的依赖项 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>optional-lib</artifactId>
<version>2.0.0</version>
<!-- true 表示对外隐藏,不传递给下游项目 -->
<optional>true</optional>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 效果
- Maven 在构建过程中不会自动将可选依赖传递给依赖该项目的其他项目。
- 可选依赖仅在当前项目中可用。
- 如果下游项目也需要该依赖,必须显式声明。
# 排除依赖
排除依赖用于主动断开某个传递依赖,被排除的资源无需指定版本号。
<dependency>
<groupId>com.example</groupId>
<artifactId>project-a</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>com.example</groupId>
<artifactId>unwanted-lib</artifactId>
<!-- 注意:exclusion 中不需要指定 version -->
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
# 依赖范围
依赖的 jar 包默认可以在任何地方使用,可以通过 <scope> 标签设定其作用范围。
# 依赖范围一览表
| 依赖范围 | 编译 | 测试 | 运行 | 打包 | 说明 |
|---|---|---|---|---|---|
compile | ✅ | ✅ | ✅ | ✅ | 默认范围,适用于所有阶段 |
provided | ✅ | ✅ | ❌ | ❌ | 编译和测试需要,运行时由 JDK 或容器提供(如 Servlet API) |
runtime | ❌ | ✅ | ✅ | ✅ | 运行和测试需要,编译时不需要(如 JDBC 驱动) |
test | ❌ | ✅ | ❌ | ❌ | 仅测试阶段需要(如 JUnit) |
system | ✅ | ✅ | ❌ | ❌ | 类似 provided,但需通过 <systemPath> 指定本地路径,不推荐使用 |
import | - | - | - | - | 仅用于 <dependencyManagement> 中导入 BOM 的依赖管理 |
# 各依赖范围详解
# compile(默认)
编译依赖是默认范围,适用于所有阶段(编译、测试、运行)。如果不指定 <scope>,则使用 compile 范围。
# provided
提供的依赖范围,表示这些依赖在编译和测试阶段是必需的,但在运行时由 JDK 或运行环境(如 Java EE 容器)提供,因此不会被打包到最终产物中。
典型场景:javax.servlet-api 在编译时需要,但运行时由 Tomcat 等 Web 容器提供。
# runtime
运行时依赖范围,表示这些依赖在运行和测试阶段是必需的,但在编译阶段不需要。这些依赖会被打包到最终产物中。
典型场景:JDBC 驱动在编译时只需要接口,运行时才需要具体的驱动实现。
# test
测试依赖范围,仅在测试阶段有效,不会被打包到最终产物中。
典型场景:JUnit、Mockito 等测试框架。
# system
系统依赖范围,与 provided 类似,但必须通过 <systemPath> 标签指定依赖的本地路径,而不是从 Maven 仓库获取。不推荐使用,因为会降低项目的可移植性。
# import
导入依赖范围,仅用于 <dependencyManagement> 中,用于导入 BOM(Bill of Materials)中定义的依赖版本管理。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.18</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2
3
4
5
6
7
8
9
10
11
# 依赖范围的传递性
依赖范围会影响传递依赖的范围,规则如下:
| 直接依赖 \ 间接依赖 | compile | provided | runtime | test |
|---|---|---|---|---|
| compile | compile | - | runtime | - |
| runtime | runtime | - | runtime | - |
| provided | provided | - | provided | - |
| test | - | - | - | - |
说明:
provided和test范围的依赖不会传递。
# 配置示例
<dependencies>
<!-- 编译依赖(默认) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>compile-dep</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<!-- 已提供依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- 运行时依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- 系统依赖(不推荐) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>system-dep</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/system-dep.jar</systemPath>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 依赖管理(dependencyManagement)
# 为什么 <dependencyManagement> 不会直接将依赖引入 classpath?
在 Maven 项目中,<dependencyManagement> 元素的主要目的是集中管理依赖的版本、范围和其他配置属性,但不会直接将这些依赖添加到项目的 classpath 中。
# 1. 集中管理和声明
- 集中管理:
<dependencyManagement>提供了一种方式来集中定义依赖的版本、作用域等信息,确保所有子模块使用一致的依赖配置。 - 声明而非引入:它只是声明了依赖项及其配置,而不会实际将这些依赖添加到项目的构建路径(classpath)中。这意味着你可以在父 POM 或 BOM 文件中定义大量依赖,而不会因为这些声明导致不必要的依赖被引入。
# 2. 避免重复和冲突
- 避免重复声明:如果每个子模块都单独声明依赖的版本,容易导致版本不一致或重复声明的问题。通过
<dependencyManagement>,子模块只需要声明依赖的groupId和artifactId,不必指定版本号,减少了冗余配置。 - 防止冲突:由于依赖版本由父 POM 或 BOM 统一管理,可以有效避免不同子模块之间依赖版本不一致导致的冲突问题。
# 3. 灵活性和控制
- 按需引入:子模块可以根据需要选择性地引入所需的依赖,而不是被动地继承所有声明的依赖。这使得项目结构更加灵活,允许开发者根据实际情况调整依赖。
- 覆盖版本:虽然
<dependencyManagement>定义了默认版本,但子模块仍然可以选择覆盖这些版本,提供了一定的灵活性。
# 4. 性能优化
- 减少解析时间:Maven 在解析依赖时,只会处理实际引入的依赖,而不是所有声明的依赖。这有助于提高构建性能,尤其是在大型项目中有大量依赖的情况下。
# 5. 示例说明
父 POM 使用 <dependencyManagement> 管理依赖版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.7.18</version>
</dependency>
</dependencies>
</dependencyManagement>
2
3
4
5
6
7
8
9
10
11
12
13
14
子模块选择性引入需要的依赖(无需指定版本号):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本号由父 POM 的 dependencyManagement 统一管理 -->
</dependency>
</dependencies>
2
3
4
5
6
7
在这个例子中,spring-boot-starter-web 被引入到了子模块的 classpath 中,而 spring-boot-starter-data-jpa 没有被引入。这样既保持了灵活性,又避免了不必要的依赖加载。
# 总结
<dependencyManagement> 不会直接将依赖引入到项目的 classpath 中,主要是为了实现依赖版本的集中管理,避免重复和冲突,提供灵活性和控制,并优化构建性能。这种方式特别适用于大型多模块项目,能够显著简化依赖管理并提高项目的可维护性。