一、Maven 是什么
Maven 是 Apache 软件基金会维护的唯一一款自动化构建工具,专注于服务 Java 平台的项目构建和依赖管理。Maven 是采用纯 Java 编写的开源的基于项目对象模型 Project Object Model(POM),可以通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具,所有的项目配置信息都被定义在 pom.xml 的文件中。
POM 怎么理解?
Java 是面向对象编程,对象就是类。而 Maven 是面向项目,把项目看做成是一个对象来进行管理。是一个跨平台的项目管理工具,主要服务于 Java 平台的项目构建、依赖管理和项目信息管理。
1️⃣项目构建:通过插件帮助完成项目的清理、编译、测试、打包、部署。比如之前除了编写源代码,每天有相当一部分时间花在了编译、运行单元测试、生成文档、打包和部署等繁琐而又不得不做的工作上。
2️⃣依赖管理:通过坐标从 maven 仓库导入 Java 类库(jar文件)。比如之前项目导入 jar 是通过 copy 方式导入项目中,而且还会存在 jar 之间的依赖和冲突。而 maven 解决了这些问题,帮助下载 Jar 包。
3️⃣仓库管理:提供统一管理所有 Jar 包的工具。
4️⃣项目信息管理:项目描述、开发者列表、版本控制系统地址等。比如发布版本之后可能还要对版本进行升级。
二、Maven 可以做什么
①添加第三方 jar 包。
②jar 包之间的依赖关系:Maven 可以自动的将当前 jar 包所依赖的其他所有 jar 包全部导入进来。
③获取第三方 jar 包:Maven 提供了一个完全统一规范的 jar 包管理体系,只需要在项目中以坐标的方式依赖一个 jar 包,Maven 就会自动从中央仓库下载到本地仓库。
④将项目拆分成多个工程模块。
⑤构建项目(打包,编译等)。
三、构建项目的几个主要环节
①清理(clean):删除以前的编译结果,为重新编译做好准备。
②编译(compile):将 Java 源程序编译为字节码文件。
③测试(test):针对项目中的关键点进行测试,确保项目在迭代开发过程中关键点的正确性。
④报告:在每一次测试后以标准的格式记录和展示测试结果。
⑤打包(package):将一个包含诸多文件的工程封装为一个压缩文件用于安装或部署。Java 工程对应 jar 包,Web 工程对应 war 包。
⑥安装(install):在 Maven 环境下特指将打包的结果——jar 包或 war 包安装到本地仓库中。
⑦部署(deploy):将打包的结果部署到远程仓库或将 war 包部署到服务器上运行。
四、Maven常用命令
①mvn -version/-v —— 显示版本信息
②mvn clean —— 清空生成的文件
③mvn compile —— 编译
④mvn test —— 编译并测试
⑤mvn package —— 生成target目录,编译、测试代码,生成测试报告,生成 jar/war 文件
⑥mvn site —— 生成项目相关信息的网站
⑦mvn clean compile —— 表示先运行清理之后运行编译,会将代码编译到 target 文件夹中
⑧mvn clean package —— 运行清理和打包
⑨mvn clean install —— 运行清理和安装,会将打好的包安装到本地仓库中,以便其他的项目可以调用
⑩mvn clean deploy —— 运行清理和发布
五、Maven核心概念
Maven 能够实现自动化构建是和它的内部原理分不开的,这里从 Maven 的九个核心概念入手,看看 Maven 是如何实现自动化构建的。
①POM
②约定的目录结构
③坐标
④依赖管理
⑤仓库管理
⑥生命周期
⑦插件和目标
⑧继承
⑨聚合
Maven 的核心程序中仅仅定义了抽象的生命周期,而具体的操作则是由 Maven 的插件来完成的。可是 Maven 的插件并不包含在 Maven 的核心程序中,在首次使用时需要联网下载。下载得到的插件会被保存到本地仓库中。本地仓库默认的位置是:~.m2\repository。
1️⃣Maven 约定的工程目录Java 开发领域普遍认同的一个观点:约定>配置>编码(能用配置解决的问题就不编码,能基于约定的就不配置)
2️⃣pom:Project Object Model 项目对象模型
将 Java 工程的相关信息封装为对象作为便于操作和管理的模型。Maven 工程的核心配置。
3️⃣Maven 的坐标
使用如下三个向量在 Maven 的仓库中唯一的确定一个 Maven 工程:
①groupid:公司或组织的域名倒序+当前项目名称
②artifactId:当前项目的模块名称
③version:当前模块的版本
com.xxp
demo
0.0.1-SNAPSHOT
如何通过坐标到仓库中查找 jar 包?将 gav 三个向量连起来com.xxp+demo+0.0.1-SNAPSHOT
复制代码以连起来的字符串作为目录结构到仓库中查找com/xxp/demo/0.0.1-SNAPSHOT/demo-0.0.1-SNAPSHOT.jar
※ 注意:自己的 Maven 工程必须执行安装操作才会进入仓库。安装的命令是:mvn install
4️⃣Maven 中最关键的部分:依赖
使用 Maven 最主要的就是使用它的依赖管理功能。要理解和掌握 Maven 的依赖管理,需要解决以下几个问题:
①依赖的目的是什么
当 A jar 包用到了 B jar 包中的某些类时,A 就对 B 产生了依赖,这是概念上的描述。那么如何在项目中以依赖的方式引入一个需要的 jar 包呢? 就是使用 dependency 标签指定被依赖 jar 包的坐标就可以了。
org.springframework
spring-core
5.2.6.RELEASE
②依赖的范围
有时依赖信息中除了目标 jar 包的坐标还有一个 scope 设置,这就是依赖的范围。依赖的范围有几个可选值,常用的有:compile、test、provided 三个,当然还有不常用的 runtime、system..
- compile:默认范围,编译测试运行都有效
- provided:在编译和测试时有效
- runtime:在测试和运行时有效
- test:只在测试时有效
- system:在编译和测试时有效,与本机系统关联,可移植性差
③依赖的传递性
A 依赖 B,B 依赖 C,A 能否使用 C 呢?那要看 B 依赖 C 的范围是不是 compile,如果是则可用,否则不可用。
④依赖的排除
如果在当前工程中引入了一个依赖是 A,而 A 又依赖了 B,那么 Maven 会自动将 A 依赖的 B 引入当前工程,但是个别情况下 B 有可能是一个不稳定版,或对当前工程有不良影响。这时可以在引入 A 的时候将 B 排除。
com.xxp
demo
0.0.1-SNAPSHOT
compile
commons-logging
commons-logging
⑤统一管理所依赖 jar 包的版本,对同一个框架的一组 jar 包最好使用相同的版本。为了方便升级框架,可以将 jar 包的版本信息统一提取出来,统一声明版本号:
4.1.1.RELEASE
UTF-8
引用前面声明的版本号:
org.springframework
spring-core
${starfish.spring.version}
compile
⑥依赖的原则:解决 jar 包冲突
- 路径最短者优先
- 路径相同时先声明者优先
5️⃣仓库
①本地仓库:为当前本机电脑上的所有 Maven 工程服务。
②远程仓库私服:架设在当前局域网环境下,为当前局域网范围内的所有 Maven 工程服务
③中央仓库:架设在 Internet 上,为全世界所有 Maven 工程服务
④中央仓库的镜像:架设在各个大洲,为中央仓库分担流量。减轻中央仓库的压力,同时更快的响应用户请求,比如阿里的镜像。
⑤不管是什么样的 jar 包,在仓库中都是按照坐标生成目录结构,所以可以通过统一的方式查询或依赖,查询地址:mvnrepository.com
6️⃣生命周期
①什么是 Maven 的生命周期?
Maven 生命周期定义了各个构建环节的执行顺序,有了这个清单,Maven 就可以自动化的执行构建命令了。Maven 有三套相互独立的生命周期,分别是:
- Clean Lifecycle 在进行真正的构建之前进行一些清理工作
- Default Lifecycle 构建的核心部分,编译,测试,打包,安装,部署等等
- Site Lifecycle 生成项目报告,站点,发布站点
它们是相互独立的,可以仅仅调用 clean 来清理工作目录,仅仅调用 site 来生成站点。当然也可以直接执行mvn clean install site
运行所有这三套生命周期。 每套生命周期都由一组阶段(Phase)组成,平时在命令行输入的命令总会对应于一个特定的阶段。比如,运行 mvn clean,这个 clean 是 Clean 生命周期的一个阶段。有 Clean 生命周期,也有 clean 阶段。
②Clean 生命周期
- pre-clean 执行一些需要在 clean 之前完成的工作
- clean 移除所有上一次构建生成的文件
- post-clean 执行一些需要在 clean 之后立刻完成的工作
③Site 生命周期
- pre-site 执行一些需要在生成站点文档之前完成的工作
- site 生成项目的站点文档
- post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
- site-deploy 将生成的站点文档部署到特定的服务器上 这里经常用到的是 site 阶段和 site-deploy 阶段,用以生成和发布 Maven 站点,这可是 Maven 相当强大 的功能,Manager 比较喜欢,文档及统计数据自动生成,很好看。
④Default 生命周期
Default 生命周期是 Maven 生命周期中最重要的一个,绝大部分工作都发生在这个生命周期中(列出一些重要阶段)
- validate:验证工程是否正确,所有需要的资源是否可用。
- compile:编译项目的源代码。
- test:使用合适的单元测试框架来测试已编译的源代码。这些测试不需要已打包和布署。
- package:把已编译的代码打包成可发布的格式,比如 jar、war 等。
- integration-test:如有需要,将包处理和发布到一个能够进行集成测试的环境。
- verify:运行所有检查,验证包是否有效且达到质量标准。
- install:把包安装到maven本地仓库,可以被其他工程作为依赖来使用。
- deploy:在集成或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享
⑤生命周期与自动化构建
运行任何一个阶段的时候,它前面的所有阶段都会被运行,例如我们运行 mvn install 的时候,代码会被编译,测试,打包。这就是 Maven 为什么能够自动执行构建过程的各个环节的原因。此外,Maven 的插件机制是完全依赖 Maven 的生命周期的,因此理解生命周期至关重要。
7️⃣插件和目标
- Maven 的核心仅仅定义了抽象的生命周期,具体的任务都是交由插件完成的
- 每个插件都能实现多个功能,每个功能就是一个插件目标
- Maven 的生命周期与插件目标相互绑定,以完成某个具体的构建任务 例如:compile 就是插件 maven-compiler-plugin 的一个目标;pre-clean 是插件 maven-clean-plugin 的一个目标
8️⃣继承
为什么需要继承机制? 由于非 compile 范围的依赖信息是不能在“依赖链”中传递的,所以有需要的工程只能单独配置。创建父工程和创建一般的 Java 工程操作一致,唯一需要注意的是:打包方式处要设置为 pom。在子工程中引用父工程 ,从当前目录到父项目的 pom.xml 文件的相对路径。
org.springframework.boot
spring-boot-starter-parent
2.3.0.RELEASE
../Parent/pom.xml
此时如果子工程的 groupId 和 version 如果和父工程重复则可以删除。在父工程中管理依赖 将 Parent 项目中的 dependencies 标签,用 dependencyManagement 标签括起来:
junit
junit
4.9
test
在子项目中重新指定需要的依赖,删除范围和版本号:
junit
junit
9️⃣聚合
为什么要使用聚合?将多个工程拆分为模块后,需要手动逐个安装到仓库后依赖才能够生效。修改源码后也需要逐个手动进 行 clean 操作。而使用了聚合之后就可以批量进行 Maven 工程的安装、清理工作。如何配置聚合? 在总的聚合工程中使用 modules/module 标签组合,指定模块工程的相对路径即可:
starfish-learn-grpc
starfish-learn-kafka
starfish-web-demo
六、package install deploy 区别
1️⃣打包区别
①mvn package
:打包到本项目,一般在项目target
目录下。
②mvn install
:打包到本地仓库,如果没设置Maven
本地仓库,一般在用户/.m2
目录下。
③mvn deploy
:打包上传到远程仓库,如:私服nexus
等,需要配置pom
文件。
2️⃣打包过程
① mvn clean package
依次执行:clean
、resources
、compile
、testResources
、testCompile
、test
、jar
(打包)。
②mvn clean install
依次执行:clean
、resources
、compile
、testResources
、testCompile
、test
、jar
(打包)、install
。
③mvn clean deploy
依次执行:clean
、resources
、compile
、testResources
、testCompile
、test
、jar
(打包)、install
、deploy
。
由上面分析主要区别如下:
①package
命令:完成项目编译、单元测试、打包功能,但打包文件未部署到本地Maven
仓库和远程Maven
仓库。
②install
命令:完成项目编译、单元测试、打包功能,同时把打包文件部署到本地Maven
仓库,但未部署到远程Maven
仓库。
③deploy
命令:完成项目编译、单元测试、打包功能,同时把打包文件部署到本地Maven
仓库和远程Maven
仓库。
七、理解 Maven 中的 SNAPSHOT 版本和正式版本
Maven 中建立的依赖管理方式基本已成为 Java 语言依赖管理的事实标准,Maven 的替代者 Gradle 也基本沿用了 Maven 的依赖管理机制。在 Maven 依赖管理中,唯一标识一个依赖项是由该依赖项的三个属性构成的,分别是 groupId、artifactId 以及 version。这三个属性可以唯一确定一个组件(Jar包或者War包)。
其实在 Nexus 仓库中,一个仓库一般分为 public(Release) 仓和 SNAPSHOT 仓,前者存放正式版本,后者存放快照版本。如果在项目配置文件中(无论是 build.gradle 还是 pom.xml)指定的版本号带有“-SNAPSHOT”后缀,比如版本号为“Junit-1.0-SNAPSHOT”,那么打出的包就是一个快照版本。
两个版本的主要区别在于,本地获取这些依赖的机制有所不同。假设依赖一个库的正式版本,构建的时候构建工具会先在本次仓库中查找是否已经有了这个依赖库,如果没有的话才会去远程仓库中去拉取。所以假设发布了 Junit-1.0.jar 到了远程仓库,有一个项目依赖了这个库,它第一次构建的时候会把该库从远程仓库中下载到本地仓库缓存,以后再次构建都不会去访问远程仓库了。所以如果修改了代码,向远程仓库中发布了新的软件包,但仍然叫 Junit-1.0.jar,那么依赖这个库的项目就无法得到最新更新。只有在重新发布的时候升级版本,比如叫做 Junit-1.1.jar,然后通知依赖该库的项目组也修改依赖版本为 Junit-1.1,这样才能使用到最新添加的功能。
在团队内部,如此开发特别繁琐。假设有两个小组负责维护两个组件,pay-service 和 pay-ui,其中 pay-ui 项目依赖于 pay-service。而这两个项目每天都会构建多次,如果每次构建都要升级 pay-service 的版本,想想都头疼。SNAPSHOT 版本就是解决这个问题的。日常构建时可以构建 pay-service 的快照版本,比如 pay-service-1.0-SNAPSHOT.jar,而 pay-ui 依赖该快照版本。每次 pay-ui 构建时,会优先去远程仓库中查看是否有最新的 pay-service-1.0-SNAPSHOT.jar,如果有则下载下来使用。即使本地仓库中已经有了 pay-service-1.0-SNAPSHOT.jar,它也会尝试去远程仓库中查看同名的 jar 是否是最新的。然而,这样不就不能充分利用本地仓库的缓存机制了吗?其实在配置 Maven 的 Repository 的时候中有个配置项,可以配置对于 SNAPSHOT 版本向远程仓库中查找的频率。频率共有四种,分别是 always、daily、interval、never。当本地仓库中存在需要的依赖项目时,always 是每次都去远程仓库查看是否有更新,daily 是只在第一次的时候查看是否有更新,当天的其它时候则不会查看;interval 允许设置一个分钟为单位的间隔时间,在这个间隔时间内只会去远程仓库中查找一次,never 是不会去远程仓库中查找(这种就和正式版本的行为一样了)。
Maven版本的配置方式为:
myRepository
...
true
XXX
其中 updatePolicy 就是那 4 种类型之一。如果配置间隔时间更新,可以写作 interval:XX (XX是间隔分钟数)。daily 配置是默认值。而在 Gradle,可以设置本地缓存的更新策略:
configurations.all {
// check for updates every build
resolutionStrategy.cacheChangingModulesFor 0,'seconds'
}
当然也可以按照分钟或者小时来设置:
configurations.all {
resolutionStrategy.cacheChangingModulesFor 10, ‘minutes'
}
configurations.all {
resolutionStrategy.cacheChangingModulesFor 4, ‘hours'
}
日常开发,可以频繁发布 SNAPSHOT 版本,以便让其它项目能实时的使用到最新的功能做联调;当版本趋于稳定时,再发布一个正式版本,供正式使用。当然在做正式发布时,也要确保当前项目的依赖项中不包含对任何 SNAPSHOT 版本的依赖,保证正式版本的稳定性。
八、其它流行的构建工具(了解)
Java 世界,目前在被使用的常用构建工具有三个:Ant、Maven、Gradle。
1️⃣Ant 的核心是由 Java 编写,采用 XML 作为构建脚本,这样就允许在任何环境下,运行构建。Ant 基于任务链思想,任务之间定义依赖,形成先后顺序。缺点是使用 XML 定义构建脚本,导致脚本臃肿。Ant 自身没有为项目构建提供指导,导致每个 build 脚本都不一样,开发人员对于每个项目都需要去熟悉脚本内容,没有提供在 Ant 生态环境内的依赖管理工具。
2️⃣Maven 团队意识到 Ant 的缺陷,采用标准的项目布局,和统一的生命周期,采用约定由于配置的思想,减少构建脚本需要的编写内容,活跃的社区,可以方便找到合适的插件,强大的依赖管理工具。缺点是采用默认的结构和生命周期,太过限制,编写插件扩展麻烦,XML 作为构建脚本。
3️⃣Gradle 同时拥有 Ant 和 Maven 的优点,它是基于 Groovy 的 DSL,提供声明式的构建语言,采用标准的项目布局,但拥有完全的可配置性,就是可以改,通过插件,提供默认的构建生命周期,也可以自己定义任务,单独运行任务,定义任务间的依赖,强大的依赖管理工具,与 Maven 和 Ivy 仓库结合,与 Ant 天生兼容,有效的重用 Ant 的任务,多种实现插件的方式,强大的官方插件库,从构建级别,支持从 Ant 或者 Maven 的逐步迁移,通过包装器,无缝的在各个平台运行。
4️⃣如何识别项目构建工具,一般来说,一个项目的根目录中就会包含构建工具的配置文件信息,也表明了该项目使用的构建工具,通常有如下的对应关系:
①build.xml - 该项目使用 Ant 构建
②pom.xml - 该项目使用 Maven 构建
③build.gradle - 该项目使用 Gradle 构建