(VIII)Maven插件

经过Maven生命周期的学习,我们知道在Maven core中仅仅定义了抽象的生命周期,具体的实现是由插件完成的,而插件是以独立的构件形式存在。

在进一步了解插件和生命周期的绑定关系之前,我们先了解一下插件目标(Plugin Goal)的概念。

一个插件往往能够完成多个功能。如果我们为其中每个功能都实现一个独立的插件,显然不可取的,因为这些功能背后有很多可以复用的代码。因此,这些功能都聚集在一个插件里,每个功能就是一个插件目标。例如maven-dependency-plugin有十几个目标,分析项目依赖dependency:analyze,列出项目的依赖树dependency:tree,列出项目所有已解析的依赖dependency:list。这是种通用写法,冒号前为插件前缀,冒号后为插件目标。例如,compiler:compile(maven-compiler-plugin的compile目标),surefire:test(maven-surefire-plugin的test目标)

✿内置绑定✿

Maven为了让用户几乎不需要任何配置就可以构建Maven项目,在核心为一些主要的生命周期阶段绑定了插件的目标。

  • clean生命周期有pre-clean、clean、post-clean三个阶段。其中clean与maven-clean-plugin:clean绑定。maven-clean-plugin仅有clean这一个目标,作用就是删除项目的输出目录。
<component>
  <role>org.apache.maven.lifecycle.Lifecycle</role>
  <implementation>org.apache.maven.lifecycle.Lifecycle</implementation>
  <role-hint>clean</role-hint>
  <configuration>
    <id>clean</id>
    
    <phases>
      <phase>pre-clean</phase>
      <phase>clean</phase>
      <phase>post-clean</phase>
    </phases>
    <default-phases>
      <clean>
        org.apache.maven.plugins:maven-clean-plugin:2.5:clean
      </clean>
    </default-phases>
    
  </configuration>
</component>
clean生命周期 阶段与插件目标绑定关系.png
  • site生命周期有pre-site,site,post-site,site-deploy四个阶段。site和maven-site-plugin:site绑定,用于生成项目站点。site-deploy和maven-site-plugin:deploy绑定,用于将项目站点部署到远程服务器上。
<component>
  <role>org.apache.maven.lifecycle.Lifecycle</role>
  <implementation>org.apache.maven.lifecycle.Lifecycle</implementation>
  <role-hint>site</role-hint>
  <configuration>
    <id>site</id>
    
    <phases>
      <phase>pre-site</phase>
      <phase>site</phase>
      <phase>post-site</phase>
      <phase>site-deploy</phase>
    </phases>
    <default-phases>
      <site>
        org.apache.maven.plugins:maven-site-plugin:3.3:site
      </site>
      <site-deploy>
        org.apache.maven.plugins:maven-site-plugin:3.3:deploy
      </site-deploy>
    </default-phases>
    
  </configuration>
</component>
site生命周期 阶段与插件目标绑定关系.png
  • default生命周期,会相对上面两种功能多一些,内置的插件绑定以打包类型jar为例,有8个阶段:
<component>
  <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
  <role-hint>jar</role-hint>
  <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
  <configuration>
    <lifecycles>
      <lifecycle>
        <id>default</id>
        
        <phases>
          <process-resources>
            org.apache.maven.plugins:maven-resources-plugin:2.6:resources
          </process-resources>
          <compile>
            org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
          </compile>
          <process-test-resources>
            org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
          </process-test-resources>
          <test-compile>
            org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
          </test-compile>
          <test>
            org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
          </test>
          <package>
            org.apache.maven.plugins:maven-jar-plugin:2.4:jar
          </package>
          <install>
            org.apache.maven.plugins:maven-install-plugin:2.4:install
          </install>
          <deploy>
            org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
          </deploy>
        </phases>
        
      </lifecycle>
    </lifecycles>
  </configuration>
</component>

