Maven

文章参考Maven教程 (biancheng.net)

一、前言

Maven 是使用 Java 语言编写的,因此它具有跨平台性,主要服务于基于 Java 平台的项目构建、依赖管理和项目信息管理。当然,Maven 也可被用于构建和管理其他的项目,例如 C#,Ruby,Scala 和其他语言编写的项目;

Maven 是现今最流行的项目管理和理解工具之一,通过它我们可以很方便地完成项目的构建,例如 Maven 只需一条命令 mvn clean install 就可以完成项目的清理、编译、运行单元测试、生成文档、打包和部署等繁琐的构建工作,为我们节约了大量宝贵的时间;


好的我知道你想问什么,项目的管理和构建啥的不是直接使用IDE就可以完成??(毕竟之前使用的不管是Pycharm还是Codeblocks或者VS都是如此),怎么JAVA还非得整的和Linux环境下的Make一样来个项目管理工具?

纠结其本质,我们会提出这样一个问题:Maven到底是干啥用的?(不要给我整那些虚幻的概念,最好直接上例子)

参考视频【IDEA版胎教级】都学这么久编程了你Maven是什么都不知道嘛?太丢人了吧?_哔哩哔哩_bilibili


目前无论使用IDEA还是Eclipse等其他IDE,使用里面ANT工具。ANT工具帮助我们进行编译,打包运行等工作;
Apache基于ANT进行了升级,研发出了全新的自动化构建工具Maven(可以说Maven现在已经逐步替代ANT了);
Maven是Apache的一款开源的项目管理工具;
以后无论是普通javase项目还是javaee项目,我们都创建的是Maven项目(因为ANT能够做的Maven都能做);
Maven使用项目对象模型(POM-Project Object Model,项目对象模型)的概念,可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具。在Maven中每个项目都相当于是一个对象,对象(项目)和对象(项目)之间是有关系的。关系包含了:依赖、继承、聚合,实现Maven项目可以更加方便的实现导jar包、拆分项目等效果;

为啥咱们之后都要创建Maven项目使用Maven来管理呢?因为咱们初学的时候创建的项目都很简单,可以统称为单体结构项目,即分层项目(树结构,所有的业务逻辑都是基于这一个项目来实现的,这也是你为啥之前使用Pycharm和Codeblocks的时候没有意识到需要自己进行手动管理项目,因为你之前创建的全都是单体结构项目);

However,JAVA的项目可不像之前我们开发的那些那么简单,随着用户基数的增大和项目复杂程度的提升,项目只使用单体结构会显得捉襟见肘,所以现在的JAVA项目一般使用的都是分布式架构,一旦采用分布式架构意味着项目的个数会越来越多(多个小项目组成一个大项目),所以我们需要一个有效的管理工具来管理组织这些小项目,这个工具就是Maven

1.Maven简介

Maven 是一款基于 Java 平台的项目管理和整合工具,它将项目的开发和管理过程抽象成一个项目对象模型(POM)。开发人员只需要做一些简单的配置,Maven 就可以自动完成项目的编译、测试、打包、发布以及部署等工作;

Maven 是使用 Java 语言编写的,因此它和 Java 一样具有跨平台性,这意味着无论是在 Windows ,还是在 Linux 或者 Mac OS 上,都可以使用相同的命令进行操作;

Maven 使用标准的目录结构和默认构建生命周期,Maven 能够帮助开发者完成以下任务:

  • 构建项目
  • 生成文档
  • 创建报告
  • 维护依赖
  • 软件配置管理
  • 发布
  • 部署

总而言之,Maven 简化并标准化了项目构建过程。它将项目的编译,生成文档,创建报告,发布,部署等任务无缝衔接,构建成一套完整的生命周期;

1.1 目标

Maven 的主要目标是为开发人员提供如下内容:

  • 一个可重复使用,可维护且易于理解的项目综合模型;
  • 与此模型进行交互的工具和插件;

1.2 核心理念

约定优于配置(Convention Over Configuration)是 Maven 最核心的设计理念之一 ,Maven对项目的目录结构、测试用例命名方式等内容都做了规定,凡是使用 Maven 管理的项目都必须遵守这些规则;

Maven 项目构建过程中,会自动创建默认项目结构,开发人员仅需要在相应目录结构下放置相应的文件即可;

下图展示了项目源代码文件,资源文件和其他配置在 Maven 项目中的默认位置

1.3 特点

Maven 具有以下特点:

  1. 设置简单。
  2. 所有项目的用法一致。
  3. 可以管理和自动进行更新依赖。
  4. 庞大且不断增长的资源库。
  5. 可扩展,使用 Java 或脚本语言可以轻松的编写插件。
  6. 几乎无需额外配置,即可立即访问新功能。
  7. 基于模型的构建:Maven 能够将任意数量的项目构建为预定义的输出类型,例如 JAR,WAR。
  8. 项目信息采取集中式的元数据管理:使用与构建过程相同的元数据,Maven 能够生成一个网站(site)和一个包含完整文档的 PDF。
  9. 发布管理和发行发布:Maven 可以与源代码控制系统(例如 Git、SVN)集成并管理项目的发布。
  10. 向后兼容性:您可以轻松地将项目从旧版本的 Maven 移植到更高版本的 Maven 中。
  11. 并行构建:它能够分析项目依赖关系,并行构建工作,使用此功能,可以将性能提高 20%-50%。
  12. 更好的错误和完整性报告:Maven 使用了较为完善的错误报告机制,它提供了指向 Maven Wiki 页面的链接,您将在其中获得有关错误的完整描述。

二、Maven基础

1.Maven POM

1.1 简介

POM(Project Object Model,项目对象模型)是 Maven 的基本组件,它是以 xml 文件的形式存放在项目的根目录下,名称为 pom.xml;

