大家好,今天我们这个专题的主角是 — Maven。Maven 作为我们开发当中比较常见的项目管理工具,用来帮助我们构建项目,管理依赖。Maven 目前是 Apache 基金会托管的顶级项目之一,诞生自 2003 年,现在已经 17 岁了。本文当中,我们将介绍 Maven 是什么,Maven 的优缺点有哪些,为什么我们要使用 Maven。 1. 什么是 Maven?那究竟什么是 Maven 呢,在 Maven 的官网上可以看到如下的解释:
翻译过来就是: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 的官网中我们就可以看到以下几个特点:
总之呢,Maven 的核心是约定大于配置,它的初衷就是帮助程序开发者在最短时间内完成项目开发过程中的每个过程,目标就是更简单,更统一,更快速。 2.2 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 版本。
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 下安装 Maven1.1 下载 Maven 安装包首先在 Maven 官网上,找到下载地址,并下载该版本apache-maven-3.6.3-bin.zip,如下图所示: 1.2 检查 jdk 版本由于 Maven 是使用 Java 开发的工具,因此需要先安装 jdk。Maven3.6.3 版本需要 jdk1.7+ 版本来支持,本教程使用的是 jdk1.8 版本。
打开 cmd 运行窗口输入: 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 之后,我们在任意目录下执行 mvn -v 命令,都可以正常查看 Maven 版本信息,即为配置成功。 2.Linux 系统中安装 Maven2.1 下载安装包在 Linux 系统中,需要使用 tar 包来进行安装。我们可以去官网下载对应安装包(apache-maven-3.6.3-bin.tar.gz),也可以使用 Linux 系统中的 wget 工具来进行下载。这里我们使用 wget 工具来进行下载。 我们可以从阿里云的镜像站中下载,速度更快些。 进入到需要下载的目录,我们这里的目录地址是 2.2 解压安装包执行 2.3 添加环境变量打开配置文件
保存文件后,执行 2.4 查看Maven版本此时,切换到任意目录下,执行命令 3.Mac 环境中安装 Maven在 Mac 环境中安装 Maven 和在 Linux 环境下安装 Maven 的步骤和过程大体是相同的,只不过在 Mac 环境中的环境变量文件位置是 `~/.bash_profile``,所以在这个文件中添加环境变量即可。 4. Maven配置我们在将 Maven 安装好之后,为了方便我们后面的使用,可以对 Maven 进行简单的配置。 进入 Maven 路径下的 conf 目录,打开 setting.xml 文件。 4.1 修改本地仓库位置在 Maven 的使用过程中,会自动将项目依赖的 jar 包从中央仓库下载到本地仓库,默认本地仓库路径是 4.2 修改镜像位置由于 Maven 中央仓库的服务器是架设在国外的,所以由于某种不可抗拒力量,国内用户如果直接使用中央仓库的话,下载速度会受很大的影响。如下图所示,个人用户可以使用阿里云镜像。这里阿里云仓库是作为中央仓库的一个镜像的,镜像库会完全屏蔽被镜像库。 镜像地址:
经过上面的简单配置之后,我们就可以开心地使用 Maven 了。 5. 使用 Maven 创建项目配置好 Maven 之后,接下来我们就可以使用 Maven 来创建我们的第一个项目了。 在 cmd 中切换到我们存放代码的目录,并执行如下命令:
参数说明:
项目创建好之后,会有如下提示:
接下来,我们将该项目导入到 Idea 中,来查看该项目。 点击运行后,可以正常输出 Hello World! 6. 视频演示安装细节6.1 安装 Maven2Your browser does not support the video tag. 6.2 安装 Maven46.1 安装 Maven2Your browser does not support the video tag. 6.2 安装 Maven47. 小结本节,我们主要讲了如何在 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 中打开项目根目录,执行
注: 这里 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)。
2. 超级 POM在我们这个项目的 pom.xml 文件中,只有短短的几行信息,但是这就全部吗?其实不然。 在 Maven 的世界中,存在着一个超级 POM(super POM),所有通过 Maven 创建的项目,其 pom.xml 文件都会继承这个超级 POM。所以在默认情况下,使用 Maven 创建出来的项目基本上都是很类似的。 那么这个超级 POM 在哪呢?长什么样呢? 2.1 超级 POM 路径位置如下图,先找到指定的 jar 包,路径: 然后我们可以使用解压工具查看该 jar 包,找到对应的 pom.xml。 具体路径: 2.2 查看超级 POM 结构
对于我们的项目,我们称超级 POM 为父 POM,我们项目中的 POM 为子 POM,一般情况下如果父子 POM 中存在相同的元素或者节点,那么子 POM 会覆盖父 POM 的元素或者节点(有点类似 Java 中的 override),但是,也会有这么几个例外存在:
子 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 的依赖范围分为以下几种:
2. 传递性依赖目前我们的项目只引用了两个依赖,spring-boot-starter 和 spring-boot-starter-test,但是是这样子的吗? 为了能够更清晰的看到我们项目依赖的结构,我们可以在 IDEA 里面安装 Maven Helper 插件。
从这里我们就可以看到,其实我们不只是引入了两个包,而是引入了很多个包,这是为什么呢? 答案是因为 Maven 的传递性依赖机制。 在我们这个项目中,我们引入了 spring-boot-starter 依赖,并且该依赖的范围是 compile,但是 spring-boot-starter 作为一个项目也有自己的依赖,在这其中的依赖范围为 compile 的依赖,则会自动转换成我们项目的依赖,例如 spring-boot 依赖,logback-core 依赖。 所以,有了 Maven 的传递性依赖机制之后,我们在使用一个依赖的时候,就不再需要考虑它又依赖了哪些,而是直接使用即可,其他的事情 Maven 会自动帮我们做完。 3. 最短路径原则有了传递性依赖能够大大节省我们在管理依赖时候所耗费的精力。但是,如果传递性依赖出了问题我们应该如何解决呢?首先,我们应该知道的是传递性依赖是从哪条依赖路径引用进来的。 在我们的项目中就存在这样的例子。我们可以看到如下两条不同的引用路径: 1. 2. 这个时候,我们可以看到,两条路径最终引用的 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,此时我们就可以将该依赖排除。
在 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 路径下,找到 从上图我们可以看到,Maven 的默认本地仓库路径是在 2.2 中央仓库Maven 中默认配置了中央仓库,我们可以在超级 POM 里面找到对应的配置。 这个仓库是由 Maven 社区来维护的,里面存放了绝大多数开源软件的包,并且是作为 Maven 的默认配置,不需要开发者额外配置。另外为了方便查询,还提供了一个查询地址,开发者可以通过这个地址更快的搜索需要构件的坐标。 2.3 其他远程仓库有了中央仓库,我们为什么还需要其他的远程仓库呢?
这种时候,我们就可以选择一个使用起来相对方便的远程仓库来配置,大大提高了我们的开发效率。 国内常用的 Maven 仓库: 阿里云镜像:
阿里巴巴镜像:
repo2 镜像:
我们可以将对应的仓库的镜像配置到 settings.xml 文件中的 mirrors 节点中即可。如下图所示,我们配置了阿里云的镜像。 2.4 私服私服也是属于远程仓库的一种,相对公共仓库而言属于某个公司或者某个开发团队私有的远程仓库。通常部署在某个局域网内,提供局域网的内部用户使用。 那私服有什么好处呢?
当我们需要一个构件的时候,Maven 会先去请求私服,如果发现私服中,没有该构件,那么就会去配置了的远程仓库中寻找,并且缓存到我们的私服中,为后续的下载请求提供服务。 3. Maven 的依赖解析顺序我们知道了 Maven 通过坐标去仓库中寻找对应的构件,那么这个机制的原理是怎么样的呢? Maven 在寻找需要的依赖的时候,会遵照下面的顺序:
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 生命周期包括:
一般情况下,Maven 的构建结果会生成在 target 目录下,我们执行 mvn clean 命令后,这个目录会被清空。 从上图,我们可以看到,对应的 target 目录被清理干净了。 2.2 default生命周期default 生命周期应该算是大多数开发者最为熟悉的生命周期,也是平时在开发过程中最常用的生命周期。 (clean,site 并不属于 default 生命周期)在 default 生命周期中,最常用的几个阶段包括:
这里我们介绍的只是在 default 生命周期中最常用的,其实在这些阶段执行的过程中,还会有其他的阶段需要执行,但是并非很常用。另外,不出意外的情况下,在生命周期中,后执行的阶段要等先执行的阶段执行完再执行。 我们试着执行 Maven 的打包命令: 2.3 site 生命周期很多时候,我们不仅仅需要构建我们的项目,还需要生成项目文档或者站点。site 生命周期则是来帮助我们做这件事情的,它能够根据我们项目中 pom.xml 的信息,来生成一个友好的站点。 跟其他的生命周期一样,site 生命周期也包含不止一个阶段:
3.插件3.1 插件目标其实在 Maven 的世界中,生命周期只是一个抽象的模型,其本身并不会直接去做事情,真正帮我们完成事情的是 Maven 的插件。Maven 的插件也属于构件的一种,也是可以放到 Maven 仓库当中的。 通常情况下,一个插件可以做 A、B、C 等等不止一件事情,但是我们又没有必要为每一个功能都做一个单独的插件。这种时候,我们一般会给这个插件绑定不同的目标,而这些目标则是对应其不同的功能。 当我们使用一个插件的目标的时候,我们可以执行命令: 使用该插件目标,我们可以看到目前我们项目中所有依赖的情况。 3.2 插件绑定我们说 Maven 的生命周期只是抽象的概念,真正帮我们完成事情的是插件,其实更确切的说,应该是生命周期与插件对应的目标绑定,来完成具体的功能。 4. 小结在本节中,我们详细介绍了 Maven 的生命周期,常用的生命周期,以及其与插件的对应关系,简单的工作原理。学完之后,能够加深 Maven 的理解,减少使用过程中的误解。 Maven 版本管理本节中,我们来介绍一下 Maven 是如何进行版本管理的。如何在项目的实际开发中,结合 Maven 来推进项目的进行。一个正常的项目的开发周期通常是很长的,这个过程当中,需要发布很多个版本,那这些版本如何表示,而我们又应该如何来管理这些版本呢? 1. 什么是版本管理那什么是版本管理呢?首先,版本管理是不同于版本控制的。版本控制通常的概念是在软件开发过程中,管理程序文件,配置文件等文件的变化。更倾向于来追踪一个项目过程中,不同时期项目的变化。但是,版本管理则不同,通常是指一个项目过程中,不同时期版本的演化过程。通俗一点讲,版本管理就像人的成长过程中,从婴儿到少年到青年到中年一直到老年这个演变过程的管理;版本控制则更关注细节,例如这个时期,身高从 160cm 长到了 165cm,或者体重 60kg 变为了 62kg 等等。 2. 约定版本号我们理解了什么是版本管理,那 Maven 是如何做的呢? 通常情况下,Maven 的版本号约定中包括如下几个部分: <主版本号>.<次版本号>.<增量版本号>.<里程碑版本号>
由于 Maven 已经维护了将近 20 年,所以,使用 Maven 这个项目的版本演变过程来举例是再合适不过了。我们打开 Maven 的官网,找到 Release Notes,打开便可以看到 Maven 从最开始的版本是如何演变成现在的模样的。(这里由于存在太多版本,所以,我们只截取了其中一部分) (Maven 版本演变历史列表) 注意: 有的同学可能会问,里程碑版本里面的 alpha,beat 是什么意思?
这些版本号在一些流行的框架的演变过程中非常常见。 3. 主干、分支与标签通常情况下,我们在进行项目开发的过程中,会使用到版本控制工具,例如 svn 或者 git,这时候就会涉及到主干,分支以及标签的概念,那么这里我们简单介绍一下这三个概念。 3.1 概念
从下面的图中,我们可以比较清晰地看出,主干,分支与标签三者之间的关系: 主干,分支与标签三者关系图 3.2 创建分支我们在了解了主干以及分支的概念之后,那么我们如何去创建分支呢?通常情况下我们可以使用 Svn 或者 Git 自带的创建分支的方式来创建需要的分支,但是,这个时候会存在一个问题:我们需要手动去修改新创建出的分支的 pom.xml 文件中的内容。既然说到版本管理,那想必 Maven 肯定是可以帮助我们做这件事情的。 那么接下来我们来看一下如何使用 Maven 来创建分支,这里我们使用 Git 配合 Maven 来演示如何创建分支。 我们想要使用 Maven 来帮助我们创建分支,首先我们需要配置 scm ,这样 Maven 就可以来代替 Git 来进行操作。 这里我们在 gitee 上创建一个仓库来放置我们的项目。 1. 将 scm 配置到 pom.xml 文件中:
2. 将 maven-release-plugin 插件配置到 pom.xml 文件中:
3. 执行
在执行的时候, Maven 会问 branch version 是否为 1.0.1-SNAPSHOT?这个是我们想要的,直接 enter 即可。 执行完成之后,我们查看 Git 的远程仓库中,可以看到新生成的分支。 4. 执行 在执行的时候,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 用于管理商城的账户信息。 项目文件图示 一般来说,对于只有一个模块的项目,我们可以在该模块下直接执行 我们来分析一下这个项目整体的结构,首先,我们看一下 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 模块下,使用
从这次构建的过程来看,我们可以看出,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 这个依赖。
然后,我们在 mall-core 模块中添加这个依赖,但是我们并不需要再声明version。
这时候,我们分别查看父模块与子模块所引入的依赖,会发现,只有子模块中有引入这个依赖,而父模块中,并没有。 父模块依赖引入情况而子模块中已经引入了这个依赖。 子模块依赖引入情况 我们通过 Maven 继承的特性,来进行依赖管理,可以更好的控制依赖的引入。而 Maven 对于插件的管理,也存在类似的元素可以使用(pluginmanagement 元素),可以做到相同的效果。 3. 反应堆反应堆指的是整个项目中所有模块的构建结构。在本节的例子中,整个构建结构包括三个模块。反应堆不仅仅包括模块本身,还包括了这三个模块直接的相互依赖关系。 现在,我们的示例项目中,mall-core 和 mall-account 是不存在依赖关系的。父模块中模块的配置顺序如下:
这里我们稍微做一下调整,用户管理模块可以提供接口给核心业务模块来调用,因此,在 mall-core 模块中引入 mall-account 依赖。重新进行项目构建。
从构建的结果来看,模块的构建顺序并不是按照我们在父模块中配置的顺序进行的,而是 Maven 在经过分析之后,生成反应堆,根据反应堆的构建顺序来进行构建的。 实际上,Maven 会根据模块间继承与依赖的关系来形成一个有向非循环图,并根据图中标记的顺序,来生成反应堆构建顺序,在构建的时候,根据这个顺序来进行构建。本节中的实例项目的有向非循环图如下: 继承与依赖关系的有向非循环图 在这个图中,是不能出现循环的,假如我们在 mall-account 模块中也添加入 mall-core 的依赖,再进行项目构建的时候,Maven 则会报错出来,提示我们这个反应堆中存在循环引用。
4. 小结在本节的学习中,我们使用一个示例项目演示了 Maven 聚合与继承两个特性,以及两者之间的关系,最后介绍了 Maven 构建项目时候,反应堆构建顺序的生成,以及一些注意事项。 |
|