分享

Maven入门教程(一)

 夜猫速读 2022-05-05 发布于湖北

大家好,今天我们这个专题的主角是 — Maven。Maven 作为我们开发当中比较常见的项目管理工具,用来帮助我们构建项目,管理依赖。Maven 目前是 Apache 基金会托管的顶级项目之一,诞生自 2003 年,现在已经 17 岁了。本文当中,我们将介绍 Maven 是什么,Maven 的优缺点有哪些,为什么我们要使用 Maven。

1. 什么是 Maven?

那究竟什么是 Maven 呢,在 Maven 的官网上可以看到如下的解释:

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.

翻译过来就是:Maven 是一个软件工程的项目管理工具,基于工程对象模型(POM),Maven 可以从中央信息篇中来构建项目,生成报告和文档

从上面的介绍中,我们可以看到 Maven 的主要功能是管理项目,构建项目

关于 Maven 的由来,据其创始人者 Jason Van Zyl 描述,是为了更加便利地建设 Jakarta Turbine 项目而创立的一个项目。在当时,比较流行的项目构建工具是 Ant,但是,在这个阶段中,各种 Java 项目之间是没有什么规范的,新创建项目的时候,就需要重新编写对应的构建脚本。Jason 就相对应设计出一套标准的项目结构,标准的构建方式,用来简化项目的构建。2003 年的时候,Maven 已经是 Apache 基金会的顶级项目了。

2. Maven 的优缺点

很多著名的项目,都是历史的产物。在当时,随着 Java 语言的流行,越来越多的项目开始使用 Java ,但是当时的构建工具并不能简单快速地完成项目构建的流程,在这种背景下,一个简单,方便,标准化的构建工具-- Maven 就产生了。

2.1 Maven的优点

从 Maven 的官网中我们就可以看到以下几个特点:

  • Making the build process easy:意思是简化构建过程,顾名思义,让构建的过程来得更简单;

  • Providing a uniform build system:意思是提供统一的构建系统,Maven 提供了一个统一的构建系统。例如:Maven 使用 POM 模型以及一系列的相关插件供开发者来使用;

  • Providing quality project information:意思是提供优质的项目信息,在使用 Maven 的过程中,你可以通过 Maven 来获得很多关于项目的信息,例如:已经覆盖的单元测试报告,项目的依赖列表等等;

  • Providing guidelines for best practices development:意思是提供最佳实践开发指南,Maven 致力于整合开发过程中的最佳实践,并引导人们朝着这个方法前进。例如:在项目过程中 Release 版本和 snapshot 版本的管理,以及 Maven 项目标准化的项目目录结构。

总之呢,Maven 的核心是约定大于配置,它的初衷就是帮助程序开发者在最短时间内完成项目开发过程中的每个过程,目标就是更简单,更统一,更快速。

2.2 Maven 的缺点

  • Maven 的整个体系相对庞大,想要完全掌握相对困难;

  • 如果项目的依赖过多,在第一次导入项目的时候需要花很长的时间来加载所需要的依赖;

  • 由于某种不可抗拒力,国内的开发者在使用 Maven 中央仓库的时候,下载速度过慢。

但是,这些问题都是有可以有解决办法的,我们后续会慢慢一一介绍。

2.3 相同类型工具对比

在 Java 开发的世界中,有三大主流的构建工具,分别是 Ant ,Maven ,Gradle。

其中 Ant 出现的世界最早,能够提供编译、测试、打包的功能,但是 Ant 缺乏对依赖的管理,以及标准的项目结构。

后来 Maven 的出现,解决了 Ant 所不能满足的两个问题,从创建项目到构建及发布项目的整个过程定义了一套完整的规范,并且提供中央仓库,对依赖进行管理。

后来,随着 Android 的流行,近年来,以 Gradle 作为项目的构建工具也越来越流行。Gradle 在 Maven 的基础上,使用基于 Groovy 的特定领域语言(DSL)来完成配置声明,相较于 XML 来说,更加灵活。目前,Maven 和 Gradle 基本上算是平分秋色的局面,在实际的开发中,后台项目管理更倾向于使用 Maven,而在移动端开发中,Gradle 的占比更大。当然两者之间也有很多相通的地方,比如依赖管理,一致的项目结构。

3. Maven 的版本

Maven 从发布到现在已经经历过很多个版本迭代,目前最新的版本是 2019-11-25 发布的 Maven 3.6.3 版本。

  • 1.0-2.x : 官方不再进行维护,也不建议开发者使用;

  • 3.0: Maven3.x 的第一个版本,也算是 Maven 的里程碑版本,完全向后兼容 Maven2,增加了 SLF4J 来进行日志管理,并且提高了项目构建效率和插件的扩展性;

  • 3.5.0: 该版本显著的变化是 Maven 的控制台支持不同级别日志输出不同颜色;

  • 3.6.3: 目前的最新版本,schemaLocations 地址支持 https。在后续的章节中,我们也会用这个版本来进行讲解。

4. 为什么使用 Maven?

说到这个问题,我们首先要看一下,如果没有 Maven,那么我们的工作是什么样子的呢?

场景一

当我们在开发过程中,当我们开发某个新功能或者修复了某个 Bug,都需要手动进行整个项目编译,运行单元测试,生成项目文档,打包,部署环境这些步骤。一旦需要重新修改代码的时候,便要将上述的操作重复一遍。机械性的重复劳动充斥着整个开发过程;

场景二

由于不同的人可能会有不同的习惯或者说是个人偏好,每当我们新建一个项目的时候,所建出来的项目可能会千奇百怪,这也给后续的维护升级带来了诸多的不便;

场景三

当项目需要依赖某个 jar 包的时候,需要到互联网上去寻找对应的 jar 包,找到 jar 包之后,将这个 jar 包添加到项目的 lib 目录下,项目组里面不同的人可能会找到不同的 jar 包,不同的 jar 包直接可能会存在冲突,这个时候,就需要去手动解决冲突;

看到这里,只想说一句,我太难了。但是,不要慌,Maven 的存在,就是为了帮助解决这些问题。

使用 Maven 之后,只需要执行一个命令就可以完成编译,运行单元测试,打包,部署的整个流程;并且 Maven 以 POM 的形式来管理 jar 包依赖;还有一点就是,使用 Maven 构建出的项目,结构统一,方便后续的维护升级。

5. 学习基础

本教程当中使用了一些 Java 项目作为例子,所以需要了解简单的 Java 基础。

6. 本课程如何设计?