POM 中定义了项目的基本信息,用于描述项目如何构建、声明项目依赖等等;

当 Maven 执行一个任务时,它会先查找当前项目的 POM 文件,读取所需的配置信息,然后执行任务。在 POM 中可以设置如下配置:

  • 项目依赖
  • 插件
  • 目标
  • 构建时的配置文件
  • 版本
  • 开发者
  • 邮件列表

在创建 POM 之前,首先要确定项目的工程组(groupId)、名称(artifactId)和版本(version),在仓库中这些属性是项目的唯一标识;

所有的 Maven 项目都有一个 POM 文件,所有的 POM 文件都必须有 project 元素和 3 个必填字段:groupId、artifactId 以及 version

下面展示一个基本的POM文件

1
2
3
4
5
6
7
8
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.biancheng.www</groupId>
<artifactId>maven</artifactId>
<version>0.0.1-SNAPSHOT</version>
</project>

1.2 Super POM

无论 POM 文件中是否显示的声明,所有的 POM 均继承自一个父 POM,这个父 POM 被称为 Super POM,它包含了一些可以被继承的默认设置;

Maven 使用 effective pom (Super POM 的配置加上项目的配置)来执行相关任务;Super POM替开发人员在 pom.xml 中做了一些最基本的配置,当然开发人员可以根据需要重写其中的配置信息;

执行以下命令 ,就可以查看 Super POM 的默认配置

1
mvn help:effective-pom

注意执行这个命令的时候需要在对应根目录下存在POM文件,不然会爆红

咱们现在在桌面创建一个pom.xml文件,将前面的示例复制粘贴,接着执行上述shell命令(但是这个输出实在太多了就只展示一部分)来查看其Super POM的默认配置;

可以看到 effective-pom 中包含了 Maven 在执行任务时需要用到的默认目录结构、输出目录、插件、仓库和报表目录等内容

实际开发过程中,Maven 的 pom.xml 文件不需要手工编写,Maven 提供了大量的原型(Archetype)插件来创建项目,包括项目结构和 pom.xml,下面我们将介绍如何使用原型插件来创建Maven项目;

2.Maven操作

尽管对于Maven我们现在可以说完全不懂,但是在介绍基础概念之前我认为很有必要实际操作演示一遍Maven创建、构建项目和运行的过程;

2.1 创建Maven项目

Maven 提供了大量不同类型的 Archetype 模板,通过它们可以帮助用户快速的创建 Java 项目,其中最简单的模板就是 maven-archetype-quickstart,它只需要用户提供项目最基本的信息,就能生成项目的基本结构及 POM 文件;

下面通过 maven-archetype-quickstart 原型,创建一个基于 Maven 的 Java 项目

在指定根目录下的shell中输入如下命令

1
mvn archetype:generate -DgroupId=net.biancheng.www -DartifactId=helloMaven -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

参数说明:

  • -DgroupId: 项目组 ID,通常为组织名或公司网址的反写。
  • -DartifactId: 项目名。
  • -DarchetypeArtifactId: 指定 ArchetypeId,maven-archetype-quickstart 用于快速创建一个简单的 Maven 项目。
  • -DinteractiveMode: 是否使用交互模式。

Maven 开始进行处理,并创建一套完整的 Maven 项目目录结构

在指定目录下我们可以看到成功创建了一个helloMaven项目,该项目使用一套标准的目录结构,如图所示

目录及文件说明:

  • helloMaven:项目名,包含 src 文件夹和 pom.xml。
  • src/main/java:用于存放项目的 Java 文件。
  • src/main/resources:用于存放项目资源文件。
  • src/test/java:用于存放所有测试 Java 文件,如 JUnit 测试类。
  • src/test/resources :用于存放测试资源文件。
  • target:项目输出位置,用于存放编译后的文件。
  • pom.xml:Maven 项目核心配置文件。

Maven 创建项目时,还自动生成了两个Java 文件: App.java 和 AppTest.java:

  • App.java 位于 src/main/java 下;
  • AppTest.java 位于 src/test/java 下;

2.2 Maven项目的构建

在创建好Maven项目之后,我们将对该项目进行构建和测试;

打开命令行窗口,跳转到 …\helloMaven 目录,执行以下 mvn 命令,对该项目进行构建

1
mvn clean package

关于构建过程中报错“不再支持源选项 5,请使用 7 或更高版本”,解决方法参考(5条消息) maven打包提示,不再支持源选项 5。请使用 7 或更高版本。_王家五哥的博客-CSDN博客_不再支持源选项5


等待项目构建完成,我们会发现在项目的根目录下生成了一个target目录,包含了如下内容

  • Maven 命令中包含了两个命令:clean 和 package,其中 clean 负责清理 target 目录,package 负责将项目构建并打包输出为 jar 文件。
  • classes:源代码编译后存放在该目录中。
  • test-classes:测试源代码编译后并存放在该目录中。
  • surefire-reports:Maven 运行测试用例生成的测试报告存放在该目录中。
  • helloMaven-1.0-SNAPSHOT.jar:Maven 对项目进行打包生成的 jar 文件。

接着进入target的class目录执行

1
java net.biancheng.www.App

3.Maven坐标

在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称为构件。在 Maven 世界中存在着数十万甚至数百万构件,在引入坐标概念之前,当用户需要使用某个构件时,只能去对应的网站寻找,但各个网站的风格迥异,这使得用户将大量的时间浪费在搜索和寻找上,严重地影响了研发效率。为了解决这个问题,于是 Maven 引入了 Maven 坐标的概念;

Maven坐标规定:世界上任何一个构件都可以使用 Maven 坐标并作为其唯一标识,Maven 坐标包括 groupId、artifactId、version、packaging 等元素,只要用户提供了正确的坐标元素,Maven 就能找到对应的构件;

