Loading

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 通过依赖调节来确定间接依赖的引入路径。

依赖调节遵循以下两条原则:

  1. 引入路径短者优先
  2. 先声明者优先

以上两条原则,优先使用第一条原则解决,第一条原则无法解决,再使用第二条原则解决。

引入路径短者优先

引入路径短者优先,顾名思义,当一个间接依赖存在多条引入路径时,引入路径短的会被解析使用。

例如,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,否则排除依赖无法生效。
posted @ 2022-03-19 18:17  OMaster  阅读(50)  评论(0编辑  收藏  举报