Maven生命周期与插件

Maven简介

Maven是一个项目构建工具,也可以管理项目的依赖。maven将构建项目的过程分为了三个独立的生命周期,每个生命周期都有一系列的阶段,每个阶段都需要和maven的插件绑定才能执行。从这个角度来说,maven又是一个插件执行框架,它的功能都是通过插件来完成的。

Maven构建的生命周期与插件

生命周期

Maven有三个内置的构建生命周期(build lifecycle),它们彼此独立,分别是:cleandefaultsite。clean生命周期主要负责项目的清理工作,default生命周期主要负责项目的部署工作,site生命周期主要负责创建项目的web站点。我们平常用的maven命令mvn clean package中的clean属于clean生命周期,package属于default生命周期。

每个生命周期都有一些列不同的构建阶段(build phase,也叫build stage)组成。生命周期内的这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。比如clean生命周期有pre-cleancleanpost-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/><reporting/>部分指定。我们平时主要用的都是build类型插件。

插件的命名

自己开发的maven插件名字需要遵循<yourplugin>-maven-plugin。maven官方的插件名字遵循maven-<yourplugin>-plugin的格式。

插件的目标

为了实现代码复用,一个maven插件可能有多个功能,比如maven-dependency-plugin,它可以完成的功能有:

  1. 分析项目的依赖,找出哪些依赖被使用了且声明了,哪些被使用了但没声明,哪些没被使用却声明了
  2. 分析项目的依赖,列出那些解析出的依赖和dependencyManagement中不匹配的部分
  3. 分析pom.xml中dependenciesdependencyManagement标签中重复的声明
  4. 输出项目的依赖树

上面只是列出了几个功能,更详细的部分可以在maven官网插件部分找到。

这些插件可以完成的独立的功能,就是插件的目标(goal)。插件及其目标可以写成下面的方式:

dependency:analyzedependency:tree,中间用冒号分开,这其实是对maven-dependency-plugin:analyzemaven-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.plugin</groupId>
        <artifactId>hello-maven-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
      </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:sayhimvn dependency:tree是不是似曾相识了?

生命周期阶段和插件的绑定

maven的生命周期的阶段需要和插件绑定,才能完成构建任务。比如default生命周期的compile这个阶段,可以通过maven-compiler-plugincompile目标完成。

一些生命周期阶段和插件默认绑定的关系:

在这里插入图片描述

也可以通过在pom.xml中自定义插件和生命周期的阶段的绑定关系,比如:

<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>verify</phase>
          <goals>
            <goal>jar-no-fork</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

将source插件的jar-no-fork目标绑定到default生命周期的verify阶段执行。

在开发自定义maven插件的时候,可以通过defaultPhase=LifecyclePhase.XX来指定插件绑定到哪个生命周期阶段执行。

一些说明:

  1. 在执行mvn命令的时候,如果一些阶段应该被执行,但是因为没有绑定maven插件,则这个阶段什么都不会做。比如mvn clean,应该也会执行pre-clean阶段,但是如果没有任何插件目标绑定到了这个阶段,它什么都不会执行。
  2. 如果有多个插件的目标绑定到了同一个生命周期阶段,它们会按照在pom.xml中配置的顺序执行。

maven插件的配置

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中通过<configuration>标签配置:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-myquery-plugin</artifactId>
        <version>1.0</version>
        <configuration>
          <url>http://www.foobar.com/query</url>
          <timeout>10</timeout>
          <options>
            <option>one</option>
            <option>two</option>
            <option>three</option>
          </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实战》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值