任何一个构件都必须明确定义自己的坐标,这是 Maven 的强制要求,任何构件都不能例外。我们在开发 Maven 项目时,也需要为其定义合适的坐标,只有定义了坐标,其他项目才能引用该项目生成的构件;

Maven项目坐标定义如下

1
2
3
4
5
6
<project> 
<groupId>net.biancheng.www</groupId>
<artifactId>helloMaven</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
</project>

Maven 坐标主要由以下元素组成:

  • groupId: 项目组 ID,定义当前 Maven 项目隶属的组织或公司,通常是唯一的,它的取值一般是项目所属公司或组织的网址或 URL 的反写,例如 net.biancheng.www;

  • artifactId: 项目 ID,通常是项目的名称;

  • version:版本;

  • packaging:项目的打包方式,默认值为 jar(只有该元素是可选的,上述三个元素都是必须定义的);

4.Maven依赖

Maven的介绍中提到,Maven是一款优秀的依赖管理工具,什么是依赖呢?

通俗的说,如果一个 Maven 构建所产生的构件(例如 Jar 文件)被其他项目引用,那么该构件就是其他项目的依赖;

4.1 依赖声明

Maven 坐标是依赖的前提,所有 Maven 项目必须明确定义自己的坐标,只有这样,它们才可能成为其他项目的依赖。当一个项目的构件成为其他项目的依赖时,该项目的坐标才能体现出它的价值;

当 Maven 项目需要声明某一个依赖时,通常只需要在其 POM 中配置该依赖的坐标信息,Maven 会根据坐标自动将依赖下载到项目中;

例如,某个项目中使用 servlet-api 作为其依赖,其配置如下

1
2
3
4
5
6
7
8
9
10
11
12
<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/maven-v4_0_0.xsd">
...
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

dependencies 元素可以包含一个或者多个 dependency 子元素,用以声明一个或者多个项目依赖,每个依赖都可以包含以下元素:

  • groupId、artifactId 和 version:依赖的基本坐标元素,对于任何一个依赖来说,这三个基本坐标元素是最重要的,Maven 根据基本坐标元素才能找到需要的依赖。
  • type:依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值是 jar。
  • scope:依赖的范围。
  • optional:标记依赖是否可选。
  • exclusions:用来排除传递性依赖。

大部分依赖声明只包含 groupId、artifactId 和 version 三个元素,至于 scope、optional 以及 exclusions 等元素,了解即可;

4.2 获取依赖

通常情况下,绝大部分依赖的 Maven 坐标都能在 https://mvnrepository.com/ 中获取,使用方法参考Maven依赖 (biancheng.net)

4.3 依赖传递

Maven 依赖传递是 Maven 的核心机制之一,它能够一定程度上简化 Maven 的依赖配置;

Maven 的依赖传递机制是指:不管 Maven 项目存在多少间接依赖,POM 中都只需要定义其直接依赖,不必定义任何间接依赖,Maven 会动读取当前项目各个直接依赖的 POM,将那些必要的间接依赖以传递性依赖的形式引入到当前项目中,由此可见,Maven 的依赖传递机制能够帮助用户一定程度上简化 POM 的配置;

项目 A 依赖于项目 B,B 又依赖于项目 C,此时 B 是 A 的直接依赖,C 是 A 的间接依赖

基于 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)

4.3.1 依赖范围

Maven 在对项目进行编译、测试和运行时,会分别使用三套不同的 classpath,Maven项目在构建的时候在不同阶段引入到相应的classpath中的依赖是不同的:

  • 编译时,Maven 会将与编译相关的依赖引入到编译 classpath 中;

  • 测试时,Maven 会将与测试相关的的依赖引入到测试 classpath 中;

  • 运行时,Maven 会将与运行相关的依赖引入到运行 classpath 中;

依赖范围:可以在 POM 的依赖声明使用 scope 元素来控制依赖与三种 classpath(编译 classpath、测试 classpath、运行 classpath )之间的关系,这就是依赖范围;

Maven有常见的6种依赖范围

依赖范围与三种classpath的关系如下

4.3.2 依赖调节

Maven 的依赖传递机制可以简化依赖的声明,用户只需要关心项目的直接依赖,而不必关心这些直接依赖会引入哪些间接依赖,但当一个间接依赖存在多条引入路径时,为了避免出现依赖重复的问题,Maven 通过依赖调节来确定间接依赖的引入路径;

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

  1. 引入路径短者优先
    • 当一个间接依赖存在多条引入路径时,引入路径短的会被解析使用
  2. 先声明者优先
    • 在引入路径长度相同的前提下,POM 文件中依赖声明的顺序决定了间接依赖会不会被解析使用,顺序靠前的优先使用

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

具体例子可以参考Maven依赖传递 (biancheng.net)理解;

4.3.3 排除依赖

因为Maven依赖的传递性,在不考虑依赖范围等因素的情况下,Maven会根据依赖传递机制将间接依赖引入,但如果我们不希望引入间接依赖,也就是将依赖C排除,此时应该如何处理?Maven为用户提供了两种解决方式:排除依赖和可选依赖;

假设存在这样的依赖关系,A 依赖于 B,B 依赖于 X,B 又依赖于 Y。B 实现了两个特性,其中一个特性依赖于 X,另一个特性依赖于 Y,且两个特性是互斥的关系,用户无法同时使用两个特性,假设 A 不需要使用X特性,此时就可以在 A 中将间接依赖 X 排除;

排除依赖是通过在 A 中使用 exclusions 元素实现的,该元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖;

