生命周期
- 每个生命周期的各个环节都是由各种插件完成!!!
- Maven有三个相互独立的生命周期(Maven的这三个生命周期不能看成一个整体)!!!
我们在开发中描述的项目的生命周期,一般是指的是 编译、测试、打包、部署``等过程,每个项目的构建生命周期或多或少都有一些差异,Maven 对构建的过程进行的抽象和统一,提出了 Maven生命周期这一个抽象的概念,它的作用是定义一条执行流程,而不会完成实际的工作,在每个流程节点中的工作都会交给具体实例对象去完成。
这个执行的流程中包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署、站点生成
等步骤,几乎覆盖了项目构建中的所有流程节点,我们可以按需选择其中的一部分步骤生成自己的项目包。
Maven 对流程、流程节点、具体工作都有专用的名词,如下:
流程:生命周期(lifeCycle)
流程节点:阶段 (phase)
具体工作:插件目标 (plugin:goal)
Maven 的生命周期有三个,分别是clean、default、site
,其中 site 使用的较少,另外两个都是经常会使用到的。这三个生命周期彼此是隔离开的个体,它们可以单独运行,也可以组合起来运行,例如:
# 单独清理
mvn clean
# 单独构建
mvn package
mvn install
mvn deploy
# 清理并构建
mvn clean install
仔细看上面的构建指令:mvn install
,而不是 mvn default
,这是因为在 mvn 指令后面接的是生命周期中的某个阶段,接下来就详细解释下阶段的含义。
生命周期的阶段
每个生命周期是由一个一个阶段组成的,我们先从一张官网的截图来感性的认知一下阶段:
上图就是 Maven 生命周期中的完整的阶段,第一次看到这个图的话,可能会看的有点眼花缭乱,但是没有关系,我们暂时先不管这些阶段在做什么,先对这些步骤做一下拆解。
在运行的时候会按照上图中从上到下的顺序运行,直到运行到指定的阶段为止,例如上面的mvn clean install
指令,会按下面的步骤运行:
找到clean
生命周期,从pre-clean
开始往下执行,直到clean
阶段执行完成。
找到default
生命周期,从validate
开始往下执行,直到install
阶段执行完成。
这里的没有指定 site
直接忽略,clean
和 default
大概会执行20多个阶段,看起来还是很多,但实际上这20多个阶段中有很大一部分是不会运行的,这涉及到一个特性:没有绑定插件目标的阶段,在生命周期中不会被调用(插件目标的概念可以看下面的标题)。
If a build phase has no goals bound to it, that build phase will not execute.
这就好办了,我们只需要关注绑定了插件目标的阶段就好了,Maven 针对不同的打包格式,提供了不同的默认插件目标绑定规则,以打包成jar来举例:
上图就是我们日常工作中常用的阶段,相比上面那张图就简单多了,接下来一一解释一下这些阶段的含义:
- clean:默认是清除target目录中的所有文件,避免将历史版本打到新的包中造成一些不在预期中的问题。
- process-resources:将资源文件src/main/resources下的文件复制到target/classes目录中。
- compile:将src/main/java下的代码编译成 class 文件,也放到target/classes目录中。
- process-test-resources:将资源文件src/test/resources下的文件复制到target/test-classes目录中。
- test-compile:将src/test/java下的代码编译成 class 文件,也放到target/test-classes目录中。
- test:运行单元测试并在target/surefire-reports中生成测试报告。
- package:将资源文件、class文件、pom文件打包成一个jar包。
- install:将生成的jar包推送到本地仓库中。
- deploy:将生成的jar包推送到远程仓库中。
现在我们执行一次deploy指令,看看控制台中打印出来的结果,对比一下是不是这些插件目标,篇幅问题,这里只放demo-a部分的结果,用红色字体标出的插件目标:
[INFO] — maven-clean-plugin:3.1.0:clean (default-clean) @ demo-a —
[INFO] Deleting E:\workspace\code\dependency-demo\demo-a\target
[INFO]
[INFO] — maven-resources-plugin:3.2.0:resources (default-resources) @ demo-a —
[INFO] Using ‘UTF-8’ encoding to copy filtered resources.
[INFO] Using ‘UTF-8’ encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] — maven-compiler-plugin:3.8.1:compile (default-compile) @ demo-a —
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to E:\workspace\code\dependency-demo\demo-a\target\classes
[INFO]
[INFO] — maven-resources-plugin:3.2.0:testResources (default-testResources) @ demo-a —
[INFO] Using ‘UTF-8’ encoding to copy filtered resources.
[INFO] Using ‘UTF-8’ encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO]
[INFO] — maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ demo-a —
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to E:\workspace\code\dependency-demo\demo-a\target\test-classes
[INFO]
[INFO] — maven-surefire-plugin:2.22.2:test (default-test) @ demo-a —
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.ls.maven.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 s - in com.ls.maven.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] — maven-jar-plugin:3.2.2:jar (default-jar) @ demo-a —
[INFO] Building jar: E:\workspace\code\dependency-demo\demo-a\target\demo-a-1.0.3.jar
[INFO]
[INFO] — maven-install-plugin:2.5.2:install (default-install) @ demo-a —
[INFO] Installing E:\workspace\code\dependency-demo\demo-a\target\demo-a-1.0.3.jar to E:\workspace\repository\com\ls\maven\demo-a\1.0.3\demo-a-1.0.3.jar
[INFO] Installing E:\workspace\code\dependency-demo\demo-a\pom.xml to E:\workspace\repository\com\ls\maven\demo-a\1.0.3\demo-a-1.0.3.pom
[INFO]
[INFO] — maven-deploy-plugin:2.8.2:deploy (default-deploy) @ demo-a —
Uploading to nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/1.0.3/demo-a-1.0.3.jar
Uploaded to nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/1.0.3/demo-a-1.0.3.jar (2.4 kB at 82 kB/s)
Uploading to nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/1.0.3/demo-a-1.0.3.pom
Uploaded to nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/1.0.3/demo-a-1.0.3.pom (876 B at 26 kB/s)
Downloading from nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/maven-metadata.xml
Downloaded from nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/maven-metadata.xml (298 B at 17 kB/s)
Uploading to nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/maven-metadata.xml
Uploaded to nexus: http://192.168.200.101:8081/repository/maven-releases/com/ls/maven/demo-a/maven-metadata.xml (329 B at 15 kB/s)
注:运行deploy指令需要先配置私服,可以参考 (三)Maven仓库概念及私服安装与使用 附:Nexus安装包下载地址,将下面的配置改成成自己的私服地址。
<distributionManagement>
<repository>
<id>nexus</id>
<url>http://192.168.200.101:8081/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus</id>
<url>http://192.168.200.101:8081/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
插件目标
如前文所述,Maven的生命周期是抽象的,各阶段的工作实际是通过Maven插件去执行完成的。在一个Maven插件中,其可以具备多个功能,通常我们称插件功能为Goal目标。以 maven-dependency-plugin 插件为例进行说明,我们已知的功能有:查看依赖列表、以树形结构图查看依赖、分析依赖。相应地,其插件目标分别为dependency:list、dependency:tree、dependency:analyze
。在插件目标的写法中,冒号前为该插件的插件前缀,冒号后为该插件的目标。两者组成了一个坐标,分别绑定到了list、tree、analyse这些阶段上,当运行到这些阶段的时候,就会通过这些个坐标找到对应编译功能的代码并运行。例如:compiler:compile是属于 maven-compiler-plugin 里面的一个功能,compiler 是目标前缀(goalPrefix),compile就是目标(goal)。两者组成了一个坐标,绑定到了compile这个阶段上,当运行到这个阶段的时候,就会通过这个坐标找到对应编译功能的代码并运行。
跳过测试插件目标
我们每次执行构建时,到走到生命周期中的test阶段,就会把项目中的单元测试全都运行一遍,单元测试较多的时候运行会比较耗时,在项目没有强制要求不需自动运行单元测试的情况下,我们可以通过下面的指令跳过测试阶段。
mvn clean
install
-D maven.test.skip=true
……
[INFO] — maven-resources-plugin:3.2.0:testResources (default-testResources) @ demo-a —
[INFO] Not copying test resources
[INFO]
[INFO] — maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ demo-a —
[INFO] Not compiling test sources
[INFO]
[INFO] — maven-surefire-plugin:2.22.2:test (default-test) @ demo-a —
[INFO] Tests are skipped.
……
上面执行结果中,用红色标记的插件目标执行结果发生了变化,不再复制src/test下面的资源文件、也不再编译src/test目录下的代码,并且直接跳过了测试阶段。
绑定插件
将Maven生命周期与Maven插件相互进行绑定,即可完成实际的构建任务。具体地,是将生命周期的阶段与插件的插件目标相互绑定,以完成某个具体的构建任务。以项目编译为例,将default生命周期的compile阶段与maven-compiler-plugin插件的compile目标绑定在一起后,即可实现项目编译
1. 内置绑定
由于Maven的生命周期需要绑定插件后才可以产生实际作用。为最大程度降低用户的配置、使用难度,Maven对各生命周期的主要阶段绑定了默认插件目标。基于此用户无需手动绑定,直接在命令行中调用Maven的相关生命周期阶段即可执行相应的构建任务。Maven对各生命周期的内置绑定如下,由于default生命周期阶段内置绑定的插件目标与最终打包的类型有关,这里以常见的打包类型jar包为例进行介绍
2. 自定义绑定
Maven内置绑定的插件可供用户实现基础的项目构建任务,而如果用户需要完成其他的构建任务时,可通过自定义绑定的方式将某个插件目标绑定到生命周期的某个阶段上
。这里我们以创建项目的源码jar包举例说明,由于内置绑定的插件目标没有可以完成该任务的,所以我们需要先确定可以完成该任务的Maven插件及插件目标,然后将其绑定到生命周期的某一阶段上
插件 maven-source-plugin 的 jar-no-fork 目标能够将项目的主代码打包为jar包。在项目POM文件build元素的子元素plugins中可包含若干个plugin元素,其可用于向项目中引入Maven的插件。Maven插件和Maven依赖一样也是基于Maven进行管理的,故其同样需要配置groupId、artifactId、version元素信息。executions元素下可包含若干个execution子元素,用于配置执行任务。这里我们配置一个id为attach-sources的任务,将该插件的目标 jar-no-fork 与default生命周期的package阶段进行绑定。实例代码如下所示
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
至此,创建项目的源码jar包的任务即可通过执行下述命令实现
mvn package
构建后生成的jar源码包如图中绿框所示。当多个插件目标被绑定到生命周期的同一个阶段时,其执行顺序将由其插件目标的声明顺序决定。本例中package阶段绑定了2个任务,一个是内置绑定的打包任务,一个是自定义绑定的jar源码包任务,其执行顺序如下图红框所示
命令行调用插件
在命令行中执行mvn -h可查看mvn命令的格式。其中phase参数指生命周期中的阶段,我们可以在命令行中使用阶段参数来执行绑定在相应阶段上的插件目标(e.g., mvn clean package),该用法已在上文介绍过。此处将不再多言。这里我们着重介绍goal参数——插件目标,即我们可以在命令行直接调用插件目标,因为不是所有插件目标都适合绑定到生命周期的阶段上,例如上文介绍的maven-dependency-plugin插件的dependency:list、dependency:tree、dependency:analyze等目标
mvn [options] [<goal(s)>] [<phase(s)>]
插件前缀
上文简要介绍了可以通过mvn命令直接调用插件目标,语法如下所示
# 通过Maven坐标调用插件
mvn [groupId]:[artifactId]:[version]:[goalName]
mvn org.apache.maven.plugins:maven-dependency-plugin:2.1:list
上述语法的弊端在于命令过长不够简洁。为便于使用,Maven对插件引入了插件前缀的概念,用来代替groupId、artifactId信息。所以一个Maven插件不仅可通过Maven坐标(groupId、artifactId)还可以通过插件前缀进行描述。使用插件前缀可大大简化命令,语法如下所示。例如 maven-dependency-plugin插件的插件前缀是dependency,则查看依赖列表,可使用下述命令实现
# 通过插件前缀调用插件
mvn [pluginPrefix]:[goalName]
mvn dependency:list
获取插件信息
日常开发中,我们经常需要了解一个插件的帮助信息,这个时候就可以通过maven-help-plugin插件的describe目标来实现。常见用法如下。其中,plugin参数用于指定欲查询的插件(Maven坐标、插件前缀均可),goal参数用于指定该插件的目标,detail参数可输出更详细的信息
# 查看指定插件的信息
mvn help:describe -Dplugin=[groupId]:[artifactId]:[version] [-Ddetail]
mvn help:describe -Dplugin=[pluginPrefix] [-Ddetail]
# 查看指定插件的某个目标的信息
mvn help:describe -Dplugin=[pluginPrefix] -Dgoal=[goalName] [-Ddetail]
这里以我们在上文自定义绑定的 maven-source-plugin 插件为例,来获取该插件的帮助信息。结果如下所示,蓝框所示的为该插件的基本信息,包含描述、Maven坐标、插件前缀(source)等信息。下面则列出了该插件的所有的目标。其中黄框所示的插件目标source:jar-no-fork,即为上文提到的用于创建项目的源码jar包
使用detail参数会显示该插件每个目标的详细信息,致使输出的信息非常多。而很多时候,我们只需要我们关注的目标的详细信息即可。这时即可通过goal参数指定插件的目标,这里我们同样以 maven-source-plugin 插件为例,查看目标jar-no-fork的详细信息。结果如下所示,黄框为插件目标。还记得我们之前在配置自定义绑定时,是通过设置execution元素下的phase元素来绑定到生命周期的阶段上的。如果用户未配置phase元素,将会使用蓝框所示的默认值进行绑定
Note:
- 在Java中,参数-D用于设置Java系统属性。这里Maven借用了这一参数,所以可以看到Maven命令中大部分参数前都有-D标识
插件配置
命令行插件配置
一般情况下插件的目标中存在若干个参数用于改变插件的行为,用户在命令行中可通过参数 Parameters的用户属性 User Property进行配置。这里以default生命周期的test阶段默认绑定的 maven-surefire-plugin 插件的test为例进行说明
执行下述命令查看插件目标 surefire:test 的详细信息
mvn help:describe -Dplugin=surefire -Dgoal=test -Ddetail
从上图红框中可以看出test目标下有参数skip、skipTests。根据绿框中的描述信息可知,当前者为true时,该目标执行时不会编译测试代码也不会执行单元测试用例;当后者为true时,该目标执行时虽然不会执行单元测试用例但其会对测试代码进行编译。蓝框为该参数的用户属性,可在命令行设置用户属性来改变插件的行为。例如我们在构建项目时,有时候为了节约项目构建时间,希望跳过单元测试,即不运行单元测试用例,即可通过在命令后添加相关属性的配置即可,如下所示
POM中插件的全局配置
上面我们介绍了如何在命令行配置插件来改变插件行为,而更多的时候我们需要在项目的POM文件中配置插件。一方面是因为在命令行中配置插件实际上是通过插件目标参数的用户属性实现的,但是有些插件参数并没有用户属性故无法在命令行中进行配置;另一方面,插件常用的配置写在POM中总比命令行一次一次敲来的省事的多。具体地,可在POM文件中的plugin元素的子元素configuration中配置插件参数 Parameters。由于该参数是直接配置到插件中并未指定具体的插件目标,所以其是一个全局配置会对该插件下的所有目标生效
下面即是一个对 maven-compiler-plugin 插件的全局配置示例,在POM中配置该插件中的参数source、tagret值为1.8——使用Java 1.8版本的编译器对源码进行编译、生成与JVM 1.8版本兼容的class文件,则该POM中的配置对于插件中存在相关参数的插件目标compiler:compile、compiler:testCompile将均生效
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
...
</plugins>
...
</build>
POM中插件的任务配置
在POM中配置插件参数,不仅支持全局配置,还可以基于任务来配置插件参数,实现在不同任务中使用不同的插件配置。虽然这里也是在POM文件的configuration元素配置插件参数,但是需要注意的是这里configuration元素是位于用于配置任务的execution元素下的。下面即是一个对 maven-compiler-plugin 插件 testCompile 目标的配置示例
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<id>compile-test-code</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
SpringBoot打包插件
SpringBoot的jar
包与普通的包对比起来主要有两点差别:
能够通过java -jar
启动
jar
包里面除了项目中本身的文件以外,还把3方jar包也一起打包了
Maven 自带的maven-jar-plugin
插件不具备这种能力,于是需要引入SpringBoot的打包工具。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
运行mvn clean package
查看运行结果,可以看到执行了repackage这个插件目标:
这里有个疑问,为什么这个插件不需要指定<version>
以及<executions>
呢?
这是因为在pom.xml
中继承了spring-boot-starter-parent
,在这个pom中定义了<pluginManagement>
,所以可以直接使用,就类似于<dependencyManagement>
。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
再看一下父级pom中的定义:
这里没有指定phase
,插件目标是怎么绑定到package
阶段的呢?
这是因为在编写插件的时候,在插件的配置文件中就已经指定了repackage这个插件目标绑定在package这个阶段上了,在spring-boot-maven-plugin-2.6.7.jar
这个包中,我们可以找到一个plugin.xml
的配置文件,打开后可以查看到如下的内容: