文章目录
插件目标
在进一步详述插件和生命周期的绑定关系之前,必须先了解插件目标(PluginCoal)的概念。我们知道,Maven 的核心仅仅定义了抽象的生命周期 ,具体的任务是交由插件完成的,插件以独立的构件形式存在,因此,Maven 核心的分发包只有不到3MB的大小,Maven 会在需要的时候下载并使用插件。
对于插件本身,为了能够复用代码,它往往能够完成多个任务。例如maven-dependency-plugin,它能够基于项目依赖做很多事情。它能够分析项目依赖,帮助找出潜在的无用依赖;它能够列出项目的依赖树,帮助分析依赖来源;它能够列出项目所有已解析的依赖等等。为每个这样的功能编写一个独立的插件显然是不可取的,因为这些任务背后有很多可以复用的代码,因此,这些功能聚集在一个插件里,每个功能就是一个插件目标。
maven-dependency-plugin 有十多个目标,每个目标对应了一个功能,上述提到的几个功能分别对应的插件目标为dependency : analyze、dependency : tree 和 dependency : list。这是一种通用的写法,冒号前面是插件前缀,冒号后面是该插件的目标。类似地,还可以写出compiler : compile(这是 maven-compiler-plugin 的 compile 目标)和surefire : test(这是 maven-sure-fire-plugin 的 test 目标)。
插件绑定
Maven 的生命周期与插件相互绑定,用以完成实际的构建任务 。具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务 。例如项目编译这一任务,它对应了 default 生命周期的 compile 这一阶段,而 maven-compiler-plugin 这一插件的 compile 目标能够完成该任务。因此,将它们绑定,就能实现项目编译的目的,如下
1️⃣内置绑定
为了能让用户几乎不用任何配置就能构建 Maven 项目,Maven 在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。
clean 生命周期仅有 pre-clean、clean 和 post-clean 三个阶段,其中的clean 与 maven-clean-plugin : clean 绑定。maven-clean-plugin 仅有clean 这一个目标,其作用就是删除项目的输出目录。clean 生命周期阶段与插件目标的绑定关系如下表格所示。
|------------|--------------------------|
| ### clean 生命周期阶段与插件目标的绑定关系 ||
| pre-clean | maven-clean-plugin:clean |
| clean |
| post-clean |
site 生命周期有 pre-site、site、post-site 和 site-deploy 四个阶段,其中,site 和 maven-siteplugin : site 相互绑定,site-deploy 和 maven-site-plugin : depoy相互绑定。maven-site-plugin 有很多目标,其中,site 目标用来生成项目站点,deploy 目标用来将项目站点部署到远程服务器上。site 生命周期阶段与插件目标的绑定关系如下表格所示。
|-------------|--------------------------|
| ### site 生命周期阶段与插件目标的绑定关系 ||
| pre-site | |
| site | maven-site-plugin:site |
| post-site | |
| site-deploy | maven-site-plugin:deploy |
相对于 clean 和 site 生命周期来说,default 生命周期与插件目标的绑定关系就显得复杂一些。这是因为对于任何项目来说,例如 jar 项目和 war 项目,它们的项目清理和站点生成任务是一样的,不过构建过程会有区别。例如 jar 项目需要打成 JAR 包,而 war 项目需要打成 WAR 包。
由于项目的打包类型会影响构建的具体过程,因此,defaut 生命周期的阶段与插件目标的绑定关系由项目打包类型所决定,打包类型是通过 POM 中的 packaging 元素定义的。最常见、最重要的打包类型是 jar,它也是默认的打包类型。基于该打包类型的项目,其 default 生命周期的内置插件绑定关系及具体任务如下表格所示。
|------------------------|--------------------------------------|-----------------|
| ### default 生命周期的内置插件绑定关系及具体任务(打包类型: jar) |||
| process-resources | maven-resources-plugin:resources | 复制主资源文件至主输出目录 |
| compile | maven-compiler-plugin:compile | 编译主代码至主输出目录 |
| process-test-resources | maven-resources-plugin:testResources | 复制测试资源文件至测试输出目录 |
| test-compile | maven-compiler-plugin:testCompile | 编译测试代码至测试输出目录 |
| test | maven-surefire-plugin:test | 执行测试用例 |
| package | maven-jar-plugin:jar | 创建项目jar包 |
| install | maven-install-plugin:install | 将项目输出构件安装到本地仓库 |
| deploy | maven-deploy-plugin:deploy | 将项目输出构件部署到本地仓库 |
注意,上图只列出了拥有插件绑定关系的阶段,default 生命周期还有很多其他阶段,默认它们没有绑定任何插件,因此也没有任何实际行为。
除了默认的打包类型 jar 之外,常见的打包类型还有 war、pom、maven-plugin、ear 等。它们的 defaut 生命周期与插件目标的绑定关系可参阅 Maven 官方文档,这里不再赘述。
你也可以从 Maven 项目的命令行执行 mvn clean install 命令,看到项目在构建过程中执行了哪些插件目标。
2️⃣自定义绑定
除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让 Maven 项目在构建过程中执行更多更富特色的任务。
一个常见的例子是创建项目的源码 jar 包,内置的插件绑定关系中并没有涉及这一任务,因此需要用户自行配置。maven-source-plugin 可以帮助我们完成该任务,它的 jar-no-fork 目标能够将项目的主代码打包成iar 文件,可以将其绑定到 default 生命周期的 verify 阶段上、在执行完集成测试后和安装构件之前创建源码 jar 包。具体配置见如下代码:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
在 POM 的 build 元素下的 plugins 子元素中声明插件的使用,该例中用到的是 maven-scurce-plugin,其 groupId 为org:apache.maven.plugins,这也是 Maven 官方插件的 groupId,紧接着 artifactld 为 maven-source-plugin,version 为 2.2.1。对于自定义绑定的插件,用户总是应该声明一个非快照版本,这样可以避免由于插件版本变化造成的构建不稳定性。
上述配置中,除了基本的插件坐标声明外,还有插件执行配置,executions 下每个 execution 子元素可以用来配置执行一个任务。该例中配置了一个 id 为 attach-sources 的任务通过 phase 配置,将其绑定到 verify 生命周期阶段上,再通过 goals 配置指定要执行的插件目标。至此,自定义插件绑定完成。运行 mvn verify 就能看到如下输出:
[INFO] --- source:2.2.1:jar-no-fork (attach-sources) @ hello-world ---
[INFO] Building jar: D:\Work\gittest\demo-maven-practical\hello-world\target\hello-world-1.0-SNAPSHOT-sources.jar
我们可以看到,当执行 verify 生命周期阶段的时候,maven-source-plugin:jar-no-fork 会得以执行,它会创建一个以" -sources.jar"结尾的源码文件包。
有时候,即使不通过 phase 元素配置生命周期阶段,插件目标也能够绑定到生命周期中去。例如,可以尝试删除上述配置中的 phase 一行,再次执行mvn verify,仍然可以看到 maven-source-plugin:jar-no-fork 得以执行。出现这种现象的原因是:有很多插件的目标在编写时已经定义了默认绑定阶段。可以使用 maven-help-plugin 查看插件详细信息,了解插件目标的默认绑定阶段。运行命令如下:
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:3.2.1 -Ddetail
该命令输出对应插件的详细信息。在输出信息中,能够看到关于目标 jar-no-fork 的如下信息:
...
source:jar-no-fork
Description: This goal bundles all the sources into a jar archive. This
goal functions the same as the jar goal but does not fork the build and is
suitable for attaching to the build lifecycle.
Implementation: org.apache.maven.plugin.source.SourceJarNoForkMojo
Language: java
Bound to phase: package
Available parameters:
...
该输出包含了一段关于 jar-no-fork 目标的描述,这里关心的是 Bound to phase 这一项,它表示该目标默认绑定的生命周期阶段(这里是package)。也就是说,当用户配置使用 maven-source-plugin 的 jar-no-fork 目标的时候,如果不指定 phase 参数,该目标就会被绑定到 package Maven 生命周期 default周期 的 package 阶段。
当插件目标被绑定到不同的生命周期阶段的时候,其执行顺序会由生命周期阶段的先后顺序决定。如果多个目标被绑定到同一个阶段,它们的执行顺序会是怎样?答案很简单,当多个插件目标绑定到同一个阶段的时候,这些插件声明的先后顺序决定了目标的执行顺序。
插件配置
完成了插件和生命周期的绑定之后,用户还可以配置插件目标的参数,进一步调整插件目标所执行的任务,以满足项目的需求。几乎所有 Maven 插件的目标都有一些可配置的参数,用户可以通过命令行和 POM 配置等方式来配置这些参数。
1️⃣命令行插件配置
在日常的 Maven 使用中,我们会经常从命令行输入并执行 Maven 命令。在这种情况下如果能够方便地更改某些插件的行为,无疑会十分方便。很多插件目标的参数都支持从命令行配置,用户可以在 Maven 命令中使用 -D 参数,并伴随一个参数键=参数值的形式,来配置插件目标的参数。
例如,maven-surefire-plugin 提供了一个 maven.test.skip 参数,当其值为 true 的时候,就会跳过执行测试。于是,在运行命令的时候,加上如下 -D 参数就能跳过测试:
mvn install -Dmaven.test.skip=true
参数- D 是 Java 自带的,其功能是通过命令行设置一个 Java 系统属性,Maven 简单地重用了该参数,在准备插件的时候检查系统属性,便实现了插件参数的配置。
2️⃣POM中插件全局配置
并不是所有的插件参数都适合从命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,对于这种情况,在 POM 文件中一次性配置就显然比重复在命令行输入要方便。
用户可以在声明插件的时候,对此插件进行一个全局的配置。也就是说,所有该基于该插件目标的任务,都会使用这些配置。例如,我们通常会需要配置 maven-compiler-plugin 告诉它编译 Java 1.5 版本的源文件,生成与 JVM1.5 兼容的字节码文件,见代码如下:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
这样,不管绑定到 compile 阶段的 maven-compiler-plugin:compie 任务,还是绑定到 test-compiler 阶段的 maven-compiler-plugin:testCompiler 任务,就都能够使用该配置,基于Java1.5 版本进行编译。
插件全局配置元素是 configuration 元素。
3️⃣POM中插件任务配置
除了为插件配置全局的参数,用户还可以为某个插件任务配置特定的参数。以 maven-antrumn-plugin 为例,它有一个目标 run,可以用来在 Maven 中调用 Ant 任务。用户将 maven-antrun-plugin:run 绑定到多个生命周期阶段上,再加以不同的配置,就可以让 Maven 在不同的生命阶段执行不同的任务,如下:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>ant-validate</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo>i am bound to validate phase</echo>
</tasks>
</configuration>
</execution>
<execution>
<id>ant-verify</id>
<phase>verify</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo>i am bound to verify phase</echo>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
在上述代码片段中,首先,maven-antrun-plugin:run 与 validate 阶段绑定,从而构成一个 id 为 ant-validate 的任务。插件全局配置中的 configuration 元素位于 plugin 元素下面,而这里的 confguration 元素则位于 execution 元素下,表示这是特定任务的配置,而非插件整体的配置。这个 ant-validate 任务配置了一个echo Ant 任务,向命令行输出一段文字,表示该任务是绑定到 validate 阶段的。第二个任务的 id 为 ant-verify,它绑定到了 verify 阶段,同样它也输出一段文字到命令行,告诉该任务绑定到了verify 阶段。