实际的阶段有

 <component>
  <role>org.apache.maven.lifecycle.Lifecycle</role>
  <implementation>org.apache.maven.lifecycle.Lifecycle</implementation>
  <role-hint>default</role-hint>
  <configuration>
    <id>default</id>
    
    <phases>
      <phase>validate</phase>
      <phase>initialize</phase>
      <phase>generate-sources</phase>
      <phase>process-sources</phase>
      <phase>generate-resources</phase>
      <phase>process-resources</phase>
      <phase>compile</phase>
      <phase>process-classes</phase>
      <phase>generate-test-sources</phase>
      <phase>process-test-sources</phase>
      <phase>generate-test-resources</phase>
      <phase>process-test-resources</phase>
      <phase>test-compile</phase>
      <phase>process-test-classes</phase>
      <phase>test</phase>
      <phase>prepare-package</phase>
      <phase>package</phase>
      <phase>pre-integration-test</phase>
      <phase>integration-test</phase>
      <phase>post-integration-test</phase>
      <phase>verify</phase>
      <phase>install</phase>
      <phase>deploy</phase>
    </phases>
    
  </configuration>
</component>
default生命周期 阶段与插件目标绑定关系.png

其他的打包内置插件绑定在components.xml可以查看到,同时可以参考Maven官方文档