关于 exclusions 元素及排除依赖说明如下:

  • 排除依赖是控制当前项目是否使用其直接依赖传递下来的间接依赖;
  • exclusions 元素下可以包含若干个 exclusion 子元素,用于排除若干个间接依赖;
  • exclusion 元素用来设置具体排除的间接依赖,该元素包含两个子元素:groupId 和 artifactId,用来确定需要排除的间接依赖的坐标信息;
  • exclusion 元素中只需要设置 groupId 和 artifactId 就可以确定需要排除的依赖,无需指定版本 version;

4.3.4 可选依赖

与上文的应用场景相同,也是 A 希望排除间接依赖 X,我们还可以在 B 中将 X 设置为可选依赖;

在 B 的 POM 关于 X 的依赖声明中使用 optional 元素,将其设置成可选依赖;

关于 optional 元素及可选依赖说明如下:

  • 可选依赖用来控制当前依赖是否向下传递成为间接依赖;
  • optional 默认值为 false,表示可以向下传递称为间接依赖;
  • 若 optional 元素取值为 true,则表示当前依赖不能向下传递成为间接依赖;

关于排除依赖和可选依赖的对比,排除依赖和可选依赖都能在项目中将间接依赖排除在外,但两者实现机制却完全不一样:

  • 排除依赖是控制当前项目是否使用其直接依赖传递下来的间接依赖;
  • 可选依赖是控制当前项目的依赖是否向下传递(即一个是控制其是否传递,一个是控制其是否使用传递下来的依赖);
  • 可选依赖的优先级高于排除依赖;
  • 若对于同一个间接依赖同时使用排除依赖和可选依赖进行设置,那么可选依赖的取值必须为 false,否则排除依赖无法生效。

4.3.5 依赖管理

子模块可以通过继承获得父模块中声明的全部依赖,这样虽然避免了在各个子模块 POM 中重复进行依赖声明,但也极有可能造成子模块中引入一些不必要的依赖。为此 Maven 引入了 dependencyManagement 来对依赖进行管理;

Maven 可以通过 dependencyManagement 元素对依赖进行管理,它具有以下 2 大特性:

  • 在该元素下声明的依赖不会实际引入到模块中,只有在 dependencies 元素下同样声明了该依赖,才会引入到模块中;
  • 该元素能够约束 dependencies 下依赖的使用,即 dependencies 声明的依赖若未指定版本,则使用 dependencyManagement 中指定的版本,否则将覆盖 dependencyManagement 中的版本;

在实际的开发过程中,dependencyManagement 很少会单独使用,通常它需要与 Maven 继承或依赖范围 import 配合使用才能展现它的优势;

继承依赖管理

由于 dependencyManagement 元素是可以被继承的,因此我们可以在父模块 POM 中使用 dependencyManagement 元素声明所有子模块的依赖,然后在各个子模块 POM 使用 dependencies 元素声明实际用到的依赖即可,这样既可以让子模块能够继承父模块的依赖配置,还能避免将不必要的依赖引入到子模块中;

在父模块 Root 中使用 dependencyManagement 元素声明的依赖,既不会给 Root 模块引入依赖,也不会给其子模块引入依赖,但这段配置是可以被继承的;

使用这种依赖管理机制似乎并不能减少太多 POM 配置,但我们仍然推荐使用这种方式,其原因主要有 2 个:

  • 在父模块中使用 dependencyManagement 声明依赖能够统一项目内依赖的版本,子模块无须声明版本,也就不会出现多个子模块使用同一依赖项版本不一致的情况,降低依赖冲突的几率;
  • dependencyManagement 声明的依赖不会被实际引入,子模块需要什么依赖就自己引入,增加了灵活性,避免引入一些不必要的依赖

导入依赖管理

import 依赖范围只能与 dependencyManagement 元素配合使用才会有效,其功能是将目标 pom.xml 中的 dependencyManagement 配置导入合并到当前 pom.xml 的 dependencyManagement 中;

由于 import 依赖范围的特殊性,一般都是指向打包类型为 pom 的模块,所以 type 元素的值一般为 pom;

若存在多个模块,它们使用的依赖版本都是一致的,则就可以定义一个使用 dependencyManagement 专门管理依赖的 POM,然后在各个模块中导入这些依赖管理配置;

5.Maven仓库

前面已经介绍过,在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称为构件;

Maven 在某个统一的位置存储所有项目的构件,这个统一的位置,我们就称之为仓库,即仓库就是存放依赖和插件的地方;

任何的构件都有唯一的坐标,该坐标定义了构件在仓库中的唯一存储路径,当 Maven 项目需要某些构件时,只要其 POM 文件中声明了这些构件的坐标,Maven 就会根据这些坐标找自动到仓库中找到并使用它们;

项目构建完成生成的构件,也可以安装或者部署到仓库中,供其他项目使用;

5.1 仓库的分类

Maven 仓库可以分为 2 个大类:

  • 本地仓库
  • 远程仓库(远程仓库还可分为3个小类):
    • 中央仓库是由 Maven 社区提供的一种特殊的远程仓库,它包含了绝大多数流行的开源构件。在默认情况下,当本地仓库没有 Maven 所需的构件时,会首先尝试从中央仓库下载。
    • 私服是一种特殊的远程仓库,它通常设立在局域网内,用来代理所有外部的远程仓库。它的好处是可以节省带宽,比外部的远程仓库更加稳定。
    • 除了中央仓库和私服外,还有很多其他公共仓库,例如 JBoss Maven 库,Java.net Maven 库等等。

Maven 根据坐标寻找构件时,它会首先查看本地仓库,若本地仓库存在此构件,则直接使用;若本地仓库不存在此构件,Maven 就会去远程仓库查找,若发现所需的构件后,则下载到本地仓库使用。如果本地仓库和远程仓库都没有所需的构件,则 Maven 报错;

5.2 本地仓库

Maven 本地仓库实际上就是本地计算机上的一个目录(文件夹),它会在第一次执行 Maven 命令时被创建;