在本课程中,我们首先会对 Maven 的核心概念进行讲解,并且,通过一些简单的项目,来加深对这些概念的理解,在课程的过程中,如果遇到平时工作时常使用的点,则会穿插一些实际工作中的最佳实践,方便在工作中能够学以致用。

Maven 安装与配置

在上一节中,我们对 Maven 有了一个大概的了解,了解到 Maven 有很多优点,那么我们今天就来介绍,如何在 Windows 环境中安装和配置 Maven。在教程中,我们使用的 Maven 版本是 3.6.3 版本,jdk 版本是 1.8。

1. Windows 下安装 Maven

1.1 下载 Maven 安装包

首先在 Maven 官网上,找到下载地址,并下载该版本apache-maven-3.6.3-bin.zip,如下图所示:

1.2 检查 jdk 版本

由于 Maven 是使用 Java 开发的工具,因此需要先安装 jdk。Maven3.6.3 版本需要 jdk1.7+ 版本来支持,本教程使用的是 jdk1.8 版本。

Tips:jdk 的安装过程在这里我们就不多赘述了,同学们可以参考下慕课网相关 Wiki 进行安装。

打开 cmd 运行窗口输入:java -version,如下图所示,能够正常查看 Java 版本信息即说明 jdk 安装成功:

1.3 解压安装包

将 Maven 的安装包复制到指定目录中,并解压该安装包。解压后在 Maven 的 bin 目录下进入 cmd,输入 mvn -v,可查看 Maven 版本信息。

1.4 配置 MAVEN_HOME

但是,现在我们只能在 Maven 的 bin 目录下执行 mvn 命令,无法随时随地使用 Maven,因此,我们需要将 Maven 的路径配置到环境变量当中。

新增了 MAVEN_HOME 之后,需要将 %MAVEN_HOME%\bin 追加到 path 当中,需要注意的时候,追加的时候需要在前面加一个; 和 path 中之前我们添加的环境变量做一个分割:

在追加完 path 之后,我们在任意目录下执行 mvn -v 命令,都可以正常查看 Maven 版本信息,即为配置成功。

2.Linux 系统中安装 Maven

2.1 下载安装包

在 Linux 系统中,需要使用 tar 包来进行安装。我们可以去官网下载对应安装包(apache-maven-3.6.3-bin.tar.gz),也可以使用 Linux 系统中的 wget 工具来进行下载。这里我们使用 wget 工具来进行下载。

我们可以从阿里云的镜像站中下载,速度更快些。

进入到需要下载的目录,我们这里的目录地址是/usr/local/src,进入该目录后,执行 wget 命令,wget https://mirrors.aliyun.com/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz

2.2 解压安装包

执行 tar -zxvf apache-maven-3.6.3-bin.tar.gz

2.3 添加环境变量

打开配置文件 vi /etc/profile在文件的最后添加如下配置:

export MAVEN_HOME=/usr/local/src/apache-maven-3.6.3
export MAVEN_HOME
export PATH=$PATH:$MAVEN_HOME/bin

保存文件后,执行 source /etc/profile 命令。

2.4 查看Maven版本

此时,切换到任意目录下,执行命令 mvn -version。可以看到当前 Maven 的版本信息。

3.Mac 环境中安装 Maven

