依赖管理
# 依赖管理
# 依赖配置
依赖指当前项目运行所需的 jar,一个项目可以设置多个依赖格式。
# 依赖传递
# 依赖具有传递性
- 直接依赖:在当前项目中通过依赖配置建立的依赖关系
- 间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源
# 依赖传递冲突问题
- 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高(项目依赖树中的位置决定)
- 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序在后的(前覆盖后)
- 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的(不同版本,后覆盖前)
# 可选依赖
对外隐藏当前所依赖的资源 -- 不透明
可选依赖(Optional Dependency)是一种依赖管理策略,用于指示某些依赖项不是强制性的,或者在编译时不是必须的,但在运行时可能需要。
这种依赖类型通常用于以下情况:
- 当你的项目依赖于一个库,但只有在某些特定条件下才会使用它。
- 当你想提供向后兼容性,允许使用旧版本的库,但同时支持新版本的功能。
在 Java 的 Maven 构建工具中,可以使用 <optional>
标签来定义可选依赖。
以下是一个 Maven pom.xml
文件中的示例:
<project>
<!-- ... 其他配置 ... -->
<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 默认为 false 不隐藏, 为 true 则对外隐藏 -->
<optional>true</optional>
</dependency>
<!-- ... 其他依赖项 ... -->
</dependencies>
<!-- ... 其他配置 ... -->
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在这个示例中,optional-lib
被标记为可选依赖。这意味着:
- Maven 在构建过程中不会自动将这个依赖项传递给其他依赖这个项目的项目。
- 这个依赖项仅在当前项目中需要时才被包含。
# 排除依赖
主动断开依赖的资源,被排除的资源无需指定版本 -- 不需要 exclusions
(标签)
# 依赖范围
依赖的 jar 默认情况可以在任何地方使用, 可以通过 <scope>
标签设定其作用范围。
- compile(默认):
- 编译依赖是默认范围,适用于所有阶段:编译、测试和运行。如果你不指定依赖范围,它将使用
compile
范围。
- 编译依赖是默认范围,适用于所有阶段:编译、测试和运行。如果你不指定依赖范围,它将使用
- provided:
- 提供的依赖范围意味着这些依赖项在编译和测试阶段是必需的,但在运行时,它们应该由 JDK 或运行环境(如Java EE容器)提供。因此,这些依赖项不会被包含在最终的打包文件中。
- runtime:
- 运行时依赖范围的依赖项在运行和测试阶段是必需的,但在编译阶段不是必需的。这意味着这些依赖项会被包含在最终的打包文件中,但不会包含在编译时的类路径中。
- test:
- 测试依赖范围的依赖项仅在测试阶段有效,它们对于编译主代码或运行应用程序是不必要的。这些依赖项不会被包含在最终的打包文件中。
- system:
- 系统依赖范围的依赖项需要从指定的系统路径中获取,而不是从 Maven 仓库中获取。这种方式不推荐使用,因为它降低了项目的可移植性。使用
system
范围时,必须提供<systemPath>
标签,指向依赖项的本地路径。
- 系统依赖范围的依赖项需要从指定的系统路径中获取,而不是从 Maven 仓库中获取。这种方式不推荐使用,因为它降低了项目的可移植性。使用
- import:
- 导入依赖范围用于在子项目中引入父项目中
<dependencyManagement>
部分定义的依赖项。这允许子项目继承父项目的依赖项版本和配置,而不必直接复制依赖项声明。
- 导入依赖范围用于在子项目中引入父项目中
- optional:
- 可选依赖范围允许某个依赖项声明为可选的,这意味着其他依赖项可以选择包含或排除这个可选依赖。可选依赖项在编译和测试阶段是可用的,但在运行时可能不可用。
- 注意:
- 依赖范围的继承性:除了
test
和provided
范围之外,其他范围的依赖项默认会继承到子模块中。test
范围的依赖项不会传递到子模块,而provided
范围的依赖项在运行时也不会传递。
- 依赖范围的继承性:除了
示例:
<project>
<!-- ... 其他配置 ... -->
<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>com.example</groupId>
<artifactId>runtime-dep</artifactId>
<version>2.0.0</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>/path/to/system-dep.jar</systemPath>
</dependency>
<!-- ... 其他依赖项 ... -->
</dependencies>
<!-- ... 其他配置 ... -->
</project>
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
43
44
45
46
47
48
49
50
依赖配置管理
# 为什么 <dependencyManagement>
不会直接将依赖引入到项目的 classpath 中
在 Maven 项目中,<dependencyManagement>
元素的主要目的是集中管理依赖的版本、范围和其他配置属性,但不会直接将这些依赖添加到项目的 classpath 中。以下是具体原因和机制:
# 1. 集中管理和声明
- 集中管理:
<dependencyManagement>
提供了一种方式来集中定义依赖的版本、作用域等信息,确保所有子模块使用一致的依赖配置。 - 声明而非引入:它只是声明了依赖项及其配置,而不会实际将这些依赖添加到项目的构建路径(classpath)中。这意味着你可以在父 POM 或 BOM 文件中定义大量依赖,而不会因为这些声明导致不必要的依赖被引入。
# 2. 避免重复和冲突
- 避免重复声明:如果每个子模块都单独声明依赖的版本,容易导致版本不一致或重复声明的问题。通过
<dependencyManagement>
,子模块只需要声明依赖而不必指定版本号,减少了冗余配置。 - 防止冲突:由于依赖版本由父 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>
</dependency>
</dependencies>
2
3
4
5
6
在这个例子中,spring-boot-starter-web
被引入到了子模块的 classpath 中,而 spring-boot-starter-data-jpa
没有被引入。这样既保持了灵活性,又避免了不必要的依赖加载。
# 总结
<dependencyManagement>
不会直接将依赖引入到项目的 classpath 中,主要是为了实现依赖版本的集中管理,避免重复和冲突,提供灵活性和控制,并优化构建性能。这种方式特别适用于大型多模块项目,能够显著简化依赖管理并提高项目的可维护性。