Maven 本地仓库可以储存本地所有项目所需的构件。当 Maven 项目第一次进行构建时,会自动从远程仓库搜索依赖项,并将其下载到本地仓库中。当项目再进行构建时,会直接从本地仓库搜索依赖项并引用,而不会再次向远程仓库获取;

Maven 本地仓库默认地址为 C:%USER_HOME%.m2\repository,当然我们也可以重新定义本地仓库的位置,只需要修改 %MAVEN_HOME%\conf 目录下的 settings.xml 文件,通过 localRepository 元素定义另一个本地仓库地址;

1
2
3
4
5
6
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>D:/myRepository/repository</localRepository>
</settings>

构件只有储存在本地仓库中,才能被其他的 Maven 项目使用,构件想要进入本地仓库,除了从远程仓库下载到本地仓库外,还可以使用命令 mvn install 将本地项目的输出构件安装到本地仓库中;

5.3 中央仓库

中央仓库是由 Maven 社区提供的一种特殊的远程仓库,它包含了绝大多数流行的开源构件。在默认情况下,当本地仓库没有 Maven 所需的构件时,会首先尝试从中央仓库下载;

中央仓库具有如下特点:

  • 这个仓库由 Maven 社区管理
  • 不需要配置
  • 需要通过网络才能访问

我们可以通过 Maven 社区提供的 URL:http://search.maven.org/#browse,浏览其中的构件。中央仓库包含了绝大多数流行的开源 Java 构件及其源码、作者信息和许可证信息等。一般来说,Maven 项目所依赖的构件都可以从中央仓库下载到;

虽然中央仓库属于远程仓库的范畴,但由于它的特殊性,一般会把它与其他远程仓库区分开,我们常说的远程仓库,一般不包括中央仓库;

5.4 远程仓库

如果 Maven 在本地仓库和中央仓库中都找不到依赖的库文件,它就会停止构建过程并输出错误信息到控制台。为避免这种情况的发生,Maven 还提供了远程仓库的概念,它是一种由开发人员自己定制的仓库,其中包含了供其他项目使用的代码库或者构件;


综上,可以总结,当通过Maven构建项目时,Maven会按照如下顺序查找依赖的构建

  1. 从本地仓库查找构件,如果没有找到,跳到第 2 步,否则继续执行其他处理。
  2. 从中央仓库查找构件,如果没有找到,并且已经设置其他远程仓库,然后移动到第 4 步;如果找到,那么将构件下载到本地仓库中使用。
  3. 如果没有设置其他远程仓库,Maven 则会停止处理并抛出错误。
  4. 在远程仓库查找构件,如果找到,则会下载到本地仓库并使用,否则 Maven 停止处理并抛出错误。

6.Maven生命周期

在 Maven 出现之前,项目构建的生命周期就已经存在,开发人员每天都在对项目进行清理,编译,测试及部署,但由于没有统一的规范,不同公司甚至不同项目之间的构建的方式都不尽相同;

Maven 从大量项目和构建工具中学习和反思,最后总结了一套高度完美的,易扩展的生命周期。这个生命周期将项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建过程进行了抽象和统一;

6.1 生命周期与插件

Maven 生命周期是抽象的,其本身不能做任何实际工作,这些实际工作(如源代码编译)都通过调用Maven插件(之后我们会介绍)中的插件目标(plugin goal)完成;

我们可以将 Maven 生命周期理解成一个抽象父类,将插件理解成其子类,将插件目标理解成子类中重写的方法;

生命周期抽象了构建的各个步骤,定义了它们的执行顺序,但却没有提供具体的实现;插件中完成了对构建过程的实现,想要完成某个构建步骤,调用插件中的一个或多个插件目标即可;

生命周期中的每个构建过程都可以绑定一个或多个插件目标,且 Maven 为大多数的构建步骤都绑定了默认的插件。例如,针对源代码编译的插件是 maven-compiler-plugin、针对集成测试的插件是 maven-surefire-plugin 等;

6.2 标准生命周期

Maven拥有三套标准的生命周期:

  • clean:用于清理项目
  • default:用于构建项目
  • site:用于建立项目站点

每套生命周期包含一系列的构建阶段(phase),这些阶段是有顺序的,且后面的阶段依赖于前面的阶段。用户与 Maven 最直接的交互方式就是调用这些生命周期阶段。以 clean 生命周期为例,它包含 pre-clean、clean 以及 post-clean 三个阶段:

  • 当用户调用 pre-clean 阶段时,则只有 pre-clean 阶段执行;
  • 当用户调用 clean 阶段时,pre-clean 和 clean 阶段都会执行;
  • 当用户调用 post-clean 阶段时,则 pre-clean、clean 以及 post-clean 三个阶段都会执行;

通过将阶段名传递给 mvn 命令,就可以调用构建阶段

1
mvn post-clean

生命周期的独立性:与构建阶段的前后依赖关系不同,三套生命周期本身是相互独立的,用户可以只调用 clean 生命周期的某个阶段,也可以只调用 default 生命周期的某个阶段,而不会对其他生命周期造成任何影响

6.2.1 clean生命周期

clean 生命周期包括以下 3 个阶段。

  • pre-clean(清理前)
  • clean(清理)
  • post-clean(清理后)

可以通过在 clean 生命周期的各个阶段定义目标来修改这部分的操作行为;

6.2.2 default生命周期

default 生命周期定义了项目真正构建时所需要的所有步骤,它是所有生命周期中最核心,最重要的部分;

default 生命周期包含非常多的阶段,如下所示

6.2.3 site生命周期

site生命周期的目的是建立和部署项目站点,Maven 能够根据 POM 包含的信息,自动生成一个友好的站点,该站点包含一些与该项目相关的文档;

