一、Maven 构建生命周期
1.1 简介
项目构建的生命周期概念在Maven之前就已经存在了。软件开发人员每天都要对项目进行清理、编译、测试、打包以及安装部署。
虽然每个软件开发人员都做相关的事情,但公司和公司之间、项目和项目之间,往往目的一样,而实现的形式各种各样。有的项目基于 IDE 工具完成编译、打包和发布,比如 MyEclipse 和 Eclipse Java EE;有些是软件开发人员自己编写脚本,对项目进行自定义构件,比如 ant 脚本(当然,ant 脚本本身也是各写各的,都不一样)。
这些都是具有个性化和针对性的,到下一个项目后,又需要改造成新项目所需要的形式。因此就产生了一个问题:感觉是一样的,又不能重用,所以必须重写。
通过学习、分析、反思和总结以前工作中对项目的构建过程,Maven 抽象出了一个适合于所有项目的构建生命周期,并将它们统一规范。
Maven 构建生命周期定义了一个项目构建和发布的过程。
一个典型的 Maven 构建(build)生命周期是由以下几个阶段的序列组成的。所有项目的管理构建过程都可以对应到这个生命周期上来。
阶段 | 处理 | 描述 |
---|---|---|
验证 validate | 验证项目 | 验证项目是否正确且所有必须信息是可用的 |
编译 compile | 执行编译 | 源代码编译在此阶段完成 |
测试 test | 测试 | 使用适当的单元测试框架执行测试 |
包装 package | 打包 | 创建JAR/WAR包如在 pom.xml 中定义提及的包 |
检查 verify | 检查 | 对集成测试的结果进行检查,以保证质量达标 |
安装 install | 安装 | 安装打包好项目到本地仓库, 以供其他项目使用。 |
发布 deploy | 发布 | 拷贝最终打包好的工程包到远程仓库,以共享给其他项目和开发人员 |
需要注意的是,Maven 中项目的构建生命周期只是 Maven 根据实际情况抽象提炼出来的一个统一标准和规范,是不能做具体事情的。也就是说,Maven 没有提供一个编译器能在编译阶段编译源代码。
所以 Maven 只是规定了生命周期的各个阶段和步骤,具体事情,由集成到 Maven 中的插件完成。比如前面介绍的生成站点,就是由 maven-site-plugin 插件完成的。
Maven 在生命周期的每个阶段都设计了插件接口。用户可以在接口上根据项目的实际需要绑定第三方的插件,做该阶段应该完成的任务,从而保证所有 Maven 项目构建过程的标准化。当然,Maven 对大多数构建阶段绑定了默认的插件,通过这样的默认绑定,又简化和稳定了实际项目的构建。
1.2 生命周期
Maven中存在三种生命周期:clean、default、site,分别用于清理项目、构建项目、生成项目站点,而在一个生命周期中通常又会包含若干个阶段。
1. clean(项目清理)各个阶段
-
pre-clean
:执行清理前的工作 -
clean
:清理上一次构建生成的所有文件 -
post-clean
:执行清理后的文件
mvn clean 中的clean就是上面的clean,在一个生命周期中,运行某个阶段的时候,它之前的所有阶段都会被运行,也就是说,mvn clean 等同于 mvn pre-clean clean ,如果我们运行 mvn post-clean ,那么 pre-clean,clean 都会被运行。这是Maven很重要的一个规则,可以大大简化命令行的输入。
2. site(项目站点生成) 各个阶段
-
pre-site
执行一些需要在生成站点文档之前完成的工作 -
site
生成项目的站点文档 -
post-site
执行一些需要在生成站点文档之后完成的工作,并且为部署做准备 -
site-deploy
将生成的站点文档部署到特定的服务器上
site阶段和site-deploy阶段,用以生成和发布Maven站点,这可是Maven相当强大的功能,Manager比较喜欢,文档及统计数据自动生成,很好看。
3. default(项目构建) 各个阶段
-
validate
(校验): 验证项目是否正确以及是否有所有必要的信息。 -
initialize
(初始化): 初始化构建工作, 比如设置环境变量或创建文件目录。 -
generate-sources
(生成源代码): 产生在编译过程中需要的源代码。 -
process-sources
(处理源代码): 处理源代码, 比如过滤值。 -
generate-resources
(生成资源文件): 产生主代码中的资源在 classpath 中的包。 -
process-resources
(处理资源文件): 复制并处理资源文件至目标目录,准备打包。 -
compile
(编译): 编译项目的源代码。 -
process-classes
(处理类文件): 处理编译生成的文件,比如说对Java class文件做字节码改善优化。 -
generate-test-sources
(生成测试源代码): 生成包含在编译阶段中的任何测试源代码。 -
process-test-sources
(处理测试源代码): 处理测试源代码,比如说,过滤任意值。 -
generate-test-resources
(生成测试资源文件): 为测试创建资源文件。 -
process-test-resources
(处理测试资源文件): 复制并处理资源文件,至目标测试目录。 -
test-compile
(编译测试源代码): 编译测试源代码 目标测试目录。 -
process-test-classes
(处理测试类文件): 处理测试源码编译生成的文件, 比如对Java class文件做字节码改善优化。 -
test(单元测试)
: 使用合适的单元测试框架运行测试。这些测试代码不会被打包或部署。 -
prepare-package
(打包前): 在实际打包之前,执行任何的必要的操作为打包做准备。 -
package
(打包): 将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件。 -
pre-integration-test
(集成测试): 在执行集成测试前进行必要的动作。比如说,搭建需要的环境。 -
integration-test
(集成测试): 处理和部署项目到可以运行集成测试环境中。 -
post-integration-test
(集成测试后): 在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。 -
verify
(验证): 运行任意的检查来验证项目包有效且达到质量标准。 -
install
(安装): 安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。 -
deploy
(部署): 将最终的包复制到远程的仓库,以让其它开发人员与项目共享。
1.3 生命周期调用
一个生命周期中的各个阶段是有先后顺序的,后一个阶段任务的执行依赖于前一个阶段任务已经完成。故当用户通过命令行执行某阶段的任务,Maven会自动地把相应的生命周期中的前置阶段任务自动依次执行完成。而默认的三种生命周期之间是相互独立的,执行某一个生命周期的阶段任务时,不会对其他生命周期产生任何影响。
用法及说明如下:
mvn phaseName1 [phaseName2]
mvn pre-clean # 只执行 clean 生命周期中的 pre-clean 阶段
mvn clean # 依次执行 clean 生命周期中的 pre-clean,clean 阶段
mvn compile # 依次执行 default 生命周期中的 validate,initialize,...,process-resources,compile 阶段
mvn clean compile # 依次执行 clean 生命周期中的 pre-clean,clean 阶段, default 生命周期中的 validate,initialize,...,process-resources,compile 阶段
mvn clean site # 依次执行 clean 生命周期中的
mvn test # 该命令调用 default 生命周期中的 test 阶段。实际执行的阶段包括 validate、initialize、generate-sources…compile…test-compile、process-test-classes、test,也就是把 default 生命周期中从开始到 test 的所有阶段都执行完了,而且是按顺序执行。
mvn clean install:该命令调用 clean 生命周期的 clean 阶段和 default 生命周期的 install 阶段。实际执行的是 clean 生命周期中的 pre-clean、clean 两个阶段和 default 生命周期中从开始的 validate 到 install 的所有阶段。
mvn clean deploy site-deploy # 该命令调用 clean 生命周期中的 pre-clean、clean 阶段,default 生命周期中从 validate 到 deploy 的所有阶段,以及 site 生命周期中的 pre-site、site、post-site 和 site-deploy 阶段。
1.4 生命周期绑定
Maven 的每个生命周期阶段都可以绑定一个或多个插件,当运行当前生命周期阶段时,Maven 就会执行该生命周期阶段绑定的所有插件。但严格来说,Maven 的生命周期绑定的其实并不是插件,而是插件的目标(Goal)。
插件目标
一个插件会有一个或多个功能, 而这里的每一个功能就对应一个插件目标。比如maven-dependency-plugin
插件有十多个目标,每个目标对应一个功能。 比如dependency:list
、dependency:tree
和dependency:analyze
这些都是插件目标,这是一种通用的写法,冒号前面是插件前缀,冒号后面是该插件的目标。
它将插件和 Maven 的生命周期彻底解耦了,每个插件可以有多个目标,每个目标绑定一个生命周期阶段,反过来,每个生命周期阶段也可以绑定多个插件目标,这些插件目标可以属于不同的插件。例如下图:
image.png
该图显示有两个 Maven 插件,其中 plugin1 有一个目标 goal,绑定了 clean 生命周期的 clean 阶段,plugin2 有两个插件 goal1 和 goal2,分别绑定了 clean 生命周期的 clean 阶段和 default 生命周期的 package 阶段。通过这个图我们可以看出,插件本身和生命周期阶段是完全解耦的,只有当具体目标绑定了该阶段,该阶段才会有执行实体。
当然,Maven 也为大多数生命周期阶段提供了默认插件,如为 compile 阶段绑定了 maven-compile-plugin:compile 目标,针对 test 阶段提供了 maven-surefire-plugin:test 等
我们可以通过 Maven 命令指定运行某个阶段,而运行某个阶段等价于运行该阶段绑定的所有插件目标,所以很自然地,我们应当也可以直接运行某个插件模板,Maven 执行具体插件目标的格式如下:mvn plugin-prefix:goal。: 左边是插件前缀而非插件名,具体规律是:一般插件的命名为 maven-xxx-plugin,而这里的插件前缀就是 xxx。比如执行 maven-compile-plugin:compile 时的命令为 mvn compile:compile,而执行 maven-surefire-plugin:test 的命令为 mvn surefire:test。
默认绑定
为了让Maven开箱即用,Maven开发了很多默认的插件来完成每个生命周期对于阶段的一些工作,同时,也将这些生命周期的一些主要的阶段和这些默认插件的插件目标进行了绑定,这就是内置绑定。参考:Maven 生命周期
自定义绑定
为了能补充内置绑定的不足,完成更多个性化的任务,Maven社区的大牛开发了很多的插件,当然了,我们自己也可以开发,后面会讲到的。那这些插件如何和Maven的生命周期的阶段进行绑定呢,这就是我们要说的自定义绑定。下面我们通过一个例子来说明自定义绑定:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.jellythink.HelloWorld.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
在POM的build
元素下的plugins
子元素中声明插件的使用,插件在Maven中同其它包一样,也是作为独立的构建存在,所以也需要通过指定groupId
、artifactId
和version
这三个坐标元素去仓库中定位插件。除了基本的插件坐标声明外,还有插件执行配置,executions
下每个execution
子元素可以用来配置执行一个任务。上述例子中通过phase
配置,将其绑定到package
生命周期阶段上,再通过goals
配置指定要执行的插件目标,这样自定义插件绑定就完成了。
执行mvn clean install命令,我们就可以看到这样的输出:
[INFO] --- maven-shade-plugin:3.2.1:shade (default) @ hello-world ---
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing E:\Code\Spring\helloworld\target\hello-world-1.0-SNAPSHOT.jar with E:\Code\Spring\helloworld\target\hello-world-1.0-SNAPSHOT-shaded.jar
可以看到执行了maven-shade-plugin插件的shade插件目标。
有的时候,你会看到有的插件不通过phase元素配置生命周期阶段,插件目标也能够绑定到生命周期中去。这个时候也不要惊讶,这主要是很多插件的目标在编写时已经定义了默认绑定阶段,我们可以通过maven-help-plugin查看插件的详细信息,了解插件目标的默认绑定阶段。执行mvn help:describe -Dplugin=org.apache.maven.plugins:maven-shade-plugin:3.2.1 -Ddetail就可以看到插件的完整信息,比如这个插件有几个插件目标,有哪些参数,默认绑定阶段等,通过查找Bound to phase: package,我们就可以看到默认绑定到哪个阶段。
插件配置
我们在实现一个功能时,也会想着通过传递参数来实现更强大的功能。Maven插件也是这样的,我们可以配置插件目标的参数,满足我们对插件更加个性化的要求。对于插件的参数配置,有以下两种常用方式:
- 通过命令行进行插件配置
我们经常看到以下这个命令:
这个就是典型的通过命令行进行插件配置,maven.test.skip是maven-surefire-plugin提供的一个参数,我们通过命令行传入一个true参数,表示跳过执行测试。mvn clean install -Dmaven.test.skip=true
参数-D是Java自带的,其功能是通过命令行设置一个Java系统属性。Maven简单的重用了该参数。
- 通过POM文件进行插件全局配置
比如有些参数配置,从项目创建到项目发布都不会改变,或者基本上很少改变。对于这种场景,就更适合通过POM文件进行插件配置。比如以下的配置我们经常在一些项目中看到:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
这样就是一个全局的配置,也就是说,所有使用该插件目标的任务,都会使用这些配置。再回头看看上面插件绑定中自定义绑定里的那个例子,那个参数就是插件特有的参数,我们就可以通过参数实现一个不一样的功能。
网友评论