原创文章,总结不易,禁止侵权转载。转载请先私信知乎「编程大K」征求同意
原创文章,总结不易,禁止侵权转载。转载请先私信知乎「编程大K」征求同意
原创文章,总结不易,禁止侵权转载。转载请先私信知乎「编程大K」征求同意
什么是Mavne
Maven 是一个项目管理工具,它包含了一个项目对象模型 (POM:Project Object Model),一组标准集合。由于 Maven 使用标准目录布局和默认构建生命周期,开发团队几乎可以立即自动化项目的构建基础设施。在多个开发团队环境的情况下,Maven 可以在很短的时间内按照标准设置工作方式。
Maven 之前,更多的是使用 Ant 的项目构建工具,Ant 有一个特点,每次都得写,每次都写的差不多,配置也臃肿。所以,后来搞出来 Maven。Maven 就是最先进的版本构建工具吗?不是的,只不过,目前在 Java 领域 Maven 使用比较多。除了 Maven,还有 Gradle。
它的主要功能有:
- 提供了一套标准化的项目结构;
- 提供了一套标准化的构建流程(编译,测试,打包,发布……);
- 提供了一套依赖管理机制。
为了实现上面的主要功能,Maven提供了两大核心:
- 依赖管理:对 jar 的统一管理(Maven 提供了一个 Maven 的中央仓库,当我们在项目中添加完会自动去中央仓库下载相关的依赖,并且解决依赖的依赖问题)
- **项目构建:**对项目进行编译、测试、打包、部署、上传到私服等
Maven模型
下面来谈一谈Maven模型的整体结构,包括三个部分:
- 项目对象模型 (Project Object Model)
- 依赖管理模型(Dependency)
- 插件(Plugin)
如上图所示,包括蓝、黄两个部分分别对应着依赖关系和项目构建两大核心功能。
首当其冲的一个核心就是项目对象模型,也就是经常使用的pom.xml
另外一个就是项目构建,Maven的项目构建可以按照生命周期具备以下三个标准生命周期:
- clean:项目清理的处理
- default(或 build):项目部署的处理
- site:项目站点文档创建的处理
项目对象模型(POM)
POM 代表项目对象模型。它是 Maven 中的基本工作单元。它是一个 XML 文件,作为 pom.xml 驻留在项目的跟目录中。POM 不仅包含有关项目的信息以及 Maven 用于构建项目的各种配置详细信息, 还包含目标和插件。
在执行任务或目标时,Maven 在当前目录中查找 POM。它读取 POM,获取所需的配置信息,然后执行目标。在POM文件中常见的配置包括一下几点:
- 项目依赖
- 插件
- 目标
- 建立档案
- 项目版本
- 开发商
- 邮件列表
在创建 POM 之前,我们应该首先确定项目组(groupId)、项目名称(artifactId) 和版本,因为这些属性有助于在存储库中唯一标识项目。
<project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.coderV</groupId> <artifactId>coderV</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>com.coderV</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> //... </plugin> </plugins> </build> </project>接下来仔细拆解POM文件结构
1、项目标识符
Maven 使用一组标识符(也称为坐标)来唯一标识项目并指定应如何打包项目工件:
- groupId: 创建项目的公司或组的唯一基本名称
- artifactId: 项目的唯一名称
- **version:**项目的一个版本
- packaging: 一种打包方法(例如WAR / JAR / ZIP)
其中所有 POM 文件都需要项目元素和三个必填字段:groupId、artifactId、version。
其中的前三个 ( groupId:artifactId:version ) 结合形成唯一标识符,并且是您指定项目将使用的外部库(例如 JAR)版本的机制。
详细说明一些基本的项目标识符6
参数名称 描述 Project root 这是项目root的标签。您需要指定基本架构设置,例如 apache 架构和 w3.org 规范。 Model version 模型版本应为 4.0.0。 **groupId ** 这是项目组/公司的 ID。这在组织或项目中通常是唯一的。 artifactId 这是项目的 ID。这通常是项目的名称 version 这是项目的版本2、依赖管理
Maven 使用存储库的概念进行依赖管理,项目中使用的这些外部库称为依赖项。Maven 中的依赖项管理功能可确保从中央存储库自动下载这些库,因此您不必将它们存储在本地。
这是 Maven 的一个关键特性,并提供以下好处:
- 结合本地仓库大幅减少从远程存储库下载的数量,减少存储使用量
- 管理项目依赖性变得更容易
- 提供了一个有效的平台,用于在组织内外交换二进制组件,而无需每次都从源代码手动安装组件
为了声明对外部库的依赖,您需要提供库的groupId、artifactId、verison。让我们看一个例子:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.16</version> </dependency>当 Maven 处理依赖项时,它会将 Spring Core 库下载到本地 Maven 存储库中,并在项目中使用
传递依赖
如下图所示,项目 A 依赖于项目 B,B 又依赖于项目 C,此时 B 是 A 的直接依赖,C 是 A 的间接依赖
Maven 的依赖传递机制是指:不管 Maven 项目存在多少间接依赖,POM 中都只需要定义其直接依赖,不必定义任何间接依赖,Maven 会动读取当前项目各个直接依赖的 POM,将那些必要的间接依赖以传递性依赖的形式引入到当前项目中。Maven 的依赖传递机制能够帮助用户一定程度上简化 POM 的配置。
基于 A、B、C 三者的依赖关系,根据 Maven 的依赖传递机制,我们只需要在项目 A 的 POM 中定义其直接依赖 B,在项目 B 的 POM 中定义其直接依赖 C,Maven 会解析 A 的直接依赖 B的 POM ,将间接依赖 C 以传递性依赖的形式引入到项目 A 中。
通过这种依赖传递关系,可以使依赖关系树迅速增长到一个很大的量级,很有可能会出现依赖重复,依赖冲突等情况,Maven 针对这些情况提供了如下功能进行处理。
- 依赖范围(Dependency scope)
- 依赖调解(Dependency mediation)
- 可选依赖(Optional dependencies)
- 排除依赖(Excluded dependencies)
- 依赖管理(Dependency management)
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 中。依赖范围与三种 classpath 的关系一览表,如下所示。
依赖范围 编译 classpath 测试 classpath 运行 classpath 例子 compile √ √ √ log4j test - √ - junit provided √ √ - servlet-api runtime - - √ JDBC-driver system √ √ - 非 Maven 仓库的本地依赖依赖范围对传递依赖的影响
项目 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。
依赖调节
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 依赖调节的第一原则已经无法解决,需要使用第二原则:先声明者优先。
Maven排除依赖和可选依赖
我们知道 Maven 依赖具有传递性,例如 A 依赖于 B,B 依赖于 C,在不考虑依赖范围等因素的情况下,Maven 会根据依赖传递机制,将间接依赖 C 引入到 A 中。但如果 A 出于某种原因,希望将间接依赖 C 排除,那该怎么办呢?Maven 为用户提供了两种解决方式:排除依赖(Dependency Exclusions)和可选依赖(Optional Dependencies)。
排除依赖
假设存在这样的依赖关系,A 依赖于 B,B 依赖于 X,B 又依赖于 Y。B 实现了两个特性,其中一个特性依赖于 X,另一个特性依赖于 Y,且两个特性是互斥的关系,用户无法同时使用两个特性,所以 A 需要排除 X,此时就可以在 A 中将间接依赖 X 排除。
排除依赖是通过在 A 中使用 exclusions 元素实现的,该元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖,示例代码如下:
<dependencies> <dependency> <groupId>net.biancheng.www</groupId> <artifactId>B</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <!-- 设置排除 --> <!-- 排除依赖必须基于直接依赖中的间接依赖设置为可以依赖为 false --> <!-- 设置当前依赖中是否使用间接依赖 --> <exclusion> <!--设置具体排除--> <groupId>net.biancheng.www</groupId> <artifactId>X</artifactId> </exclusion> </exclusions> </dependency> </dependencies>关于 exclusions 元素及排除依赖说明如下:
- 排除依赖是控制当前项目是否使用其直接依赖传递下来的间接依赖;
- exclusions 元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖;
- exclusion 元素用来设置具体排除的间接依赖,该元素包含两个子元素:groupId 和 artifactId,用来确定需要排除的间接依赖的坐标信息;
- exclusion 元素中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖,无需指定版本 version。
可选依赖
与上文的应用场景相同,也是 A 希望排除间接依赖 X,除了在 B 中设置可选依赖外,我们还可以在 B 中将 X 设置为可选依赖。
设置可选依赖
在 B 的 POM 关于 X 的依赖声明中使用 optional 元素,将其设置成可选依赖,示例配置如下:
<dependencies> <dependency> <groupId>net.biancheng.www</groupId> <artifactId>X</artifactId> <version>1.0-SNAPSHOT</version> <!--设置可选依赖 --> <optional>true</optional> </dependency> </dependencies>关于 optional 元素及可选依赖说明如下:
- 可选依赖用来控制当前依赖是否向下传递成为间接依赖;
- optional 默认值为 false,表示可以向下传递称为间接依赖;
- 若 optional 元素取值为 true,则表示当前依赖不能向下传递成为间接依赖。
排除依赖 VS 可选依赖
排除依赖和可选依赖都能在项目中将间接依赖排除在外,但两者实现机制却完全不一样。
-
- 排除依赖是控制当前项目是否使用其直接依赖传递下来的接间依赖;
- 可选依赖是控制当前项目的依赖是否向下传递;
- 可选依赖的优先级高于排除依赖;
- 若对于同一个间接依赖同时使用排除依赖和可选依赖进行设置,那么可选依赖的取值必须为 false,否则排除依赖无法生效。
3、Maven 仓库
在 Maven 术语中,仓库是存储所有项目的 jar包的地方,Maven 可以轻松使用它们。根据类别可以将Maven仓库分为三类:
1)本地仓库 默认情况下,每个本地计算机的用户目录下都有一个路径名为.m2/repository/的仓库目录,这个就是本地的仓库
也可以在 settings.xml 文件配置本地仓库的路径
2)远程仓库 远程仓库也称为私服,由公司或者项目组维护,是开发人员自己定义的远程仓库,其中包含项目所需要的库或其他的jar包,可以通过<repositories>标签来制定远程仓库地址
<repositories> <repository> <id>companyname.lib1</id> <url>http://download.companyname.org/maven2/lib1</url> </repository> </repositories>3)中央仓库
Maven中央仓库是Maven社区提供的仓库。它包含大量常用的库。当 Maven 在本地存储库中找不到任何依赖项时,就会从中央仓库中搜索
其中中央仓库有以下几点需要注意:
- 此存储库由 Maven 社区管理。
- 不需要配置,但可以替换中央仓库的源为制定的镜像源
- 它需要互联网访问才能搜索。
Maven 依赖加载顺序
当我们执行 Maven 构建命令时,Maven 开始按以下顺序查找依赖库:
- 第 1 步- 在本地存储库中搜索依赖项,如果未找到,则转到第 2 步,否则执行进一步处理。
- 第 2 步- 如果没有提到远程存储库,则跳转到第 3 步。在一个或多个远程存储库中搜索依赖项,如果找到则将其下载到本地存储库以供将来参考
- 第 3 步- 在中央存储库中搜索依赖项,将其下载到本地存储库以供将来参考。如果未找到 Maven 只是停止处理并抛出错误(无法找到依赖项)
4、属性(properties)
自定义属性有助于pom.xml文件更易于阅读和维护。一个十分经典的使用场景就是,通过自定义属性来定义项目依赖项的版本。
Maven的properties是一个占位符,通过properties定义不同属性名的值
例如下面的例子中,通过<properties>定义了一个spring.version的属性,其具体的值为 5.3.16;如果想要将 Spring 升级到更新的版本,就只需更改<spring.version>属性标签内的值,所有在其<version>标签中使用该属性的依赖项都将更新。
<properties> <spring.version>5.3.16</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> </dependencies>另一个常用的场景就是使用<properties>定义构建路径的变量,例如
<properties> <project.build.folder>${project.build.directory}/tmp/</project.build.folder> </properties> <plugin> //... <outputDirectory>${project.resources.build.folder}</outputDirectory> //... </plugin>5、Build
build部分也是 Maven POM 中非常重要的部分。它提供有关默认 Maven目标、已编译项目的目录和应用程序的最终名称的信息。默认build如下所示:
<build> <!--当项目没有规定目标(Maven2叫做阶段(phase))时的默认值, --> <!--必须跟命令行上的参数相同例如jar:jar,或者与某个阶段(phase)相同例如install、compile等 --> <defaultGoal>install</defaultGoal> <!-- 构建产生的所有文件存放的目录,默认为${basedir}/target,即项目根目录下的target --> <directory>${basedir}/target</directory> <!-- 产生的构件的文件名,默认值是${artifactId}-${version}--> <finalName>${artifactId}-${version}</finalName> <!--当filtering开关打开时,使用到的过滤器属性文件列表。 --> <!--项目配置信息中诸如${spring.version}之类的占位符会被属性文件中的实际值替换掉 --> <filters> <filter>filters/filter1.properties</filter> </filters> //... </build>编译工件的默认输出文件夹名为*target*,打包工件的最终名称由*artifactId*和*version*组成,但您可以随时更改。
6、配置文件
构建配置文件是一组配置值,可用于设置或覆盖 Maven 构建的默认值。使用构建配置文件,您可以为不同的环境(例如生产环境和开发环境)自定义构建。
配置文件主要分为两种类型,一种是在定义在项目上pom.xml文件中,另一种是定义在setting.xml上
<profiles> <profile> <id>production</id> <build> <plugins> <plugin> //... </plugin> </plugins> </build> </profile> <profile> <id>development</id> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <plugins> <plugin> //... </plugin> </plugins> </build> </profile> </profiles>定义在pom.xml文件上的profile可以看做pom.xml的副本,拥有与pom.xml相同的子元素与配置方法。
正如您在上面的示例中看到的,默认配置文件设置为development。如果要运行生产配置文件,可以使用以下 Maven 的 -P 命令显示的激活一个profile:
mvn clean install -P productionMaven 构建生命周期
每个 Maven 构建都遵循指定的生命周期。您可以执行多个构建生命周期目标,包括编译项目代码、创建包以及在本地 Maven 依赖项存储库中安装存档文件的目标。
以下列表显示了最重要的 Maven生命周期阶段:
- validate: 检查项目的正确性
- compile: 将提供的源代码编译成二进制工件
- test: 执行单元测试
- package: 将编译后的代码打包*到归档文件中
- integration-test: 执行额外的测试,这需要打包
- verify——检查包是否有效
- install – 将包文件安装到本地 Maven 存储库
- deploy – 将包文件部署到远程服务器或存储库
1、Clean 生命周期
当我们执行 mvn post-clean 命令时,Maven 调用 clean 生命周期,它包含以下阶段:
- pre-clean:执行一些需要在clean之前完成的工作
- clean:移除所有上一次构建生成的文件
- post-clean:执行一些需要在clean之后立刻完成的工作
在一个生命周期中,运行某个阶段的时候,它之前的所有阶段都会被运行,也就是说,如果执行 mvn clean 将运行pre-clean, clean两个生命周期阶段;运行 mvn post-clean ,则运行pre-clean, clean, post-clean三个生命周期阶段
2、Default (Build) 生命周期
这是 Maven 的主要生命周期,被用于构建应用,包括下面的 23 个阶段:
生命周期阶段 描述 validate(校验) 校验项目是否正确并且所有必要的信息可以完成项目的构建过程。 initialize(初始化) 初始化构建状态,比如设置属性值。 generate-sources(生成源代码) 生成包含在编译阶段中的任何源代码。 process-sources(处理源代码) 处理源代码,比如说,过滤任意值。 generate-resources(生成资源文件) 生成将会包含在项目包中的资源文件。 process-resources (处理资源文件) 复制和处理资源到目标目录,为打包阶段最好准备。 compile(编译) 编译项目的源代码。 process-classes(处理类文件) 处理编译生成的文件,比如说对Java class文件做字节码改善优化。 generate-test-sources(生成测试源代码) 生成包含在编译阶段中的任何测试源代码。 process-test-sources(处理测试源代码) 处理测试源代码,比如说,过滤任意值。 generate-test-resources(生成测试资源文件) 为测试创建资源文件。 process-test-resources(处理测试资源文件) 复制和处理测试资源到目标目录。 test-compile(编译测试源码) 编译测试源代码到测试目标目录. process-test-classes(处理测试类文件) 处理测试源码编译生成的文件。 test(测试) 使用合适的单元测试框架运行测试(Juint是其中之一)。 prepare-package(准备打包) 在实际打包之前,执行任何的必要的操作为打包做准备。 package(打包) 将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件。 pre-integration-test(集成测试前) 在执行集成测试前进行必要的动作。比如说,搭建需要的环境。 integration-test(集成测试) 处理和部署项目到可以运行集成测试环境中。 post-integration-test(集成测试后) 在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。 verify (验证) 运行任意的检查来验证项目包有效且达到质量标准。 install(安装) 安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。 deploy(部署) 将最终的项目包复制到远程仓库中与其他开发者和项目共享。Site 生命周期
Maven Site 插件一般用来创建新的报告文档、部署站点等。
- pre-site:执行一些需要在生成站点文档之前完成的工作
- site:生成项目的站点文档
- post-site: 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
- site-deploy:将生成的站点文档部署到特定的服务器上
插件(Plugin)
插件管理与依赖管理原理一样、不同的是定义的元素标签不一样、插件管理标签是build标签的子标签pluginManagement中
pluginManagement 用来做插件管理的。它是表示插件声明,即你在项目中的pluginManagement下声明了插件,Maven不会加载该插件,pluginManagement声明可以被继承。如下面的例子
<pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.1</version> <configuration> <attach>true</attach> </configuration> <executions> <execution> <phase>compile</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> </plugins> </pluginManagement> <!-- 子POM--> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> </plugin> </plugins>原创文章,总结不易,禁止侵权转载。转载请先私信知乎「编程大K」征求同意
hi,我是 @编程大K ,擅长后端、人工智能、大数据处理的伪全能工程师,在平台输出关于技术文章、职业发展和自我提升的干货,看到感兴趣的实事热榜也忍不住唠两句嗑。想与你一同进步,可以通过这些回答来认识我呀:
机器学习方面的论文应该在哪找?
计算机互联网「寒冬」是暂时趋势还是永久趋势?
对你影响最深的计算机书籍是哪一本?
Spark的内存计算 主要体现在 哪些方面?