Maven生命周期与插件
除了坐标、依赖以及仓库之外,Maven另外两个核心概念是生命周期和插件。命令行的输入往往对应生命周期,而生命周期是抽象的,实际行为由插件完成。
何为生命周期
在Maven出现之前,项目构建的生命周期就已经存在,清理、编译、测试及部署。但公司与公司间,项目与项目间,往往使用不同方式做类似的工作。
Maven的生命周期对所有的构建过程进行抽象和统一。这个生命周期包括项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。
Maven的生命周期是抽象的,实际任务都交由插件来完成。这种思想与设计模式中的模板方法(Template Method)非常相似,生命周期是模板方法,而插件就是子类覆写的方法。
总结:Maven的生命周期定义了项目构建的流程,而插件负责实现流程中每个步骤,最后将插件与生命周期流程中每个步骤绑定即可完成项目构建。在这个过程中插件可以在多个项目中复用,而通过更换插件绑定亦可实现差异化构建。
生命周期详解
Maven拥有三套相互独立的生命周期。
生命周期 | 阶段(phase) | 说明 |
---|---|---|
clean | pre-clean | 执行清理前需要完成的工作 |
clean | 清理上一次构建生成的文件 | |
post-clean | 执行清理后需要完成的工作 | |
default | vaildate | 验证,确保当前配置和POM内容是有效的,包含对POM文件树的验证。 |
intianlize | 初始化,执行构建生命周期的主任务之前的初始化 | |
generate-sources | 生成源码,代码生成器生成后期阶段中处理或编译的源代码 | |
proccess-sources | 处理源码,提供解析、修改和转换源码。常规源码和生成的源码都可以再这里处理 | |
generate-resoureces | 生成资源,生成非源码资源,通常包括元数据文件和配置文件 | |
process-resources | 处理资源,处理非源码资源,修改、转换和重定位资源都能在这阶段发生 | |
compile | 编译,编译源码。编译过的类被放到目标目录树中 | |
process-classes | 处理类,处理类文件转换和增强步骤。字节码交织器和常用工具常在这一阶段操作 | |
generate-test-sources | 生成测试源码,生成要操作的单元测试代码 | |
process-test-sources | 处理测试源码,在编译前对测试源码执行任何必要的处理。修改、转换或复制源代码 | |
generate-test-resources | 生成测试资源,生成与测试相关的非源码资源 | |
process-test-resources | 处理测试资源,处理、转换或重新定位于测试相关的资源 | |
test-compile | 测试编译,编译单元测试的源码 | |
process-test-classes | 处理测试类,对编译生成文件做后期处理(Maven2.0.5及以上) | |
test | 测试,运行编译过的单元测试并累计结果 | |
prepare-package | 执行打包前的所有操作(Maven2.1及以上) | |
package | 打包,将可执行的二进制文件打包到一个分布式归档文件中,如jar或war | |
pre-integration-test | 前集成测试,准备集成测试,将归档文件部署到一个服务器上执行 | |
integration-test | 集成测试,执行真正的集成测试,指在一个受到一定控制的模拟的真实部署环境中测试代码 | |
post-integration-test | 后集成测试,解除集成测试准备,涉及环境重置或重新初始化 | |
verify | 检验,检验可部署归档文件的有效性和完整性,通过后,将安装该归档 | |
install | 安装,将项目包安装到本地仓库,供其他项目依赖 | |
deploy | 部署,将项目发布到远程仓库,供其他开发人员与项目共享 | |
site | pre-site | 执行一些在生成项目站点之前需要完成的工作 |
site | 生成项目站点文档 | |
peo-site | 执行一些在生成项目站点之后需要完成的工作 | |
site-deploy | 将生成的项目站点发布到服务器上 |
生命周期详解
从命令行执行Maven任务的最主要方式就是调用Maven的生命周期阶段。
Ps:Maven的三套生命周期是相互独立的,而一个生命周期阶段是有前后依赖关系的。
如:
mvn clean:该命令调用clean生命周期的clean阶段,实际执行clean生命周期中的pre-clean,clean阶段
mvn test:该命令调用default生命周期的test阶段,实际执行default生命周期中vaildate至test所有阶段
mvn clean install:该命令调用clean生命周期的clean阶段以及default生命周期的install阶段,实际执行clean生命周期中的pre-clean,clean阶段,以及default生命周期的vaildate至install所有阶段。
mvn clean deploy site-deploy:该命令调用clean生命周期的clean阶段、default生命周期的deploy阶段以及site生命周期的site-deploy阶段,实际执行实际执行clean生命周期中的pre-clean,clean阶段、default生命周期的所有阶段以及site生命周期的所有阶段。
由于Maven中主要生命周期阶段不多,而常用Maven命令都是基于这些阶段简单组合而成,因此只要对Maven生命周期有基本的理解,就可以正确而熟练地使用Maven命令。
插件目标
Maven的核心仅定义了抽象的生命周期,具体的任务是交由插件完成,插件以独立的构件形式存在。
对于插件本身,为了代码复用,它往往具备多个功能,而每个功能都统称为插件目标(Plugin Goal)。
如:maven-dependency-plugin,基于项目依赖做很多事情。
1 帮助分析项目依赖,帮助找出潜在的无用依赖;
2 列出项目依赖树,帮助分析依赖来源
3 列出项目已解析的依赖 等…
这些任务有很多代码可以复用。因此,这些功能聚集在一个插件中,每个功能就是一个插件目标。
插件目标使用语法:
1 完整命令 mvn groupId:artifactId:version:goal 如 mvn org.apache.maven.plugins:maven-dependency-plugin:3.0.2:tree
2 简化version mvn groupId:artifactId:goal 如 mvn org.apache.maven.plugins:maven-dependency-plugin:tree
3 使用插件前缀 mvn 插件前缀:goal 如 mvn dependency:tree
插件绑定
Maven的生命周期与插件目标相互绑定,用以完成实际的构建任务。
内置绑定
为了使用户几乎不用任何配置就能构建Maven项目,Maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,
当用户通过命令行调用生命周期时,对应的插件目标就会执行相应的任务。
如下图(default生命周期的阶段与插件目标的绑定关系由项目打包类型决定(packaging元素)下图以jar包构建为例)
生命周期 | 阶段(phase) | 内置插件 | 执行任务 |
---|---|---|---|
clean | pre-clean | ||
clean | maven-clean-plugin:clean | 删除项目的输出目录 | |
post-clean | |||
default | vaildate | ||
intianlize | |||
generate-sources | |||
proccess-sources | |||
generate-resoureces | |||
process-resources | maven-resources-plugin:resources | 复制主资源文件至主输出目录 | |
compile | maven-compiler-plugin:compile | 编译主代码至主输出目录 | |
process-classes | |||
generate-test-sources | |||
process-test-sources | |||
generate-test-resources | |||
process-test-resources | maven-resources-plugin:testResources | 复制测试资源文件至测试输出目录 | |
test-compile | maven-compiler-plugin:testCompile | 编译测试代码至测试输出目录 | |
process-test-classes | |||
test | maven-surefire-plugin:test | 执行测试用例 | |
prepare-package | |||
package | maven-jar-plugin:jar | 创建项目jar包 | |
pre-integration-test | |||
integration-test | |||
post-integration-test | |||
verify | |||
install | maven-install-plugin:install | 将项目输出构件安装到本地仓库 | |
deploy | maven-deploy-plugin:deploy | 将项目输出构件安装到远程仓库 | |
site | pre-site | ||
site | maven-site-plugin:site | 生成项目站点 | |
peo-site | |||
site-deploy | maven-site-plugin:deploy | 将项目站点部署到远程服务器上 |
Ps:空白的生命周期阶段,默认没有绑定任何插件,因此也没有任何实际行为
除默认打包类型jar外,常见的打包类型还有war、pom、maven-plugin、ear等。查看相关类型插件绑定官网http://maven.apache.org/guide…
自定义绑定
除了内置绑定以外,用户可以选择将某个插件目标绑定到生命周期的某个阶段,能让Maven项目在构建过程中执行更多更富特色的任务。
如maven-source-plugin:jar-no-fork,能够将项目主代码打成jar文件。将其绑定到default生命周期的verify阶段上,在执行完继承测试后和安装构件之前创建源码jar包。配置如下
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions> <!-- 插件执行配置 -->
<execution> <!-- executions下每个execution子元素可以配置执行一个任务 -->
<id>attach-sources</id> <!-- 配置任务id -->
<!-- <phase>verify</phase> --> <!-- phase元素配置绑定生命周期阶段 -->
<goals> <!-- 配置要执行的插件目标 -->
<goal>jar-no-fork</goal>
</goals>
<execution>
</executions>
</plugin>
</plugins>
</build>
完成自定义插件绑定后,运行mvn verify即可。
在上述代码中,注释了phase元素也能实现绑定,原因是很多插件目标在编写时已经定义了默认绑定阶段,可以使用maven-help-plugin查看插件详细信息,了解插件目标的默认绑定阶段。
mvn help:describe -Dplugin=groupId:artifactId:version
当插件目标绑定到不同的生命周期时,其执行顺序会由生命周期阶段的先后顺序决定。如果多个目标被绑定到同一个阶段,这些插件声明的先后顺序决定目标的执行顺序。
插件配置
完成插件目标与生命周期绑定后,用户还可以配置插件目标的参数,进一步调整插件目标所执行的任务,以满足项目的需求。几乎所有Maven插件目标都有一些可配置参数,可通过命令行和POM配置等方式来配置参数。
1)命令行插件配置
使用-D参数,并伴随一个参数名=参数值的形式,来配置参数
(命令行参数是由插件参数的表达式(Expression)决定,并非所有插件目标参数都有表达式,只能在POM中配置)
如maven-surrfire-plugin插件提供了一个maven.test.skip参数,当其值为true时,就会跳过执行测试。
mvn install -Dmaven.test.skip=true
2)POM中插件全局配置
并非所有插件参数都适合从命令行配置,有些参数的值从项目构建到项目发布都不会改变,或很少改变,在POM文件中一次性配置显然比重复在命令行输入要方便。
如mvan-compiler-plugin 可以配置全局参数 实现compile以及testCompile任务都能使用全局配置。
<build>
<plugins>
<plugin>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<configuration> <!-- 声明插件全局配置 所有基于该插件目标的任务,都会使用这些配置 -->
...
</configuration>
</plugin>
</plugins>
</builds>
3)POM中插件任务配置
除了为插件配置全局的参数,还可以为某个插件任务配置特定的参数。
如maven-antrun-plugin 可以配置插件任务参数,使run目标任务在不同生命周期输出不同的语句。
<build>
<plugins>
<plugin>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<executions>
<execution>
<id>...</id>
<phase>...</phase>
<goals>
<goal>...</goal>
</goals>
<configuration> <!-- 插件任务一配置 -->
...
</configuration>
</execution>
<execution>
<id>...</id>
<phase>...</phase>
<goals>
<goal>...</goal>
</goals>
<configuration> <!-- 插件任务二配置 -->
...
</configuration>
</execution>
...
</executions>
</plugin>
</plugins>
</builds>
获取插件信息
仅仅理解如何配置使用插件是不够的,实现一个构建任务,用户需知道去哪找到合适的插件,并详细了解该插件的配置点。由于Maven的插件非常多,而且这其中大部分没有完善的文档,因此使用正确的插件并进行正确的配置,不容易。
1)在线插件信息
基本上所有的Maven插件都来自于apache和Codehaus。
apache | 说明 | 官方插件,用户多,稳定性好 |
详细列表 | http://maven.apache.org/plugi… | |
下载地址 | http://repo1.maven.org/maven2… | |
Codehaus | 说明 | 文档和可靠性相对较差,遇到问题,往往需要自己看源码 |
详细列表 | http://mojo.codehaus.org/plug… | |
下载地址 | http://repository.codehaus.or… |
虽然并非所有插件都提供了完善的文档,但一些核心插件的文档还是非常丰富的。
一般来说,通过阅读插件在文档中的使用介绍和实例,就应该能够在自己的项目中很好地使用该插件。
当我们需要了解非常细节的目标参数时,就需要进一步访问该插件每个目标的文档。文档详细解释了该参数的作用、类型等信息。
2)使用maven-help-plugin插件
除了访问在线的插件文档之外,还可以借助maven-help-plugin来获取插件的详细信息。
执行maven-help-plugin的describe目标,指定要查询的插件的坐标,可查询插件的坐标,前缀(Goal Prefix),目标信息
mvn help:describe -Dplugin = (groupId:artifactId[:version] | Goal Prefix) -Dgoal = goal -Ddetail
从命令行调用插件
mvn -h 显示mvn命令帮助,可以看到如下信息:
usage:mvn [options] [<goal(s)]>] [<phase(s)>]
Options:
...
options表示可用的选项,除了选项之外,mvn命令后面可以添加一个到多个goal和phase,分别指插件目标和生命周期阶段。mvn命令可以激活生命周期阶段,从而执行那些绑定在生命周期阶段上的插件目标。也可以直接执行插件目标,因为有些插件目标不适用于生命周期阶段,如maven-help-plugin:describe。
直接执行插件目标语法 在上文中已提及,可在插件目标中查看。
插件解析机制
在命令行中执行插件目标,可使用插件前缀替代坐标,方便用户使用和配置插件。
Maven的这一特性是双刃剑,虽然它简化了插件的使用和配置,但如果出现问题,用户很难定位出问题的插件构件。
如mvn help:system 执行了什么插件。它的坐标是什么。这与Maven的插件解析机制有关。
1)插件仓库
与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中,在需要的时候,Maven会先从本地仓库寻找插件,不存在则从远程仓库找到插件,下载到本地仓库使用。
插件的远程仓库不同于依赖的远程仓库,配置方式也不同,插件仓库配置如下:
<pluginRepositories>
<pluginRepository>
... <!-- 此处与依赖远程仓库配置一样,可参考阅读总结二查看 -->
</pluginRepository>
...
</pluginRepositoties>
Maven内置了插件仓库指向中央仓库,并关闭了对SNAPSHOT的支持。
一般来说,中央仓库所包含的插件完全能够满足我们的需要,因此也不需要配置其他的插件仓库,只有在很少的情况下,项目使用的插件无法在中央仓库找到,或者自己编写了插件,可以参考上述配置,在POM中或settings.xml中 加入其他插件仓库。
2)插件的默认groupId
在POM中配置插件时,如果该插件是Maven官方插件(groupId为org.apache.maven.plugins),则可以省略groupId配置。
不推荐使用这一特性,只省略一行配置,但会让团队中不熟悉Maven的成员感到费解。
3)解析插件版本
同样为了简化插件的配置和使用,用户可没有提供插件的版本,Maven会自动解析插件版本。
首先,Maven在超级POM中为所有核心插件设定了版本,超级POM是所有Maven项目的父POM,所有项目都继承了这个超级POM配置。所以用户使用插件未设定插件版本的情况有以下几种:
1 核心插件:通过超级POM继承设定版本
2 非核心插件:通过仓库元数据 groupId/artifactId/maven-metadata.xml ,遍历并归并本地仓库和远程仓库的仓库元数据,根据latest和release计算出插件的版本。Maven3之后使用release,避免使用latest获取到快照版本,因为快照版本的频繁更新会导致插件行为的不稳定。
4)解析插件前缀
mvn命令行支持使用插件前缀来简化插件的调用。Maven是如何通过插件前缀获取插件的坐标的?
插件前缀与groupId:artifactId是一一对应的,这种匹配关系存储在仓库元数据中,该仓库员数据位于groupId/maven-metadate.xml,Maven在解析插件仓库元数据时,会默认使用org.apache.maven.plugins和org.codehaus.mojo两个groupId,可以再settings.xml中配置其他groupId
<settings>
...
<pluginGroups>
<pluginGroup>...</pluginGroup>
</pluginGroups>
...
<settings>
插件仓库元数据中存储了所有插件前缀与group:artifactId的对应关系
插件仓库元数据检查顺序为:apache -> codehaus -> 用户自定义插件组 -> 都不包含该前缀,则报错