site 生命周期包含以下 4 个阶段:

  • pre-site
  • site
  • post-site
  • site-deploy

7.Maven插件

Maven 实际上是一个依赖插件执行的框架,它执行的每个任务实际上都由插件完成的。Maven 的核心发布包中并不包含任何 Maven 插件,它们以独立构件的形式存在, 只有在 Maven 需要使用某个插件时,才会去仓库中下载;

如下表所示,Maven 提供了如下 2 种类型的插件

7.1 插件目标

对于 Maven 插件而言,为了提高代码的复用性,通常一个 Maven 插件能够实现多个功能,每一个功能都是一个插件目标,即 Maven 插件是插件目标的集合。我们可以把插件理解为一个类,而插件目标是类中的方法,调用插件目标就能实现对应的功能;


插件目标的通用写法如下

1
[插件名]:[插件目标名]

例如,maven-compiler-plugin 插件的 compile 目标的通用写法如下

1
maven-compiler-plugin:compile

使用Maven命令执行插件目标,语法如下

1
mvn [插件名]:[目标名]

例如,调用 maven-compiler-plugin 插件的 compile 目标,命令如下

1
mvn compiler:compile

7.2 插件绑定

为了完成某个具体的构建任务,Maven 生命周期的阶段需要和 Maven 插件的目标相互绑定。例如,代码编译任务对应了default 生命周期的 compile 阶段,而 maven-compiler-plugin 插件的 compile 目标能够完成这个任务,因此将它们进行绑定就能达到代码编译的目的;

7.2.1 内置绑定

Maven 默认为一些核心的生命周期阶段绑定了插件目标,当用户调用这些阶段时,对应的插件目标就会自动执行相应的任务

上表中,default 生命周期中只列出了绑定了插件目标的阶段,它还有很多其他的阶段,但这些阶段默认没有绑定任何插件目标,因此它们也没有任何实际的行为;

7.2.2 自定义绑定

除了内置绑定之外,用户也可以自己选择将某个插件目标绑定到 Maven 生命周期的某个阶段上,这种绑定方式就是自定义绑定。自定义绑定能够让 Maven 在构建过程中执行更多更丰富的任务;

我们常常在自定义绑定的同时通过execution元素定义一些执行配置,executions 下的每一个 executin 子元素都可以用来配置执行一个任务,execution 下各个元素含义如下:

  • id:任务的唯一标识。
  • phase:插件目标需要绑定的生命周期阶段。
  • goals:用于指定一组插件目标,其子元素 goal 用于指定一个插件目标。
  • configuration:该任务的配置,其子元素 tasks 用于指定该插件目标执行的任务。

当插件目标绑定到生命周期的不同阶段时,其执行顺序由生命周期阶段的先后顺序决定。如果多个目标绑定到同一个生命周期阶段,其执行顺序与插件声明顺序一致,先声明的先执行,后声明的后执行。

7.3 插件管理

Maven 使用 dependencyManagement 对依赖进行管理,与之类似地,Maven 中还提供了一个名为 pluginManagement 的元素,它可以帮助用户管理 Maven 插件;

pluginManagement 元素与 dependencyManagement 元素的原理十分相似,在 pluginManagement 元素中可以声明插件及插件配置,但不会发生实际的插件调用行为,只有在 POM 中配置了真正的 plugin 元素,且其 groupId 和 artifactId 与 pluginManagement 元素中配置的插件匹配时,pluginManagement 元素的配置才会影响到实际的插件行为;

使用 pluginManagement 管理插件很简单,只需要将声明插件的配置添加在 pluginManagement 元素中即可;

  • 当项目中的多个模块存在相同的插件时,应当将插件配置移动到父模块的 pluginManagement 元素中,即使各个模块对于同一插件的具体配置不尽相同,也应当在父模块中使用 pluginManagement 元素对插件版本进行统一声明;

  • 我们甚至可以将项目中所有插件的版本信息都在父模块的 POM 中声明,子模块中不再配置任何的版本信息,这样不仅可以统一项目的插件版本,还可以避免出现版本冲突或插件不稳定等问题;

8.Maven&jar包

Maven 是通过仓库对依赖进行管理的,当 Maven 项目需要某个依赖时,只要其 POM 中声明了依赖的坐标信息,Maven 就会自动从仓库中去下载该构件使用;

但在实际的开发过程中,经常会遇到一种情况:某一个项目需要依赖于存储在本地的某个 jar 包,该 jar 包无法从任何仓库中下载的,这种依赖被称为外部依赖本地依赖,那么这种依赖是如何声明的呢?

本节比较难,我们就不细讲了,参考Maven导入本地jar包 (biancheng.net)

9.Maven站点

Maven 不仅仅是一款项目构建和依赖管理工具,它还能够聚合项目信息,促进项目团队间地交流;POM 中可以包含各种项目信息,例如:项目描述、SCM 地址、许可证信息,开发者信息等;

用户可以使用 Maven 提供的 maven-site-plugin 插件让 Maven 生成一个 Web 站点, 以站点的形式发布以上信息;

10.Maven原型

Archetype 是 Maven 项目的模板工具包,它定义了 Maven 项目的基本架构;

Archetype 为开发人员提供了数千种创建 Maven 项目的模板,Maven 通过这些模板可以帮助用户快速的生成项目的目录结构以及 POM 文件;

Maven Archetype 由下面 5 个模块组成:

  • maven-archetype-plugin:Archetype 插件。
  • archetype-packaging:用于描述 Archetype 的生命周期与构建项目软件包。
  • archetype-models:用于描述类与引用。
  • archetype-common:核心类。
  • archetype-testing:用于测试 Maven Archetype 的内部组件。

10.1 maven-archetype-plugin

