Maven是一个项目构建工具,也可以管理项目的依赖。maven将构建项目的过程分为了三个独立的生命周期,每个生命周期都有一系列的阶段,每个阶段都需要和maven的插件绑定才能执行。从这个角度来说,maven又是一个插件执行框架,它的功能都是通过插件来完成的。
Maven有三个内置的构建生命周期(build lifecycle),它们彼此独立,分别是:clean
、default
、site
。clean生命周期主要负责项目的清理工作,default生命周期主要负责项目的部署工作,site生命周期主要负责创建项目的web站点。我们平常用的maven命令mvn clean package
中的clean属于clean生命周期,package属于default生命周期。
每个生命周期都有一些列不同的构建阶段(build phase,也叫build stage)组成。生命周期内的这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。比如clean生命周期有pre-clean
、clean
、post-clean
三个阶段,当我们执行mvn clean
的时候其实相当于执行了mvn pre-clean clean
,clean前面的阶段都会被执行。
生命周期及其包含的阶段如下:
Clean生命周期:
阶段(phase) | 说明 |
---|---|
pre-clean | 执行清理前的预处理工作 |
clean | 清理之前构建的结果 |
post-clean | 执行清理后需要做的工作 |
default生命周期:
阶段(phase) | 说明 |
---|---|
validate | 验证项目有效性还有所有必要的信息是否可以获取到 |
initialize | 初始化构建状态,比如设置一些属性或者创建一些目录 |
generate-sources | 生成一些源码,这些源码在编译的时候可能会用到 |
process-sources | 处理源码,比如过滤一些值 |
generate-resources | 生成一些资源,在打包的时候可能会包含这些资源 |
process-resources | 拷贝或者处理资源,将它们移动到目标目录,用于打包 |
compile | 编译项目的源码 |
process-classes | 后置处理编译阶段生成的文件,比如对Java类进行字节码增强 |
generate-test-sources | 生成一些测试源码,在编译的时候可能会用到 |
process-test-sources | 处理测试源码,比如过滤一些值 |
generate-test-resources | 创建测试需要用的资源 |
process-test-resources | 拷贝或者处理资源,将它们移动到测试用的目录 |
test-compile | 编译测试源码,结果输出到目标目录 |
process-test-classes | 后置处理测试阶段编译产生的文件,比如对Java类进行字节码增强 |
test | 使用合适的单元测试框架进行测试,这些测试不应该依赖被打包或部署的代码 |
prepare-package | 在真正打包前做一些必要的准备操作,这个操作会产生未打包的,处理过的package版本 |
package | 将编译后的代码打包成可分发的格式,比如jar包 |
pre-integration-test | 执行集成测试之前的准备工作,比如设置一些需要的环境信息 |
integration-test | 如果有必要,会处理和部署包到继承测试可以运行的环境中 |
post-integration-test | 集成测试后的后置处理,比如清理集成测试用的环境 |
verify | 运行检测任务,以检查包是否合法,是否符合质量要求 |
install | 将包安装到本地仓库,作为依赖提供给本地的其它项目用 |
deploy | 将包发送到远程仓库,其它的开发者和项目可以使用它作为依赖 |
site生命周期:
阶段(phase) | 说明 |
---|---|
pre-site | 执行生成项目站点前的准备工作 |
site | 生成项目站点文档 |
post-site | 执行操作用于完成站点文档生成,为站点部署做准备 |
site-deploy | 部署站点文档到指定的web服务器 |
生命周期中的阶段,类似java的模版方法,它是抽象的,需要由插件去实现它。执行maven命令的时候都是由各个生命周期阶段的插件来完成具体的工作的。比如default生命周期的package阶段可能就是由maven-jar-plugin
完成的,之所以说是可能,是因为maven会给一些阶段绑定默认的插件实现,我们也可以通过提供别的插件来改变阶段的执行。
插件除了和生命周期的阶段绑定执行,也可以单独执行,比如mvn dependency:tree
可以直接执行dependency插件的tree目标。即生命周期离不开插件,但是插件却可以离开生命周期单独执行。以compiler插件的compile目标为例:
pilaf@pilafdeMacBook-Pro spring-101 % mvn compiler:compile
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< org.example:spring-101 >-----------------------
[INFO] Building spring-101 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-cli) @ spring-101 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 13 source files to /Users/pilaf/IdeaProjects/spring-101/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.662 s
[INFO] Finished at: 2022-08-30T12:01:41+08:00
[INFO] ------------------------------------------------------------------------
maven插件有build类型和reporting类型插件两类,分别在pom.xml的
和
部分指定。我们平时主要用的都是build类型插件。
自己开发的maven插件名字需要遵循
。maven官方的插件名字遵循maven-
的格式。
为了实现代码复用,一个maven插件可能有多个功能,比如maven-dependency-plugin
,它可以完成的功能有:
dependencies
和dependencyManagement
标签中重复的声明上面只是列出了几个功能,更详细的部分可以在maven官网插件部分找到。
这些插件可以完成的独立的功能,就是插件的目标
(goal)。插件及其目标可以写成下面的方式:
dependency:analyze
、dependency:tree
,中间用冒号分开,这其实是对maven-dependency-plugin:analyze
和maven-dependency-plugin:tree
的简写。
前面说过,我们可以直接调用生命周期中的某个阶段,比如mvn clean
,我们也可以直接调用插件的目标,比如mvn dependency:tree
就是调用了dependency
插件的tree
目标。
为了便于理解插件和目标的简写,我们假设自己开发了一个插件,叫做hello-maven-plugin
,它有个goal是sayhi,它的groupId是sample.plugin
,artifcatId是hello-maven-plugin
,version是1.0-SNAPSHOT
。为了在项目中使用这个插件,需要在项目的pom.xml中有如下配置:
<build>
<plugins>
<plugin>
<groupId>sample.plugingroupId>
<artifactId>hello-maven-pluginartifactId>
<version>1.0-SNAPSHOTversion>
plugin>
plugins>
build>
我们在命令行中执行插件的完整命令是mvn groupId:artifactId:version:goal
,对于我们的hello-maven-plugin,就是mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi
。
如果我们默认用最新版本的插件,就不需要指定版本,命令可以简写为mvn sample.plugin:hello-maven-plugin:sayhi
。
如果我们的插件命名遵循了规范,命令可以简写为mvn sample.plugin:hello:sayhi
。
如果我们的插件和别的插件的groupId不冲突的话,可以将groupId省略掉(对于maven官方插件来说,它的groupId是org.apache.maven.plugins
),命令可以进一步简写为mvn hello:sayhi
。
mvn hello:sayhi
和mvn dependency:tree
是不是似曾相识了?
maven的生命周期的阶段需要和插件绑定,才能完成构建任务。比如default生命周期的compile这个阶段,可以通过maven-compiler-plugin
的compile
目标完成。
一些生命周期阶段和插件默认绑定的关系:
也可以通过在pom.xml中自定义插件和生命周期的阶段的绑定关系,比如:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-source-pluginartifactId>
<version>2.1.1version>
<executions>
<execution>
<id>attach-sourcesid>
<phase>verifyphase>
<goals>
<goal>jar-no-forkgoal>
goals>
execution>
executions>
plugin>
plugins>
build>
将source插件的jar-no-fork目标绑定到default生命周期的verify阶段执行。
在开发自定义maven插件的时候,可以通过defaultPhase=LifecyclePhase.XX
来指定插件绑定到哪个生命周期阶段执行。
一些说明:
mvn clean
,应该也会执行pre-clean
阶段,但是如果没有任何插件目标绑定到了这个阶段,它什么都不会执行。maven插件执行的时候,有的需要用户指定一些参数,不同的插件的参数名字是不一样的,比如开发有一个目标名字叫"query"的插件:
@Mojo( name = "query" )
public class MyQueryMojo extends AbstractMojo
{
@Parameter(property = "query.url", required = true)
private String url;
@Parameter(property = "timeout", required = false, defaultValue = "50")
private int timeout;
@Parameter(property = "options")
private String[] options;
public void execute()
throws MojoExecutionException
{
...
}
}
为了在插件执行的时候,传入"query.url"参数,你可以直接在pom.xml中通过
标签配置:
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-myquery-pluginartifactId>
<version>1.0version>
<configuration>
<url>http://www.foobar.com/queryurl>
<timeout>10timeout>
<options>
<option>oneoption>
<option>twooption>
<option>threeoption>
options>
configuration>
plugin>
plugins>
build>
...
project>
也可以在执行mvn命令的时候通过命令行传入:
mvn myquery:query -Dquery.url=http://maven.apache.org
这是不是和我们平时想要跳过测试时输入的似曾相识:
mvn clean package -Dmaven.test.skip=true
比如:
pilaf@pilafdeMacBook-Pro spring-101 % mvn clean package -Dmaven.test.skip=true
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< org.example:spring-101 >-----------------------
[INFO] Building spring-101 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ spring-101 ---
[INFO] Deleting /Users/pilaf/IdeaProjects/spring-101/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ spring-101 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ spring-101 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 13 source files to /Users/pilaf/IdeaProjects/spring-101/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ spring-101 ---
[INFO] Not copying test resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ spring-101 ---
[INFO] Not compiling test sources
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ spring-101 ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ spring-101 ---
[INFO] Building jar: /Users/pilaf/IdeaProjects/spring-101/target/spring-101-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.830 s
[INFO] Finished at: 2022-08-30T12:19:14+08:00
[INFO] ------------------------------------------------------------------------
可以看到"maven.skip.test"参数被default生命周期test阶段的maven-surefire-plugin插件接收到了,由于配置的是true,所以它跳过测试,输出了"Tests are skipped"。
参考资料:
1.maven官网
2.许晓斌《Maven实战》