之前执行过的mvn clean test

    C:\Subversion\MavenPrj\helloMaven>mvn clean test
    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] Building parent 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO]
    [INFO] --- maven-clean-plugin:3.0.0:clean (default-clean) @ hello-maven ---
    [INFO] Deleting C:\Subversion\MavenPrj\helloMaven\target
    [INFO]
    [INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ hello-maven ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] skip non existing resourceDirectory C:\Subversion\MavenPrj\helloMaven\src\main\resources
    [INFO]
    [INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ hello-maven ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 1 source file to C:\Subversion\MavenPrj\helloMaven\target\classes
    [INFO]
    [INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ hello-maven ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] skip non existing resourceDirectory C:\Subversion\MavenPrj\helloMaven\src\test\resources
    [INFO]
    [INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ hello-maven ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 1 source file to C:\Subversion\MavenPrj\helloMaven\target\test-classes
    [INFO]
    [INFO] --- maven-surefire-plugin:2.20.1:test (default-test) @ hello-maven ---
    [INFO]
    [INFO] -------------------------------------------------------
    [INFO]  T E S T S
    [INFO] -------------------------------------------------------
    [INFO] Running com.play.myMaven.HelloWorldTest
    [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.02 s - in com.play.myMaven.HelloWorldTest
    [INFO]
    [INFO] Results:
    [INFO]
    [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 1.969 s
    [INFO] Finished at: 2018-06-05T19:52:48+08:00
    [INFO] Final Memory: 16M/167M
    [INFO] ------------------------------------------------------------------------

从输出中可以看到Maven执行插件目标顺序为:

  • maven-clean-plugin:clean
  • maven-resources-plugin:resources
  • maven-compiler-plugin:compile
  • maven-resources-plugin:testResources
  • maven-compiler-plugin:testCompile
  • maven-surefire-plugin:test
    对照上文的clean、default、site表格,可以看到在test及其之前的阶段凡是绑定了内置插件的阶段均有相应的插件执行。

✿自定义绑定✿

除了Maven内置绑定,Maven支持用户自定义绑定,可以自己选择将某个插件目标绑定到生命周期的某个阶段。
来举个例子,创建项目的源码jar包。内置中并没有涉及到这个任务,需要我们自行配置。maven-source-plugin:jar-no-fork能够将项目的主代码打包成jar文件,把这个插件绑定在default生命周期的verify阶段上,在执行完集成测试后创建源码jar包。

  <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-source-plugin</artifactId>
      <executions>
        <execution>
          <id>attach-sources</id>
          <phase>verify</phase>
          <goals>
            <goal>jar-no-fork</goal>
          </goals>
        </execution>
      </executions>
  </plugin>
  • 需要配置插件坐标
  • 配置插件执行(每个声明的任务都需要包裹在一个execution元素中),id元素为当前POM唯一标识,phase元素指定阶段,goal元素指定目标,还应有个version元素这里我也不知道版本是什么就不写version,让Maven自动寻找最新稳定版(仍然建议在xml中声明version版本),ctrl+鼠标左键进入jar-no-fork说明:
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
...
<mojo>
  <goal>jar-no-fork</goal>
  <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.</description>
  ...
</mojo>

jar-no-fork目标将所有的资源打包成一个jar归档文件。这个目标与jar目标的功能相同,但不派生构建,适合附加到构建生命周期。自定义插件绑定完成。运行mvn verify

[INFO] --- maven-source-plugin:3.0.1:jar-no-fork (attach-sources) @ hello-maven ---
[INFO] Building jar: C:\Subversion\MavenPrj\helloMaven\target\hello-maven-1.0-SNAPSHOT-sources.jar

verify阶段会执行maven-source-plugin:jar-no-fork,并创建一个-sources.jar结尾的源码文件包。

有时候不通过phase元素配置生命周期阶段,插件目标也能够绑定到对应的生命周期。因为,有很多插件的目标在编写时已经定义了默认绑定阶段。可以使用maven-help-plugin查看插件详细信息,了解插件目标默认绑定阶段。运行命令:mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:3.0.1 -Ddetail=true

source:test-jar-no-fork
  Description: This goal bundles all the test sources into a jar archive.
    This goal functions the same as the test-jar goal but does not fork the
    build, and is suitable for attaching to the build lifecycle.
  Implementation: org.apache.maven.plugins.source.TestSourceJarNoForkMojo
  Language: java
  Bound to phase: package

Bound to phase: package 默认绑定的生命周期阶段是package。若我们不指定phase参数,该目标会被绑定到 package 阶段。

多个目标被绑定到同一阶段,根据插件声明的先后顺序决定目标的执行顺序。


✿插件配置✿

1.命令行插件配置
在Maven命令中使用-D参数,伴随一个参数键=参数值的形式,来配置插件目标的参数。(注意=两侧不要有空格)
eg:maven-surefire-plugin提供了一个maven.test.skip参数,当值为true就会跳过执行测试:
执行mvn install -Dmaven.test.skip=true

C:\Subversion\MavenPrj\helloMaven>mvn install -Dmaven.test.skip=true
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building parent 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ hello-maven ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ hello-maven ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ hello-maven ---
[INFO] Not copying test resources
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ hello-maven ---
[INFO] Not compiling test sources
[INFO]
[INFO] --- maven-surefire-plugin:2.20.1:test (default-test) @ hello-maven ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ hello-maven ---
[INFO] Building jar: C:\Subversion\MavenPrj\helloMaven\target\hello-maven-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.5.2.RELEASE:repackage (default) @ hello-maven ---
[INFO]
[INFO] --- maven-source-plugin:2.4:jar-no-fork (attach-sources) @ hello-maven ---
[INFO] Building jar: C:\Subversion\MavenPrj\helloMaven\target\hello-maven-1.0-SNAPSHOT-sources.jar
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ hello-maven ---
[INFO] Installing C:\Subversion\MavenPrj\helloMaven\target\hello-maven-1.0-SNAPSHOT.jar to C:\Repository\m2repo\com\play\myMaven\hello-maven\1.0-SNAPSHOT\hello-maven-1.0-SNAPSHOT.jar
[INFO] Installing C:\Subversion\MavenPrj\helloMaven\pom.xml to C:\Repository\m2repo\com\play\myMaven\hello-maven\1.0-SNAPSHOT\hello-maven-1.0-SNAPSHOT.pom
[INFO] Installing C:\Subversion\MavenPrj\helloMaven\target\hello-maven-1.0-SNAPSHOT-sources.jar to C:\Repository\m2repo\com\play\myMaven\hello-maven\1.0-SNAPSHOT\hello-maven-1.0-SNAPSHOT-sources.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

参数-D是Java自带的,功能是通过命令行设置一个Java系统属性,Maven简单地重用了该参数,在准备插件的时候检查系统属性,即实现了插件参数的配置。
resources:testResources 和compiler:testCompile 显示Not copying test resources
surefire:surefire 显示 Tests are skipped.

2.POM中插件全局配置
并不是所有插件都适合从命令行配置。有些参数的值从项目创建到项目发布都不会改变或者很少改变,这种情况在POM中一次性配置就比重复在命令行输入要方便。

我们在声明插件的时候,可以对插件进行一个全局的配置,即基于该插件目标的任务,都会使用这些配置。

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.9.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>

无论绑定到哪个阶段的javadoc:complie任务,都会使用该配置,基于Java1.8进行编译。

3.POM中插件任务配置
同样地,我们也可以对插件任务进行配置。

  <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.1.0</version>
    <executions>
      <execution>
        <id>copy-docker-resources</id>
        <phase>prepare-package</phase>
        <goals>
          <goal>copy-resources</goal>
        </goals>
        <configuration>
          <outputDirectory>target</outputDirectory>
          <resources>
            <!--拷贝docker资源文件-->
            <resource>
              <directory>${project.basedir}/src/main/Docker</directory>
            </resource>
          </resources>
        </configuration>
      </execution>
    </executions>
  </plugin>

在prepare-package阶段执行copy-resources任务时,输出目录为target,拷贝docker资源文件目录为~/src/main/Docker


✿获取插件信息✿

我们需要知道去哪里寻找合适的插件,找到后还需要知道该插件的配置点都有哪些。

1.在线插件信息
基本上所有主要的Maven插件都来自ApacheCodehaus
所有官方插件能在这里下载:https://repo.maven.apache.org/maven2/http://repository.codehaus.org/org/codehaus/mojo/
例如插件:maven-surefire-plugin,访问http://maven.apache.org/plugins/maven-surefire-plugin

maven-surefire-plugin信息获取.png

可以阅读插件文档中的介绍和实例。
Goals.png

查阅该插件的目标。点击Goal继续查看目标参数等信息。https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html
有Required Parameters、Optional Parameters、Parameter Details三个模块,在Parameter Details中找到skip

skip:

Set this to "true" to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you enable it using the "maven.test.skip" property, 
because maven.test.skip disables both running the tests and compiling the tests. Consider using the skipTests parameter instead.
Type: boolean
Required: No
User Property: maven.test.skip
Default: false

2.通过maven-help-plugin描述插件
例如上文中的mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:3.0.1 -Ddetail=true

<plugin>

The Maven Plugin to describe. This must be specified in one of three ways: 
plugin-prefix, i.e. 'help'
groupId:artifactId, i.e. 'org.apache.maven.plugins:maven-help-plugin'
Type: java.lang.String
Since: 2.0
Required: No
User Property: plugin
Alias: prefix

上述是help:describe的plugin参数的文档描述,我们需要在plugin=后填入plugin-prefix插件前缀、groupId:artifactId、groupId:artifactId:version三种方式中的任意一种就OK了。

<detail>

This flag specifies that a detailed (verbose) list of goal (Mojo) information should be given.
Type: boolean
Since: 2.1
Required: No
User Property: detail
Default: false

上述是help:describe的detail参数的文档描述,设置为true将会给出详细的目标信息。

我们来看下插件compiler的信息,运行mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin

Name: Apache Maven Compiler Plugin
Description: The Compiler Plugin is used to compile the sources of your
  project.
Group Id: org.apache.maven.plugins
Artifact Id: maven-compiler-plugin
Version: 3.7.0
Goal Prefix: compiler

This plugin has 3 goals:

compiler:compile
  Description: Compiles application sources

compiler:help
  Description: Display help information on maven-compiler-plugin.
    Call mvn compiler:help -Ddetail=true -Dgoal=<goal-name> to display
    parameter details.

compiler:testCompile
  Description: Compiles application test sources.

For more information, run 'mvn help:describe [...] -Ddetail'

简化一下,用plugin-prefix来执行。运行mvn help:describe -Dplugin=compiler,结果同上述结果一致。

同理,目标describe存在参数goal,那我们只看compiler中的compile任务,就可以添加goal参数。运行:mvn help:describe -Dplugin=compiler -Dgoal=compile即可


✿从命令行调用插件✿

执行mvn -h命令

C:\Subversion\MavenPrj\helloMaven>mvn -h

usage: mvn [options] [<goal(s)>] [<phase(s)>]
Options:...

另外
mvn help:describe -Dplugin=compile
mvn dependency:tree
等价于
mvn org.apache.maven.plugins:maven-help-plugin:describe -Dplugin=compiler
mvn org.apache.maven.plugins:maven-dependency-plugin:tree


✿插件解析机制✿*

1.插件仓库
Maven会区别对待依赖的远程仓库插件的远程仓库
解压一下$M2_HOME/lib/maven-model-builder-3.3.9.jar,进入\org\apache\maven\model\,打开pom-4.0.0.xml,这个是所有Maven项目都会继承的超级POM,里面有Maven内置的pluginRepositories

<pluginRepositories>
  <pluginRepository>
    <id>central</id>
    <name>Central Repository</name>
    <url>https://repo.maven.apache.org/maven2</url>
    <layout>default</layout>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
    <releases>
      <updatePolicy>never</updatePolicy>
    </releases>
  </pluginRepository>
</pluginRepositories>

对比内置依赖的远程仓库repositories

<repositories>
  <repository>
    <id>central</id>
    <name>Central Repository</name>
    <url>https://repo.maven.apache.org/maven2</url>
    <layout>default</layout>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
 </repositories>

原谅我没看出来特别的地方......欢迎在评论区补充

2.插件的默认groupId
在POM中配置插件的时候,如果该插件时Maven的官方插件(即如果其groupId是org.apache.maven.plugins),就可以忽略groupId配置。Maven在解析该插件的时候自动补齐这个值。

3.解析插件版本
Maven在用户没有提供插件版本的情况下,会自动解析插件版本。Maven在超级POM中为所有核心插件设定了版本。所有项目都继承超级POM的配置。如果使用的某个插件版本没有设定版本,同时又不属于核心插件的范畴。Maven会去检查所有仓库中可用的版本,然后做出选择(最新的稳定版本-release)。

4.解析插件前缀
插件前缀与groupId:artifactId是一一对应的,这种匹配关系存储在仓库元数据中。当然我们也可以通过配置settings.xml让Maven检查其他pluginGroup上的插件仓库元数据:

<settings>
    <pluginGroups>
        <pluginGroup>com.your.plugins</pluginGroup>
    </pluginGroups>
</settings>

基于该配置,Maven就不仅仅会检查org/apache/maven/plugins/maven-metadata.xml,还会检查com/your/plugins/maven-metadata.xml


插件仓库元数据.png

上述是org.apache.maven.plugins.groupId下插件仓库元数据中的片段。

可以看到maven-clean-plugin的前缀就是clean,maven-compiler-plugin的前缀就是compiler,maven-dependency-plugin的前缀就是dependency

当Maven解析到dependency:tree时,首先基于默认的groupId归并所有插件仓库的元数据,即org/apache/maven/plugins/maven-metadata.xml,然后检查归并后的元数据,找到对应的artifactId;然后结合当前元数据的groupId,解析得到version。如果org/apache/maven/plugins/maven-metadata.xml中没有记录该插件的前缀,接着检查其他groupId下的元数据,以及用户自定义的插件组。所有元数据中都不包含该前缀,则报错。


《Maven实战》学习笔记

推荐阅读更多精彩内容

  • 所有项目的构建都是有生命周期的,这个生命周期包括:项目清理、初始化、编译、测试、打包、集成测试、验证、部署、站点生...
    zlcook阅读 1,884评论 0 21
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 105,059评论 12 126
  • 生命周期是maven的又一大核心,maven的生命周期是抽象的,而实际行为都是以插件的方式来完成的,下面我将对生命...
    小炼君阅读 589评论 0 50
  • 其实这篇读后感应该由爸爸来写,可是爸爸总是很忙,便只好依然由我来抒情了。 爸爸强力推荐我看《爱哭鬼小隼》和《多多,...
    一阐提人阅读 57评论 0 2
  • 简书上,已有半个月没再更新,除了工作忙碌,更多的是自我懈怠。 至于懈怠的原因,还是因为网络小说。寒假里沉迷许久,好...
    爱如你我阅读 56评论 0 2