在 Mac 环境中安装 Maven 和在 Linux 环境下安装 Maven 的步骤和过程大体是相同的,只不过在 Mac 环境中的环境变量文件位置是 `~/.bash_profile``,所以在这个文件中添加环境变量即可。

4. Maven配置

我们在将 Maven 安装好之后,为了方便我们后面的使用,可以对 Maven 进行简单的配置。

进入 Maven 路径下的 conf 目录,打开 setting.xml 文件。

4.1 修改本地仓库位置

在 Maven 的使用过程中,会自动将项目依赖的 jar 包从中央仓库下载到本地仓库,默认本地仓库路径是${user.home}/.m2/repository,这样的话,会占用较多的 C 盘空间,因此,我们可以自定义该路径。

4.2 修改镜像位置

由于 Maven 中央仓库的服务器是架设在国外的,所以由于某种不可抗拒力量,国内用户如果直接使用中央仓库的话,下载速度会受很大的影响。如下图所示,个人用户可以使用阿里云镜像。这里阿里云仓库是作为中央仓库的一个镜像的,镜像库会完全屏蔽被镜像库。

镜像地址:

<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
</mirror>
代码块123456

经过上面的简单配置之后,我们就可以开心地使用 Maven 了。

5. 使用 Maven 创建项目

配置好 Maven 之后,接下来我们就可以使用 Maven 来创建我们的第一个项目了。

在 cmd 中切换到我们存放代码的目录,并执行如下命令:

mvn archetype:generate -DgroupId=com.mic.tech -DartifactId=firstProject -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

参数说明:

  • -DgourpId: 组织名,一般为公司网址的反写;

  • -DartifactId: 项目名-模块名;

  • -DarchetypeArtifactId: 用来指定 ArchetypeId,这里用到的是maven-archetype-quickstart,即创建一个简单 Java 应用;

  • -DinteractiveMode: 是否使用交互模式。

项目创建好之后,会有如下提示:

Tips:由于是第一个Maven项目,在创建的时候,需要下载 Maven 插件,所以耗时会相对长一点。

接下来,我们将该项目导入到 Idea 中,来查看该项目。

点击运行后,可以正常输出 Hello World!

6. 视频演示安装细节

6.1 安装 Maven2

Your browser does not support the video tag.

6.2 安装 Maven4

6.1 安装 Maven2

Your browser does not support the video tag.

6.2 安装 Maven4

7. 小结

本节,我们主要讲了如何在 Windows 环境下安装 Maven 以及修改简单配置,并且使用 Maven 创建运行了一个 Java 应用。

5. 小结

本节中,我们介绍了 Maven 中的一个重要的概念–依赖,介绍了什么是依赖,以及依赖的几个特性,最后我们也总结了在平时的工作中常常会用到的依赖优化的方式,能够帮助我们更好的管理项目的依赖。

Maven POM 模型

在上一节当中,我们使用 Maven 创建了我们的第一个项目,今天我们来介绍一下 Maven 中重要的概念 POM 模型

1. POM 模型

1.1 什么是 POM?

POM(项目对象模型)是 Maven 最基本,也是非常重要的一个概念。通常情况下,我们可以看到 POM 的表现形式是 pom.xml,在这个 XML 文件中定义着关于我们工程的方方面面,当我们想要通过 Maven 命令来进行操作的时候,例如:编译,打包等等,Maven 都会从 pom.xml 文件中来读取工程相关的信息。

1.2 查看项目结构

我们在 cmd 中打开项目根目录,执行 tree /f 命令。可以看到如下图的项目结构:

  • 每个项目都有一个 pom.xml 文件,该文件中定义本项目的对象模型,描述本项目,配置插件,声明依赖;

  • 对于简单的 Maven 项目,src/main 目录放置项目的源码和资源文件,一般情况下,源码放置在 Java 目录下,App.java 就是 Maven Archtype 插件生成的一个简单类,classpath 资源文件放置在resources 目录下。

  • src/test 目录下放置我们的测试用例,与 main 目录类似,src/test/java 目录下放置我们的测试类源码,AppTest.java 则是 Maven Archtype 插件生成的一个简单测试类,src/test/resources 放置测试用到的 classpath 资源文件。

注: 这里 Maven 只是帮我们创建了一个简单的 Maven 项目,其中 resources 目录则需要手动创建。

1.2 查看项目 pom.xml 文件

我们打开项目中的 pom.xml 文件,如下图:

现在看到的这个 pom.xml 是 Maven 项目中最基础的 POM,后面随着项目的慢慢的进行,这个 pom.xml会变得更加复杂,我们可以向其中添加更多的依赖,也可以在里面配置我们需要的插件。

从头开始看:groupId,artifactId,packaging,version 几个元素是 Maven 的坐标,用来唯一标识一个项目。

接下来是 name,url 这两个元素则是用来描述信息,给人更好的可读性。

最后是 dependencies,这里 Maven 默认依赖了 3.8.1 版本的 junit,其中 scope 用来标记该依赖的范围为 test。

1.3 Maven 的坐标

接下来我们就重点介绍一下 Maven 的坐标(Coordinates)。

  • groupId:groupId 为我们组织的逆向域名,这里的组织可以是公司,团体,小组等等。例如Apache 基金会的项目都是以 org.apache 来作为 groupId 的;

  • artifactId:该组织下,项目的唯一标识;

  • packaging:项目类型,描述的是项目在打包之后的输出结果,常见的 jar 类型的输出结果是一个jar 包,war 类型则输入 war 包,一般 Web 项目的打包方式为 war。

  • version:项目的版本号,用来标记本项目的某一特定版本。SNAPSHOT 则是用来标记项目过程中的快照版本,该版本类型表明本项目不是稳定版本,常见的还有 RELEASE,则表示该版本为本项目的稳定版本。

2. 超级 POM

在我们这个项目的 pom.xml 文件中,只有短短的几行信息,但是这就全部吗?其实不然。

在 Maven 的世界中,存在着一个超级 POM(super POM),所有通过 Maven 创建的项目,其 pom.xml 文件都会继承这个超级 POM。所以在默认情况下,使用 Maven 创建出来的项目基本上都是很类似的。

那么这个超级 POM 在哪呢?长什么样呢?

2.1 超级 POM 路径位置

如下图,先找到指定的 jar 包,路径:.\apache-maven-3.6.3\lib\maven-model-builder-3.6.3.jar

然后我们可以使用解压工具查看该 jar 包,找到对应的 pom.xml。

具体路径:org\apache\maven\model\pom-4.0.0.xml

2.2 查看超级 POM 结构

<project>  <modelVersion>4.0.0</modelVersion>
<repositories> <repository> <id>central</id> <name>Central Repository</name> <url>https://repo.maven./maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
<pluginRepositories> <pluginRepository> <id>central</id> <name>Central Repository</name> <url>https://repo.maven./maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories>
<build> <directory>${project.basedir}/target</directory> <outputDirectory>${project.build.directory}/classes</outputDirectory> <finalName>${project.artifactId}-${project.version}</finalName> <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory> <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory> <scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory> <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory> <resources> <resource> <directory>${project.basedir}/src/main/resources</directory> </resource> </resources> <testResources> <testResource> <directory>${project.basedir}/src/test/resources</directory> </testResource> </testResources> <pluginManagement> <!-- NOTE: These plugins will be removed from future versions of the super POM --> <!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) --> <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.3</version> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-5</version> </plugin> <plugin> <artifactId>maven-dependency-plugin</artifactId> <version>2.8</version> </plugin> <plugin> <artifactId>maven-release-plugin</artifactId> <version>2.5.3</version> </plugin> </plugins> </pluginManagement> </build>
<reporting> <outputDirectory>${project.build.directory}/site</outputDirectory> </reporting>
<profiles> <!-- NOTE: The release profile will be removed from future versions of the super POM --> <profile> <id>release-profile</id>
<activation> <property> <name>performRelease</name> <value>true</value> </property> </activation>
<build> <plugins> <plugin> <inherited>true</inherited> <artifactId>maven-source-plugin</artifactId> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> <plugin> <inherited>true</inherited> <artifactId>maven-javadoc-plugin</artifactId> <executions> <execution> <id>attach-javadocs</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <inherited>true</inherited> <artifactId>maven-deploy-plugin</artifactId> <configuration> <updateReleaseInfo>true</updateReleaseInfo> </configuration> </plugin> </plugins> </build> </profile> </profiles></project>

对于我们的项目,我们称超级 POM 为父 POM,我们项目中的 POM 为子 POM,一般情况下如果父子 POM 中存在相同的元素或者节点,那么子 POM 会覆盖父 POM 的元素或者节点(有点类似 Java 中的 override),但是,也会有这么几个例外存在:

  • dependencies;

  • developers 和 contributors;

  • plugins;

  • resources。

子 POM 在继承这些元素的时候,并不会直接覆盖,而是在其基础上继续追加。

3. 小结

本节中,我们介绍了 Maven 的 POM 模型,查看 Maven 工程的 pom.xml 文件,以及 pom.xml 文件中的坐标,最后我们还介绍了超级 POM,这样,我们就对 Maven 的 POM 模型有了一个基本的认识。

Maven 的依赖

在上一节中,我们重点介绍了 Maven 的项目对象模型(POM),本节我们重点介绍另一个重要概念–依赖。我们会介绍什么是依赖,以及在我们平时的工作中的最佳实践。

1. 何为依赖?

依赖即为本项目对其他项目的引用,这里的其他项目可以是外部项目,也可以是内部项目。我们在开发项目的过程中,将其他项目作为依赖引用进来,最终在打包的过程中,依赖会和我们开发的项目打包到一起来运行。

在我们的项目没有使用 Maven 的时候,我们需要手动去管理我们的依赖,例如:添加依赖,删除依赖。在使用了 Maven 之后,我们可以在 pom.xml 文件里面看到我们所有的依赖,并且可以灵活的管理项目的依赖。


在上图中,我们可以清晰的看到我们项目目前所引用的依赖。

1.1 依赖的范围

Maven 在编译和运行以及执行测试用例的时候,分别会使用不同的 classpath。而 Maven 的依赖范围则是用来控制依赖与不同 classpath 关系的。

Maven 的依赖范围分为以下几种:

  • compile: 编译依赖范围。Maven 默认的依赖范围,该范围的依赖对编译,运行,测试不同的classpath 都有效。例如我们项目中的 spring-boot-starter;

  • test: 测试依赖范围。该依赖范围只对测试 classpath 有效,在编译项目或者运行项目的时候,是无法使用此类依赖的。例如我们项目中的 spring-boot-starter-test;

  • provided: 已提供依赖范围。该 Maven 依赖对于编译和测试的 classpath 有效,但是在运行时无效;

  • runtime: 运行时依赖范围。顾名思义,该依赖范围对测试和运行的 classpath 有效,但是在编译时无效;

  • system: 系统依赖范围。该依赖范围与 classpath 的关系与 provided 依赖范围是相同的。但是,在使用时需要谨慎注意,因为此类依赖大多数是与本机绑定的,而不是通过Maven仓库解析出来的,切换环境后,可能会导致依赖失效或者依赖错误。

2. 传递性依赖

目前我们的项目只引用了两个依赖,spring-boot-starter 和 spring-boot-starter-test,但是是这样子的吗?

为了能够更清晰的看到我们项目依赖的结构,我们可以在 IDEA 里面安装 Maven Helper 插件。


安装好之后,我们打开项目的 pom.xml 文件,可以看到在文件的下方多了一个 Dependency Analyzer按钮。打开之后,如下图。

从这里我们就可以看到,其实我们不只是引入了两个包,而是引入了很多个包,这是为什么呢?

答案是因为 Maven 的传递性依赖机制

在我们这个项目中,我们引入了 spring-boot-starter 依赖,并且该依赖的范围是 compile,但是 spring-boot-starter 作为一个项目也有自己的依赖,在这其中的依赖范围为 compile 的依赖,则会自动转换成我们项目的依赖,例如 spring-boot 依赖,logback-core 依赖。

所以,有了 Maven 的传递性依赖机制之后,我们在使用一个依赖的时候,就不再需要考虑它又依赖了哪些,而是直接使用即可,其他的事情 Maven 会自动帮我们做完。

3. 最短路径原则

有了传递性依赖能够大大节省我们在管理依赖时候所耗费的精力。但是,如果传递性依赖出了问题我们应该如何解决呢?首先,我们应该知道的是传递性依赖是从哪条依赖路径引用进来的。

在我们的项目中就存在这样的例子。我们可以看到如下两条不同的引用路径:

1. spring-boot-starter --> spring-boot --> spring-aop --> spring-core

2. spring-boot-starter --> spring-core

这个时候,我们可以看到,两条路径最终引用的 spring-core 版本都是 5.2.5-RELEASE。但是如果引用的 spring-core 版本不同,Maven 会怎么做呢?

使用最短路径原则,路径2中的 spring-core 版本会本引用,这样就不会造成重复依赖的问题产生。

4. 最佳实践

4.1 排除依赖

传递性依赖可以帮助我们简化项目依赖的管理,但是同时也会带来其他的不必要的风险,例如:会隐式地引入一些依赖,这些依赖可能并不是我们希望引入的,或者这些隐式引入的依赖是 SNAPSHOT 版本的依赖。依赖的不稳定导致了我们项目的不稳定。

在我们的项目中,spring-boot-starter-test 依赖中排除了 junit-vintage-engine 依赖是由于我们使用的 springboot 版本是 2.2.6-RELEASE,对应的 Junit 版本是 5.x,但 junit-vintage-engine 依赖中包含了 4.x 版本的 Junit,此时我们就可以将该依赖排除。

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-test</artifactId>    <scope>test</scope>    <exclusions>        <exclusion>            <groupId>org.junit.vintage</groupId>            <artifactId>junit-vintage-engine</artifactId>        </exclusion>    </exclusions></dependency>

在 exclusions 标签中,可以有多个 exclusion 标签,用来排除不需要的依赖。

4.2 依赖归类

在我们实际的开发过程中,我们可能会需要整合很多第三方框架,在整合这些框架的时候,往往需要在 pom.xml 里面添加多个依赖来完成整合。而这些依赖往往是需要保持相同版本的,在升级框架的时候,都是要统一升级到一个相同的版本。

如下图,我们可以看到,在引入 dubbo 框架的时候,我们需要引入两个相关的依赖,而且版本号是相同的,这个时候,我们就可以把对应的版本号提取出来,放到 properties 标签里面,作为一个全局参数来使用。类似于 Java 语言中抽象的思想。

这时候,我们可以看到,如果在将来的某一天我们需要升级升级 dubbo 框架对应的版本,只需要修改 properties 中的版本号,就能将所有依赖的版本一起升级。

4.3 依赖优化

我们再回过头来看一下 Maven Helper 工具所展示的场景

我们在这个工具中可以看到我们项目现在引入的所有的依赖,可以看到哪些是我们用到的,哪些是没有用来的,依赖与依赖之间是否存在冲突。如果出现了上述情况,我们就可以通过删除或者依赖排除的方式来将我们不需要的依赖删除掉,从而使我们的项目更简洁。

5. 小结

本节中,我们介绍了 Maven 中的一个重要的概念–依赖,介绍了什么是依赖,以及依赖的几个特性,最后我们也总结了在平时的工作中常常会用到的依赖优化的方式,能够帮助我们更好的管理项目的依赖。

Maven 仓库

在之前的章节中,我们分别介绍了 Maven 中的工程对象模型(POM)以及 Maven 的依赖管理,但是,这个时候,我们势必会有一个疑问,当我找到一个依赖的坐标后,只需要将该坐标放入到我项目的 POM 文件当中,这个依赖就算是被引入了,那这个依赖是从哪里来的呢?

在本节中,我们就带着这个疑问来学习 Maven 的仓库,了解如何使用 Maven 仓库。

1. 什么是 Maven 仓库

我们先想象一下,如果没有 Maven,我们在开发不同项目的时候,如果需要依赖同一个 jar 包,那么就需要分别在两个不同项目中将这个 jar 包引入进去,对于一个程序员来说,这样的做法显然是不合理的,不仅需要我们手动到处复制,而且会多占用我们的磁盘空间。

那这个时候,Maven 仓库就出现了。我们通常把依赖称为构件,每一个构件都有自己唯一的坐标,基于这种模式,我们就可以把这些构件存放在一个指定的位置–Maven仓库当中,然后通过其坐标来寻找该构件。

在我们学习或者实际开发过程中,只需要在我们的项目当中声明依赖的坐标,在项目编译的或者打包的过程中,Maven 会自动从仓库中去寻找该构件,这样就不需要我们在本地存储这个依赖了。

2. 仓库的分类

对于 Maven 来说,主要的仓库种类可以分为两种,一种是本地仓库,另一种是远程仓库。而在远程仓库当中呢,又可以分为中央仓库,私服和其他的公共仓库。

2.1 本地仓库

在我们声明的 MAVEN_HOME 路径下,找到 conf\settings.xml,其中可以看到 Maven 的本地仓库路径配置:

从上图我们可以看到,Maven 的默认本地仓库路径是在 ${user.home}/.m2/repository,我们为了方便将其修改为了 D:\repo

2.2 中央仓库

Maven 中默认配置了中央仓库,我们可以在超级 POM 里面找到对应的配置。

这个仓库是由 Maven 社区来维护的,里面存放了绝大多数开源软件的包,并且是作为 Maven 的默认配置,不需要开发者额外配置。另外为了方便查询,还提供了一个查询地址,开发者可以通过这个地址更快的搜索需要构件的坐标。

2.3 其他远程仓库

有了中央仓库,我们为什么还需要其他的远程仓库呢?

  • 我们要找的构件可能不存在于中央仓库中;

  • 由于某些原因,访问中央仓库的速度相对较慢。

这种时候,我们就可以选择一个使用起来相对方便的远程仓库来配置,大大提高了我们的开发效率。

国内常用的 Maven 仓库:

阿里云镜像:

<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
</mirror>

阿里巴巴镜像:

<mirror>
    <id>ibiblio</id>
    <mirrorOf>central</mirrorOf>
    <name>Human Readable Name for this Mirror.</name>
    <url>http://mirrors.ibiblio.org/pub/mirrors/maven2/</url>
</mirror>

repo2 镜像:

<mirror>  
    <id>repo2</id>  
    <mirrorOf>central</mirrorOf>  
    <name>Human Readable Name for this Mirror.</name>  
    <url>http://repo2.maven.org/maven2/</url>  
</mirror>

我们可以将对应的仓库的镜像配置到 settings.xml 文件中的 mirrors 节点中即可。如下图所示,我们配置了阿里云的镜像。

2.4 私服

私服也是属于远程仓库的一种,相对公共仓库而言属于某个公司或者某个开发团队私有的远程仓库。通常部署在某个局域网内,提供局域网的内部用户使用。

那私服有什么好处呢?

  • 更快的下载速度:由于是局域网内部的请求,因此下载构件的速度是可以保证的;

  • 更稳定的构建:想象一下,如果我们依赖某个外部的远程仓库,当这个仓库出现不可能用的情况,哪怕是网络的波动,都有可能会造成我们的构建失败;

  • 部署第三方构件:如果一个公司使用了微服务架构,那么公共仓库是肯定没办法获取这些私有的构件的。

当我们需要一个构件的时候,Maven 会先去请求私服,如果发现私服中,没有该构件,那么就会去配置了的远程仓库中寻找,并且缓存到我们的私服中,为后续的下载请求提供服务。

3. Maven 的依赖解析顺序

我们知道了 Maven 通过坐标去仓库中寻找对应的构件,那么这个机制的原理是怎么样的呢?

Maven 在寻找需要的依赖的时候,会遵照下面的顺序:

  1. 如果构件的依赖范围是 system,Maven 会直接从本地的文件系统来解析该构件;

  2. 根据配置的依赖坐标,在本地仓库中寻找该构件,如果能够搜索到,则解析成功;

  3. 如果本地仓库没有搜索到,那么就会去已经配置了的远程仓库中搜索该构件,搜索到后,下载到本地仓库中,提供项目使用;

  4. 如果依赖的版本是 RELEASE 或 LATEST,那么就会根据更新策略去读取所有远程仓库中的元数据信息(groupId/artifactId/maven-metadata.xml),并且与本地仓库中对应的元数据合并后,计算出真实值,再将其下载到本地仓库中;

  5. 如果依赖的版本是 SNAPSHOT,那么就会根据更新策略去读取所有远程仓库中的元数据信息(groupId/artifactId/version/maven-metadata.xml),并且与本地仓库中对应的元数据信息合并后,得到最新的快照版本值,根据这个值去寻找对应的依赖;

  6. 解析出的快照版本一般是带有时间戳的,下载下来后,会将该时间戳删掉,以无时间戳的形式来使用。

4. 小结

在本节中,我们介绍了什么是 Maven 仓库,主要的仓库分类以及不同仓库的特点。最后我们还介绍了从 Maven 仓库中的依赖解析机制。

Maven 生命周期

我们今天带来的是 Maven 的另一个重要概念–生命周期。在学习了 Maven 的生命周期之后,在使用 Maven 的过程中,就能够够好的理解每一步操作的意义。

1. 什么是生命周期

其实生命周期这个概念并不是 Maven 首创的,因为即使不用 Maven,这些事情也是需要我们去做的。想象一下在没有 Maven 的时候,我们开发完一个项目之后,一般是直接使用 Java 的相关命令进行编译,打包等等工作。

但是这些工作无聊而且繁琐,基本上充斥在开发者每天日常的工作中,无论是开发新功能,还是修改一个 Bug,都需要重复以上操作。当然有聪明的开发者,也会将这些每天重复的事情做成脚本来执行。

那么问题又来了,不同公司的不同项目之间或多或少会存在些许差异,这种时候,可能就需要开发者针对这些差异来定制一些步骤,或者脚本。也就是说,每当我们开始开发一个新项目的时候,或者换到另一个项目组的时候,我们构建项目的步骤或者方式都可能会发生变化。

Maven 的出现,可以说是很大程度上缓解了这种问题的发生。通过吸取很多项目的经验,Maven 定义了一套完整而且统一的生命周期模型。使用这个模型,我们将构建的细节交给 Maven,只需要理解对应生命周期的含义即可完成构建。就好像,人到了青少年的时候,就要去上学,到了青年的时候,就要出来工作类似,我们不需要知道上学或者工作中具体的事情,只需要知道,到了这个阶段,能够做这个事情就可以了。

2. 生命周期详解

Maven 的生命周期并非只有一套,而是有三套,并且这三套生命周期之间是没有关系的。一套生命周期包含很多个不同的阶段,这些不同的阶段是有顺序的,有些阶段必须要在某个阶段完成之后,才能进行。Maven 的三套生命周期分别为:clean(清理)default(默认)site(站点)。接下来我们就一一介绍一下这三个生命周期。

2.1 clean 生命周期

clean 生命周期包括:

  1. pre-clean:清理前的准备工作;

  2. clean:清理上一次构建的结果;

  3. post-clean:清理结束后需要完成的工作。

一般情况下,Maven 的构建结果会生成在 target 目录下,我们执行 mvn clean 命令后,这个目录会被清空。

从上图,我们可以看到,对应的 target 目录被清理干净了。

2.2 default生命周期

default 生命周期应该算是大多数开发者最为熟悉的生命周期,也是平时在开发过程中最常用的生命周期。

(clean,site 并不属于 default 生命周期)在 default 生命周期中,最常用的几个阶段包括:

  1. validate:验证阶段。验证项目构建过程中需要的信息的正确性;

  2. compil:编译阶段;

  3. test:测试阶段。使用测试框架对项目进行测试,打包过程中,非必要阶段,可以跳过执行。

  4. package:打包阶段。将编译好的文件打包成 jar 包,war 包或者 ear 包;

  5. verify:检查阶段。检查打包结果的有效性;

  6. install:本地部署阶段。将包部署到本地仓库,可以提供给本地开发过程中其他项目使用;

  7. deploy:远程仓库部署阶段。将最终的包复制到远程仓库,提供给使用该仓库的其他开发者使用。

这里我们介绍的只是在 default 生命周期中最常用的,其实在这些阶段执行的过程中,还会有其他的阶段需要执行,但是并非很常用。另外,不出意外的情况下,在生命周期中,后执行的阶段要等先执行的阶段执行完再执行。

我们试着执行 Maven 的打包命令:mvn package -DskipTests。执行完成之后,可以看到其所经过的生命周期。因此,当我们想要构建项目的时候,并不需要分别执行 package 阶段之前的阶段,而是 Maven 自动为我们执行。突然发现,原来构建项目是如此的简单,方便。

2.3 site 生命周期

很多时候,我们不仅仅需要构建我们的项目,还需要生成项目文档或者站点。site 生命周期则是来帮助我们做这件事情的,它能够根据我们项目中 pom.xml 的信息,来生成一个友好的站点。

跟其他的生命周期一样,site 生命周期也包含不止一个阶段:

  1. pre-site:准备阶段。在生成站点前所需要做的工作;

  2. site:生成站点阶段;

  3. post-site:结束阶段。生成站点结束后所需要做的工作;

  4. site-deploy:发布阶段。我们可以将上面生成的站点发布到对应服务器中。

3.插件

3.1 插件目标

其实在 Maven 的世界中,生命周期只是一个抽象的模型,其本身并不会直接去做事情,真正帮我们完成事情的是 Maven 的插件。Maven 的插件也属于构件的一种,也是可以放到 Maven 仓库当中的。

通常情况下,一个插件可以做 A、B、C 等等不止一件事情,但是我们又没有必要为每一个功能都做一个单独的插件。这种时候,我们一般会给这个插件绑定不同的目标,而这些目标则是对应其不同的功能。

当我们使用一个插件的目标的时候,我们可以执行命令:mvn pluginName:goalName。例如当我们执行dependency插件的 list 目标的时候,我们可以执行命令:mvn dependency:list

使用该插件目标,我们可以看到目前我们项目中所有依赖的情况。

3.2 插件绑定

我们说 Maven 的生命周期只是抽象的概念,真正帮我们完成事情的是插件,其实更确切的说,应该是生命周期与插件对应的目标绑定,来完成具体的功能。

4. 小结

在本节中,我们详细介绍了 Maven 的生命周期,常用的生命周期,以及其与插件的对应关系,简单的工作原理。学完之后,能够加深 Maven 的理解,减少使用过程中的误解。

Maven 版本管理

本节中,我们来介绍一下 Maven 是如何进行版本管理的。如何在项目的实际开发中,结合 Maven 来推进项目的进行。一个正常的项目的开发周期通常是很长的,这个过程当中,需要发布很多个版本,那这些版本如何表示,而我们又应该如何来管理这些版本呢?

1. 什么是版本管理

那什么是版本管理呢?首先,版本管理是不同于版本控制的。版本控制通常的概念是在软件开发过程中,管理程序文件,配置文件等文件的变化。更倾向于来追踪一个项目过程中,不同时期项目的变化。但是,版本管理则不同,通常是指一个项目过程中,不同时期版本的演化过程。通俗一点讲,版本管理就像人的成长过程中,从婴儿到少年到青年到中年一直到老年这个演变过程的管理;版本控制则更关注细节,例如这个时期,身高从 160cm 长到了 165cm,或者体重 60kg 变为了 62kg 等等。

2. 约定版本号

我们理解了什么是版本管理,那 Maven 是如何做的呢?

通常情况下,Maven 的版本号约定中包括如下几个部分:

<主版本号>.<次版本号>.<增量版本号>.<里程碑版本号>

  • 主版本号:主版本号表示该项目的重大升级。例如:Maven1 到 Maven2;

  • 次版本号:表示在该主版本下,较大范围的升级或变化。例如:Maven-3.0 到 Maven-3.1;

  • 增量版本号:增量版本通常是用来修复bug的版本。例如:Maven-3.1.1;

  • 里程碑版本号:用来标记里程碑版本。例如:Maven-3.0-alpha-3。

由于 Maven 已经维护了将近 20 年,所以,使用 Maven 这个项目的版本演变过程来举例是再合适不过了。我们打开 Maven 的官网,找到 Release Notes,打开便可以看到 Maven 从最开始的版本是如何演变成现在的模样的。(这里由于存在太多版本,所以,我们只截取了其中一部分)

(Maven 版本演变历史列表)

注意: 有的同学可能会问,里程碑版本里面的 alpha,beat 是什么意思?

  • alpha 版本: alpha 版本被称为是内测版本。通常会存在比较多 bug,主要是面向测试人员使用;

  • beat 版本: 这个版本也是测试版本。会比 alpha 版本新增加一些功能;

  • RC 版本: 即将发布的候选版本。这个阶段不会再新增功能,主要用于修复 Bug;

  • GA 版本: 正式发布版本,也可以对应于 release 版本。

这些版本号在一些流行的框架的演变过程中非常常见。

3. 主干、分支与标签

通常情况下,我们在进行项目开发的过程中,会使用到版本控制工具,例如 svn 或者 git,这时候就会涉及到主干,分支以及标签的概念,那么这里我们简单介绍一下这三个概念。

3.1 概念

  • 主干: 通常是项目代码的主体。会存在于整个项目周期中,其他的所有分支都是从这里开始的,一般情况下,项目最新的源代码或者所有的变更记录都可以在这里找到;

  • 分支: 从主干中某个节点分离出来的代码。在分支刚刚创建的时候,具有当时主干中所有的源代码。通常分支是为了修改某些 Bug 或者进行一些其他的开发,这些源代码的修改并不会影响主干。一旦这个分支中的代码验证通过后,可以将代码归并到主干中去;

  • 标签: 通常用来标记主干或者分支中某个时间节点的状态,

从下面的图中,我们可以比较清晰地看出,主干,分支与标签三者之间的关系:

主干,分支与标签三者关系图

3.2 创建分支

我们在了解了主干以及分支的概念之后,那么我们如何去创建分支呢?通常情况下我们可以使用 Svn 或者 Git 自带的创建分支的方式来创建需要的分支,但是,这个时候会存在一个问题:我们需要手动去修改新创建出的分支的 pom.xml 文件中的内容。既然说到版本管理,那想必 Maven 肯定是可以帮助我们做这件事情的。

那么接下来我们来看一下如何使用 Maven 来创建分支,这里我们使用 Git 配合 Maven 来演示如何创建分支。

我们想要使用 Maven 来帮助我们创建分支,首先我们需要配置 scm ,这样 Maven 就可以来代替 Git 来进行操作。

这里我们在 gitee 上创建一个仓库来放置我们的项目。

1. 将 scm 配置到 pom.xml 文件中

<scm>
    <connection>scm:git:git@gitee.com:jhulk/first-project.git</connection>
    <developerConnection>scm:git:git@gitee.com:jhulk/first-project.git</developerConnection>
</scm>
代码块1234

2. 将 maven-release-plugin 插件配置到 pom.xml 文件中:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-release-plugin</artifactId>
        <version>2.5.3</version>
        <configuration>
            <tagNameFormat>${project.version}</tagNameFormat>
            <autoVersionSubmodules>true</autoVersionSubmodules>
        </configuration>
    </plugin>
</plugins>

3. 执行 mvn release:branch -DbranchName=1.0.1 -DupdateBranchVersions=true -DupdateWorkingCopyVersions=false 命令

  • -DbranchName: 目标分支。这里我们可以根据需要来自定义新分支的命名结构;

  • -DupdateBranchVersions: 在分支中更新版本;

  • -DupdateWorkingCopyVersions: 在当前分支更新版本。

在执行的时候, Maven 会问 branch version 是否为 1.0.1-SNAPSHOT?这个是我们想要的,直接 enter 即可。

执行完成之后,我们查看 Git 的远程仓库中,可以看到新生成的分支。

4. 执行 mvn release:prepare 命令

在执行的时候,Maven 会问 release version 是否是 1.0.0?这个是否是我们想要的,直接选择 enter 即可。

紧接着,Maven 会问我们 release tag 是否是1.0.0-SNAPSHOT?这个也是我们想要的,直接 ente r即可。

紧接着,Maven 会问我们新的版本是否是1.0.1-SNAPSHOT?这里输入新版本为1.1.0-SNAPSHOT,然后 enter。

执行成功之后,我们更新一下代码,会发现,主干上的 pom.xml 文件的版本已经升级为1.1.0-SNAPSHOT了。

4. 小结

本节当中,我们着重介绍了在 Maven 的世界中,我们一般是如何来约束版本的,以及这些约束所代表的含义,最后,我们还介绍了如何使用 Maven 来管理这些版本。

Maven 的聚合与继承

通常情况下,我们在实际开发过程中,会对项目进行模块(module)划分,来提供项目的清晰度并且能够更加方便的重用代码。但是,在这种时候,我们在构建项目的时候就需要分别构建不同的模块,Maven 的聚合特性能够将各个不同的模块聚合到一起来进行构建。而继承的特性,则能够帮助我们抽取各个模块公用的依赖、插件等,实现配置统一。

1. 聚合

这里我们以一个简单的 mall 项目作为例子。先来看一下这个项目的结构,整个项目包括 mall-core 和 mall-account 两个功能模块和 mall-aggregator 一个聚合模块。其中, mall-core 处理商城项目的核心业务逻辑, mall-account 用于管理商城的账户信息。

项目文件图示

一般来说,对于只有一个模块的项目,我们可以在该模块下直接执行 mvn clean package 命令来进行项目构建,但是,对于这样的多模块项目,我们如果要构建不同模块的话,需要分别在对应模块下执行 Maven 的相关命令,这样看起来是非常繁琐的。这个时候,Maven 的聚合特性就能够起到作用。

我们来分析一下这个项目整体的结构,首先,我们看一下 mall-aggregator 模块。这个模块作为整个工程的聚合模块,并没有实际的代码,但是其本身也是一个 Maven 项目,所以,也会存在 pom.xml 文件。那我们首先来看一下这个 pom.xml 文件有什么特点。

mall-aggregator 模块的 pom.xml 文件

我们可以看到这里面也会有相对应的 groupId , artifactId , version ,packaging 信息,其中 packaging 的值必须是 pom,否则聚合项目无法构建。我们从 modules 中可以看到整个项目包含两个模块,分别是 mall-core 和 mall-account 。通常情况下,我们将不同的模块放到聚合模块下,其中 module 的值分别对应不同模块的 artifactId 值。

在这个时候,我们 mall-aggregator 模块下,使用 mvn clean package 来进行构建,可以将两个模块同时打包完成。

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] mall-aggregator                                                    [pom]
[INFO] mall-core                                                          [jar]
[INFO] mall-account                                                       [jar]
[INFO]
[INFO] ----------------------< com.mic:mall-aggregator >-----------------------
[INFO] Building mall-aggregator 1.0.0-SNAPSHOT                            [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mall-aggregator ---
[INFO]
[INFO] -------------------------< com.mic:mall-core >--------------------------
[INFO] Building mall-core 1.0.0-SNAPSHOT                                  [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] ------------------------< com.mic:mall-account >------------------------
[INFO] Building mall-account 1.0.0-SNAPSHOT                               [3/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for mall-aggregator 1.0.0-SNAPSHOT:
[INFO]
[INFO] mall-aggregator .................................... SUCCESS [  0.494 s]
[INFO] mall-core .......................................... SUCCESS [  2.152 s]
[INFO] mall-account ....................................... SUCCESS [  0.145 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.002 s
[INFO] Finished at: 2020-05-05T11:32:54+08:00
[INFO] ------------------------------------------------------------------------

从这次构建的过程来看,我们可以看出,Maven 会首先解析聚合模块的 pom.xml 文件,分析出有哪些模块需要构建,进而计算出一个反应堆构建顺序(Reactor Build Order),并且根据这个顺序来依次进行模块的构建。

2. 继承

2.1 继承特性

现在我们解决了同时构建不同模块同时构建的问题,但是,对于多模块项目来说,还是会有些其他的问题存在,例如,不同模块间有相同的 groupId,version ;有时候,也会需要引入相同的依赖。这个时候,如果每个模块都重复引入的话,结果就会造成冗余。作为一个遵循面向对象的程序员来讲,这样的做法显然是不合理的。

因此,Maven 也引入了类似的机制来解决这个问题,就是继承的特性。

类似于 Java 中的父类与子类,我们也可以创建一个父模块,让其他的模块作为子模块来继承该模块,从而继承父模块中声明的依赖以及配置。

对于父模块来说,只是作为配置的公共模块,是不需要代码的,而且 pom.xml 文件中的 packaging 方式也是 pom,因此,我们可以将聚合模块同时作为父模块来使用,没有必要再创建一个父模块。(当然,这里也是可以单独创建父模块的)

此时,我们查看 mall-core 或者 mall-account 的 pom.xml 文件。

mall-core 模块的 pom.xml 文件

我们可以看到 mall-core 模块继承了父模块的坐标信息,并重新定义了 artifactId 。这时候,我们在父模块引入一个依赖,然后查看 mall-core 模块的 pom.xml 文件,会发现,在 mall-core 模块中也会引入这个依赖。但是,实际上,我们并没有在 mall-core 模块中显式的声明这个依赖。

2.2 依赖管理

其实问题并没有完全解决,并不是所有的子模块都需要引入 fastjson-1.2.49.jar 这个依赖,那要怎么办呢?

在 POM 中,我们可以在父模块中声明 dependencyManagement 元素,让子模块来继承。dependencyManagement 元素并不会实际的引入依赖,但是可以起到很好的约束依赖的作用。

首先,我们在父模块的 pom.xml 文件中声明这个元素,并加入 fastjson-1.2.49.jar 这个依赖。

<properties>
    <fastjson.version>1.2.49</fastjson.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

然后,我们在 mall-core 模块中添加这个依赖,但是我们并不需要再声明version。

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

这时候,我们分别查看父模块与子模块所引入的依赖,会发现,只有子模块中有引入这个依赖,而父模块中,并没有。

父模块依赖引入情况而子模块中已经引入了这个依赖。

子模块依赖引入情况

我们通过 Maven 继承的特性,来进行依赖管理,可以更好的控制依赖的引入。而 Maven 对于插件的管理,也存在类似的元素可以使用(pluginmanagement 元素),可以做到相同的效果。

3. 反应堆

反应堆指的是整个项目中所有模块的构建结构。在本节的例子中,整个构建结构包括三个模块。反应堆不仅仅包括模块本身,还包括了这三个模块直接的相互依赖关系。

现在,我们的示例项目中,mall-core 和 mall-account 是不存在依赖关系的。父模块中模块的配置顺序如下:

<!-- 模块配置 -->
<modules>
    <module>mall-core</module>
    <module>mall-account</module>
</modules>

这里我们稍微做一下调整,用户管理模块可以提供接口给核心业务模块来调用,因此,在 mall-core 模块中引入 mall-account 依赖。重新进行项目构建。

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] mall-aggregator                                                    [pom]
[INFO] mall-account                                                       [jar]
[INFO] mall-core                                                          [jar]
[INFO]
[INFO] ----------------------< com.mic:mall-aggregator >-----------------------
[INFO] Building mall-aggregator 1.0.0-SNAPSHOT                            [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mall-aggregator ---
[INFO]
[INFO] ------------------------< com.mic:mall-account >------------------------
[INFO] Building mall-account 1.0.0-SNAPSHOT                               [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] -------------------------< com.mic:mall-core >--------------------------
[INFO] Building mall-core 1.0.0-SNAPSHOT                                  [3/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for mall-aggregator 1.0.0-SNAPSHOT:
[INFO]
[INFO] mall-aggregator .................................... SUCCESS [  0.220 s]
[INFO] mall-account ....................................... SUCCESS [  1.006 s]
[INFO] mall-core .......................................... SUCCESS [  0.132 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.533 s
[INFO] Finished at: 2020-05-05T14:25:48+08:00
[INFO] ------------------------------------------------------------------------

从构建的结果来看,模块的构建顺序并不是按照我们在父模块中配置的顺序进行的,而是 Maven 在经过分析之后,生成反应堆,根据反应堆的构建顺序来进行构建的。

实际上,Maven 会根据模块间继承与依赖的关系来形成一个有向非循环图,并根据图中标记的顺序,来生成反应堆构建顺序,在构建的时候,根据这个顺序来进行构建。本节中的实例项目的有向非循环图如下:

继承与依赖关系的有向非循环图

在这个图中,是不能出现循环的,假如我们在 mall-account 模块中也添加入 mall-core 的依赖,再进行项目构建的时候,Maven 则会报错出来,提示我们这个反应堆中存在循环引用。

[INFO] Scanning for projects...
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.mic:mall-account:1.0.0-SNAPSHOT'}' and 'Vertex{label='com.mic:mall-core:1.0.0-SNAPSHOT'}' introduces to cycle in the graph com.m
ic:mall-core:1.0.0-SNAPSHOT --> com.mic:mall-account:1.0.0-SNAPSHOT --> com.mic:mall-core:1.0.0-SNAPSHOT @
[ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.mic:mall-account:1.0.0-SNAPSHOT'}' and 'Vertex{label='com.mic:mall-core:1.0.0-SNAPSHOT'}' introduces to cycle in the graph com.mic:mall-
core:1.0.0-SNAPSHOT --> com.mic:mall-account:1.0.0-SNAPSHOT --> com.mic:mall-core:1.0.0-SNAPSHOT -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki./confluence/display/MAVEN/ProjectCycleException

4. 小结

在本节的学习中,我们使用一个示例项目演示了 Maven 聚合与继承两个特性,以及两者之间的关系,最后介绍了 Maven 构建项目时候,反应堆构建顺序的生成,以及一些注意事项。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多