我们知道 Maven 的所有功能都是通过插件实现的,Archetype 也不例外,它是由一个名为 maven-archetype-plugin 的插件实现的,该插件提供了 ArcheType 的所有功能;

虽然 ArcheType 只是一个插件,但其应用范围十分的广泛,几乎所有的主流 IDE(例如 Eclipse、NetBeans 和 IntelliJ IDEA)都在集成 Maven 时着重继承了 Archetype 特性,以方便用户快速的创建 Maven 项目;

为了满足用户的需求,Maven 为用户提供了大量不同类型的 Archetype 模板;

在执行以下命令时,Maven 会输出一个 ArcheType 列表,每个ArcheType 前面都对应一个编号,我们可以根据不同的需求选择合适的 Archetype;

1
mvn archetype:generate #帮助用户快速的创建 Maven 项目

说到底,Archetype 只是一个模板,为了保持模板的通用性,它的很多重要的信息都是可配置的,在用户选择了 Archetype 后,还需要提供一些关于项目的基本参数,主要包括以下参数:

  • groupId
  • artifactId
  • version
  • package

在输入以上参数后,Archetype 插件就能够为用户生成项目的基本目录结构和 POM 文件;

11.Maven快照

Maven 项目第一次构建时,会自动从远程仓库搜索依赖项,并将其下载到本地仓库中。当项目再进行构建时,会直接从本地仓库搜索依赖项并引用,而不会再次向远程仓库获取。这样的设计能够避免项目每次构建时都去远程仓库下载依赖,减轻了网络带宽的压力,但也带来了问题;

大型的应用软件通常由多个功能模块组成,这些模块一般分别于不同的团队负责开发。假设有两个团队,他们分别负责项目中的 app-ui(前端) 和 data-service(数据服务) 两个模块,且 app-ui 需要依赖 data-service 项目作为数据服务来源;

基于以上假设,若 data-service 团队正在进行快节奏的 bug 修复及功能增强,会在短时间内高频率地更新代码以及发布版本。就会出现以下情况:

  1. data-service 团队每次发布新版本更新代码时,都应该通知 app-ui 团队;
  2. app-ui 团队则需要定期更新其 pom.xml 以获得最新的版本;

这样造成的影响最显著的就是影响开发效率,要解决这个问题,可以使用快照版本:data-servcie 团队每次更新代码都使用快照版本发布到仓库中,app-ui 团队则引用快照版本的依赖,这样 app-ui 不再需要重复修改 pom.xml 中的配置,每次构建时都会自动从仓库中获取最新的构件;

快照:一种特殊的版本,表示当前开发进度的副本,与常规版本的构件不同之处在于快照版本的构件在发布的时候Maven会自动为其打上时间戳,在之后依赖该构件的项目进行构建的时候Maven会自动从仓库中找到最新的快照版本;

定义一个组件或模块为快照版本,只需要在其pom.xml中的version元素的值后加上-SNAPSHOT即可

1
2
3
4
<groupId>net.biancheng.www</groupId>
<artifactId>helloMaven</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>

11.1 SNAPSHOT&RELEASE

Maven仓库分为SNAPSHOT快照仓库和RELEASE发行仓库,Snapshot 快照仓库用于保存开发过程中的不稳定 SNAPSHOT 版本,Release 发行仓库则用来保存稳定的 RELEASE 版本;

Maven 会根据模块的版本号(pom.xml 文件中的 version 元素)中是否带有 -SNAPSHOT 来判断是 SNAPSHOT 版本还是正式 RELEASE 版本:带有 -SNAPSHOT 是SNAPSHOT(快照)版本,不带 -SNAPSHOT 的就是正式 RELEASE(发布)版本;

SNAPSHOT 版本和 RELEASE 版本区别如下

12.Maven继承

关于几个常见概念的区别,可以参考继承、实现、依赖、关联、聚合、组合的联系与区别 - 简书 (jianshu.com)


Maven 在设计时,借鉴了 Java 面向对象中的继承思想,提出了 POM 继承思想;

当一个项目包含多个模块时,可以在该项目中再创建一个父模块,并在其 POM 中声明依赖,其他模块的 POM 可通过继承父模块的 POM 来获得对相关依赖的声明。创建父模块的目的是为了消除子模块 POM 中的重复配置,其中不包含有任何实际代码,因此父模块 POM 的打包类型(packaging)必须是 pom;

如下项目存在多个模块以及相互之间的关系

  • App-UI-WAR 依赖于 App-Core-lib 和 App-Data-lib;
  • Root 是 App-Core-lib 和 App-Data-lib 的父模块;
  • Root 在它的依赖部分定义了 junit 4.9、mysql-connector-java 5.1.18 以及 c3p0 0.9.1 作为其依赖;

在子模块 App-Core-lib 和 App-Data-lib 的 pom.xml 中,使用 parent 元素声明父模块,parent元素的子元素如下

子模块的 POM 中,当前模块的 groupId 和 version 元素可以省略,但这并不意味着当前模块没有 groupId 和 version,子模块会隐式的从父模块中继承这两个元素,即由父模块控制子模块的公司组织 id 以及版本,这样可以简化 POM 的配置;

下面给出Maven可以通过继承获得POM的元素

13.Maven聚合

在实际的开发过程中,我们所接触的项目一般都由多个模块组成,在构建项目时,如果每次都按模块一个一个地进行构建会十分得麻烦,Maven 的聚合功能很好的解决了这个问题;

使用 Maven 聚合功能对项目进行构建时,需要在该项目中额外创建一个的聚合模块,然后通过这个模块构建整个项目的所有模块。聚合模块仅仅是帮助聚合其他模块的工具,其本身并无任何实质内容,因此聚合模块中只有一个 POM 文件,不像其他的模块一样包含 src/main/java、src/test/java 等多个目录;

