Maven基础
一、简介
Maven是一个项目管理工具,包含:
- 一个项目对象模型(Project Object Model)
- 一组标准集合(约定优于配置)
- 一个项目生命周期(Project Lifecycle)
- 一个依赖管理系统(Dependency Management System)和用来运行定义在生命周期街道(phase)中插件(plugin)目标(goal)的逻辑。
二、安装
下载Maven,并解压,配置环境变量。
三、Maven仓库
- 本地仓库:主要作用为在本地缓存jar包。项目需要依赖某些jar包时,先去本地仓库找,找不到再去私服找,私服找不到就去中央仓库找。
- 私服(非必须):主要作用为存储公司内部jar。假如某项目A依赖别的项目B,不可能将项目B引入到本地,也不可能每次找项目B团队打包,因此将项目B的jar上传到私服,每次直接从私服获取即可。另外,私服还充当了中央仓库的角色。
- 中央仓库:主要作用为仓库存储了互联网上的jar,由Maven团队来维护。
中央仓库可详见:Maven远程仓库优先级
四、Mavne依赖
1. 依赖坐标
依然是通过groupId + artifactId + version来在仓库中定位一个项目:
- groupId:parent的子元素,父项目的groupId,用于定位父项目;
- artifactId:parent的子元素,父项目的artifactId,用于定位父项目;
- version:parent的子元素,父项目的version,用于定位父项目;
2. 依赖范围
首先,我们要知道 Maven 在对项目进行编译、测试和运行时,会分别使用三套不同的 classpath。Maven 项目构建时,在不同阶段引入到 classpath 中的依赖时不同的。例如编译时,Maven 会将与编译相关的依赖引入到编译 classpath 中;测试时,Maven 会将与测试相关的的依赖引入到测试 classpath 中;运行时,Maven 会将与运行相关的依赖引入到运行 classpath 中。
我们可以在 POM 的依赖声明使用 scope 元素来控制依赖与三种 classpath(编译 classpath、测试 classpath、运行 classpath )之间的关系,这就是依赖范围。
Maven 具有以下 6 中常见的依赖范围,如下表所示,常用的只有前四中,后两者一般不推荐使用。
依赖范围 | 是否打包 | 描述 |
---|---|---|
compile | 被打包,被间接依赖 | 编译依赖范围,scope 元素的缺省值。使用此依赖范围的 Maven 依赖,对于三种 classpath 均有效,即该 Maven 依赖在上述三种 classpath 均会被引入。例如,log4j 在编译、测试、运行过程都是必须的。 |
test | 不被打包,不被间接依赖 | 测试依赖范围。使用此依赖范围的 Maven 依赖,只对测试 classpath 有效。例如,Junit 依赖只有在测试阶段才需要。 |
provided | 不被打包,不被间接依赖 | 已提供依赖范围。使用此依赖范围的 Maven 依赖,只对编译 classpath 和测试 classpath 有效。例如,servlet-api 依赖对于编译、测试阶段而言是需要的,但是运行阶段,由于外部容器已经提供,故不需要 Maven 重复引入该依赖>。 |
runtime | 被打包,被间接依赖 | 运行时依赖范围。使用此依赖范围的 Maven 依赖,只对测试 classpath、运行 classpath 有效。例如,JDBC 驱动实现依赖,其在编译时只需 JDK 提供的 JDBC 接口即可,只有测试、运行阶段才需要实现了 JDBC 接口的驱动。 |
system | - | 系统依赖范围,其效果与 provided 的依赖范围一致。其用于添加非 Maven 仓库的本地依赖,通过依赖元素 dependency 中的 systemPath 元素指定本地依赖的路径。鉴于使用其会导致项目的可移植性降低,一般不推荐使用。 |
import | - | 导入依赖范围,该依赖范围只能与 dependencyManagement 元素配合使用,其功能是将目标 pom.xml 文件中 dependencyManagement 的配置导入合并到当前 pom.xml 的 dependencyManagement 中。 |
依赖传递性:指的是父子Module间的依赖传递,有传递性即父Module的依赖会传递到子Module,反之则不会;所有依赖在父子Module中都有传递性
依赖打包:指的是jar包是否包含该依赖,即A项目依赖B.jar包,B项目中被打包依赖会被引入A项目,反之则不会;
依赖范围与三种 classpath 的关系一览表,如下所示。
依赖范围 | 编译 classpath | 测试 classpath | 运行 classpath | 例子 |
---|---|---|---|---|
compile | √ | √ | √ | spring-core |
test | - | √ | - | junit |
provided | √ | √ | - | servlet-api |
runtime | - | - | √ | JDBC-driver |
依赖范围对传递依赖的影响
项目 A 依赖于项目 B,B 又依赖于项目 C,此时我们可以将 A 对于 B 的依赖称之为第一直接依赖,B 对于 C 的依赖称之为第二直接依赖。
B 是 A 的直接依赖,C 是 A 的间接依赖,根据 Maven 的依赖传递机制,间接依赖 C 会以传递性依赖的形式引入到 A 中,但这种引入并不是无条件的,它会受到依赖范围的影响。
传递性依赖的依赖范围受第一直接依赖和第二直接依赖的范围影响,如下表所示。
依赖范围 | compile | test | provided | runtime |
---|---|---|---|---|
compile | compile | - | - | runtime |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
注:上表中,左边第一列表示第一直接依赖的依赖范围,上边第一行表示第二直接依赖的依赖范围。交叉部分的单元格的取值为传递性依赖的依赖范围,若交叉单元格取值为“-”,则表示该传递性依赖不能被传递。
通过上表,可以总结出以下规律(*):
- 当第二直接依赖的范围是 compile 时,传递性依赖的范围与第一直接依赖的范围一致;
- 当第二直接依赖的范围是 test 时,传递性依赖不会被传递;
- 当第二直接依赖的范围是 provided 时,只传递第一直接依赖的范围也为 provided 的依赖,且传递性依赖的范围也为 provided;
- 当第二直接依赖的范围是 runtime 时,传递性依赖的范围与第一直接依赖的范围一致,但 compile 例外,此时传递性依赖的范围为 runtime。
3. 依赖调节
Maven 的依赖传递机制可以简化依赖的声明,用户只需要关心项目的直接依赖,而不必关心这些直接依赖会引入哪些间接依赖。但当一个间接依赖存在多条引入路径时,为了避免出现依赖重复的问题,Maven 通过依赖调节来确定间接依赖的引入路径。
依赖调节遵循以下两条原则:
- 引入路径短者优先
- 先声明者优先
以上两条原则,优先使用第一条原则解决,第一条原则无法解决,再使用第二条原则解决。
引入路径短者优先
引入路径短者优先,顾名思义,当一个间接依赖存在多条引入路径时,引入路径短的会被解析使用。
例如,A 存在这样的依赖关系:
A->B->C->D(1.0)
A->X->D(2.0)
D 是 A 的间接依赖,但两条引入路径上有两个不同的版本,很显然不能同时引入,否则造成重复依赖的问题。根据 Maven 依赖调节的第一个原则:引入路径短者优先,D(1.0)的路径长度为 3,D(2.0)的路径长度为 2,因此间接依赖 D(2.0)将从 A->X->D(2.0) 路径引入到 A 中。
先声明者优先
先声明者优先,顾名思义,在引入路径长度相同的前提下,POM 文件中依赖声明的顺序决定了间接依赖会不会被解析使用,顺序靠前的优先使用。
例如,A 存在以下依赖关系:
A->B->D(1.0)
A->X->D(2.0)
D 是 A 的间接依赖,其两条引入路径的长度都是 2,此时 Maven 依赖调节的第一原则已经无法解决,需要使用第二原则:先声明者优先。即A项目先引入B,则间接依赖D(1.0),否则简介依赖D(2.0)。
4. 可选依赖和排除依赖
可选依赖
假设存在这样的依赖关系,A 依赖于 B,B 依赖于 X,B 又依赖于 Y。B 实现了两个特性,其中一个特性依赖于 X,另一个特性依赖于 Y,且两个特性是互斥的关系,用户无法同时使用两个特性,所以 A 需要排除 X,此时就可以在 B 中将 X 设置为可选依赖。
<dependencies>
<dependency>
<groupId>org.omaster</groupId>
<artifactId>X</artifactId>
<version>1.0</version>
<!--设置可选依赖 -->
<optional>true</optional>
</dependency>
</dependencies>
关于 optional 元素及可选依赖说明如下:
- 可选依赖用来控制当前依赖是否向下传递成为间接依赖;
- optional 默认值为 false,表示可以向下传递称为间接依赖;
- 若 optional 元素取值为 true,则表示当前依赖不能向下传递成为间接依赖。
排除依赖
与上文的应用场景相同,也是 A 希望排除间接依赖 X,除了在 B 中设置可选依赖外,我们还可以通过在 A 中使用 exclusions 元素实现的,该元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖。
<dependencies>
<dependency>
<groupId>org.omaster</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
<exclusions>
<!-- 设置排除 -->
<!-- 排除依赖必须基于直接依赖中的间接依赖设置为可以依赖为 false -->
<!-- 设置当前依赖中是否使用间接依赖 -->
<exclusion>
<!--设置具体排除-->
<groupId>org.omaster</groupId>
<artifactId>X</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
关于 exclusions 元素及排除依赖说明如下:
-
排除依赖是控制当前项目是否使用其直接依赖传递下来的间接依赖;
-
exclusions 元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖;
-
exclusion 元素用来设置具体排除的间接依赖,该元素包含两个子元素:groupId 和 artifactId,用来确定需要排除的间接依赖的坐标信息;
-
exclusion 元素中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖,无需指定版本 version。
可选依赖 VS 排除依赖
可选依赖和排除依赖都能在项目中将间接依赖排除在外,但两者实现机制却完全不一样。
- 可选依赖是控制当前项目的依赖是否向下传递;
- 可选依赖的优先级高于排除依赖;
- 排除依赖是控制当前项目是否使用其直接依赖传递下来的接间依赖;
- 若对于同一个间接依赖同时使用排除依赖和可选依赖进行设置,那么可选依赖的取值必须为 false,否则排除依赖无法生效。