与父模块相似,聚合模块的打包方式(packaging)也是 pom,用户可以在其 POM 中通过 modules 下的 module 子元素来添加需要聚合的模块的目录路径;

聚合模块在构建时,Maven 会先解析聚合模块的 POM、分析需要构建的模块,并根据这些模块之间的关系计算出构建顺序,然后根据这个顺序依次构建各个模块;构建完成后输出的是一个项目构建的小结报告,该报告中包括各个模块构建成功与否、构建花费的时间、以及整个构建构成所花费的时间等信息;


在实际的项目中,一个模块往往既是聚合模块又是其他项目的父模块,那么 Maven 的继承和聚合的关系是如何的呢?

Maven 的继承和聚合的目的不同,继承的目的是为了消除 POM 中的重复配置,聚合的目的是为了方便快速的构建项目:

  • 对于继承中的父模块来说,它不知道那些模块继承了它,但子模块都知道自己的父模块是谁;

  • 对于聚合模块来说,它知道哪些模块被聚合了,但那些被聚合的模块根本不知道聚合模块的存在;

两者在结构和形式上还是有一定的共同点的,最直观的就是两者的打包方式都是 pom,两者除了 POM 外都没有实际的代码内容;

14.Maven Profile

一个项目通常都会有多个不同的运行环境,例如开发环境,测试环境、生产环境等。而不同环境的构建过程很可能是不同的,例如数据源配置、插件、以及依赖的版本等。每次将项目部署到不同的环境时,都需要修改相应的配置,这样重复的工作,不仅浪费劳动力,还容易出错。为了解决这一问题,Maven 引入了 Profile 的概念,通过它可以为不同的环境定制不同的构建过程

Profile 可以分为 3 个类型,它们的作用范围也各不相同;

14.1 声明Profile

Maven 通过 profiles 元素来声明一组 Profile 配置,该元素下可以包含多个 profile 子元素,每个 profile 元素表示一个 Profile 配置。每个 profile 元素中通常都要包含一个 id 子元素,该元素是调用当前 Profile 的标识;

定义 Profile 的一般形式如下:

1
2
3
4
5
6
7
8
9
10
<profiles>
<profile>
<id>profile id</id>
....
</profile>
<profile>
<id>profile id</id>
....
</profile>
</profiles>

除此之外,Profile 中还可以声明一些其他的 POM 元素,但不同位置的 Profile 所能声明的 POM 元素也是不同的:

  • 在 pom.xml 中声明的 Profile,由于其能够随着 pom.xml 一起存在,它被提交到代码仓库中,被 Maven 安装到本地仓库或远程仓库中,所以它能够修改或增加很多 POM 元素;
  • 在 setting.xml 中声明的 Profile 是无法保证能够随着 pom.xml 一起被分发的,因此 Maven 不允许用户在该类型的 Profile 修改或增加依赖或插件等配置信息,它只能声明以下范围较为宽泛的元素:
    • repositories:仓库配置;
    • pluginRepositories:插件仓库配置;
    • properties:键值对,该键值对可以在 pom.xml 中使用;

14.2 激活Profile

Profile 能够在项目构建时,修改 POM 中配置或者添加一些额外的配置元素,用户可以通过多种方式激活 Profile,以实现不同环境使用不同的配置,执行不同的构建过程;

Profile 可以通过以下 6 种方式激活:

  • 命令行激活
  • settings.xml 文件显示激活
  • 系统属性激活
  • 操作系统环境激活
  • 文件存在与否激活
  • 默认激活

详细流程参考Maven Profile精讲 (biancheng.net),这里不再赘述;

15.Maven镜像

如果一个仓库 A 可以提供另一个仓库 B 的所有内容,那么就可以认为仓库 A 是仓库 B 的一个镜像,即仓库 B 中的任何一个构件都能从它的镜像中获取;

国内开发人员由于网络原因,直接从中央仓库下载构件时,速度较慢或不稳定,我们通常会使用中央仓库的国内镜像站来解决该问题;

配置 Maven 镜像的方法也非常的简单,我们只需要在 Maven 安装目录中 setting.xml 文件的 mirrors 节点中,使用 mirror 标签添加镜像的相关信息即可;

镜像通常会和 Maven 私服配合使用,由于 Maven 私服可以代理所有外部的公共仓库(包括中央仓库),因此对于组织内部的用户来说,使用一个私服就相当于使用了所有需要的外部仓库,这样就可以将配置集中到私服中,简化 Maven 本身的配置。这种情况下,用户所有所需的构件都可以从私服中获取,此时私服就是所有仓库的镜像;

需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务时,Maven 也无法访问被镜像仓库,因而将无法下载构件;

16.Maven私服

Maven 私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的远程仓库(中央仓库、其他远程公共仓库);

建立了 Maven 私服后,当局域网内的用户需要某个构件时,会按照如下顺序进行请求和下载:

  1. 请求本地仓库,若本地仓库不存在所需构件,则跳转到第 2 步;
  2. 请求 Maven 私服,将所需构件下载到本地仓库,若私服中不存在所需构件,则跳转到第 3 步;
  3. 请求外部的远程仓库,将所需构件下载并缓存到 Maven 私服,若外部远程仓库不存在所需构件,则 Maven 直接报错;

此外,一些无法从外部仓库下载到的构件,也能从本地上传到私服供其他人使用;

使用Maven私服主要有如下五个优势:

  1. 节省外网带宽
  2. 下载速度更快
  3. 便于部署第三方构件
  4. 提高项目的稳定性,增强对项目的控制
  5. 降低中央仓库的负荷压力

Maven
https://gintoki-jpg.github.io/2022/11/12/后端_MAVEN/
作者
杨再俨
发布于
2022年11月12日
许可协议