maven系列(五)生命周期和插件

除了坐标、依赖以及仓库以外,maven另外两个核心概念就是生命周期和插件。maven的生命周期是抽象的,其实际行为都是由插件来完成。

7.1 什么是生命周期

在maven出现之前,项目构建的生命周期就已经存在,软件开发人员每天都在对项目进行清理、编译、测试以及部署。

Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结出一套高度完善的、易扩展的生命周期。这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的创建,都能映射到这样一个生命周期上。

Maven的生命周期是抽象的, 这意味着生命周期本身不做任何实际任务,在maven的设计中,实际的任务都交给插件来完成。这种思想与设计模式的模板方法(template method)非常相似。模板方法在父类中定义算法的整体结构,子类可以通过实现或者重写父类的方法来控制实际的行为,这样既保证了算法有足够的可扩展性,又能够严格控制算法的整体结构。如下的模板方法抽象类能够很好的体现maven生命周期的概念。

public abstract class AbstractBuild {
    private AbstractBuild(){}
    public void build() {
        init();
        compile();
        test();
        packagee();
        integrationTest();
        deploy();
    }

    /**
     * 抽象方法 初始化
     */
    protected abstract void init();

    /**
     * 抽象方法 编译
     */
    protected abstract void compile();

    /**
     * 测试
     */
    protected abstract void test();

    /**
     * 打包:因为package是关键词
     */
    protected abstract void packagee();

    /**
     *  集成测试
     */
    protected abstract void integrationTest();

    /**
     * 部署
     */
    protected abstract void deploy();
}

这个类没有具体实现初始化,编译,测试等行为,它们都交由子类去实现。

虽然上述代码和Maven实际代码相去甚远,Maven的生命周期包含更多的步骤和更复杂的逻辑,但是它们的基本理念是相同的。为了不让用户重复发明*,Maven设计了插件机制。

每个构建步骤都可以绑定一个或多个插件行为,而且maven为大多数构建步骤编写并绑定了默认插件。例如,针对编译的插件有maven-compiler-plugin,针对测试的插件有maven-surefire-plugin等。当用户有特殊需要的时候,也可以配置插件定制构建行为,甚至自己编写插件。

maven定义的生命周期和插件机制一方面保证了所以Maven项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建。此外,扩展性也不错。

7.2 生命周期详解

三套生命周期

Maven其实拥有三套相互独立的生命周期,他们分别为clean、default和site。clean生命周期的目的是清理项目,default生命周期的目的是构建项目,而site则是建立项目站点。

每个生命周期包含一些阶段(phase),这些阶段是有顺序的,且具有依赖的关系。

但是三套生命周期本身是相互独立的, 用户可以仅仅调用default生命周期的某个阶段,而不会对其他生命周期产生任何影响。

clean生命周期

clean生命周期的目的是清理项目,它包含三个阶段:

  1. pre-clean:执行一些清理前需要完成的任务
  2. clean:清理上一次构建生成的文件
  3. post-clean:执行一些清理后需要完成的工作

default生命周期

default生命周期定义了真正构建时所需要的所有步骤,它是所有生命周期中最核心的部分。

  • validate
  • initialize
  • generate-sources:
  • process-sources:处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
  • generate-resources:
  • process-resources:
  • compile:编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
  • process-classes
  • generate-test-sources
  • process-test-sources处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作以后,复制到项目输出的测试classpath目录中。
  • generate-test-resources:
  • process-test-resources:
  • test-compile:编译项目的测试代码。一般来说,是编译src/test/java目录下的java文件至项目输出的测试classpath目录中。
  • process-test-classes
  • test:使用单元测试框架运行测试,测试代码不会被打包或部署。
  • prepare-package
  • package:接受编译好的代码,打包成可发布的格式,如JAR。
  • pre-integration-test:
  • integration-test
  • post-integration-test:
  • verify:
  • install:将包安装到maven本地仓库,供本地其他maven项目使用。
  • deploy:将最终的包复制到远程仓库,供其他开发人员和maven项目使用。

site生命周期

site生命周期的目的是建立和发布项目站点,maven能够基于pom所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。该生命周期包括:

  • pre-site:执行一些在生成项目站点之后需要完成的工作。
  • site:生成项目站点文档。
  • post-site:执行一些在生成项目站点之后需要完成的工作。
  • site-deploy:将生成的项目站点发布到服务器上。

命令行与生命周期

以常见命令解释其执行的生命周期阶段:

  • $mvn clean:该命令调用clean生命周期的clean阶段。实际执行的阶段为clean生命周期的pre-cleanclean阶段。
  • $mvn test:该命令调用default生命周期的test阶段。实际执行的阶段为default生命周期的validateinitialize等,直到test的所有阶段。这也解释了为什么在执行测试的时候,项目的代码能够自动得以编译。
  • $mvn clean install:该命令调用clean生命周期的clean阶段和default生命周期的install阶段。实际执行的阶段为clean生命周期的pre-cleanclean阶段,以及default生命周期从validate到install的全部阶段。该命令结合了两个生命周期,在执行正在的项目构建之前清理项目是一个很好的实践。

7.3 插件目标

Maven的核心仅仅定义了抽象的生命周期,具体的任务是交给插件完成的,插件以独立的构件形式存在,因此,maven核心的分发包只有3MB,Maven会在需要的时候下载并使用插件。

maven-dependency-plugin有十多个目标,每个目标对应一个功能,上述提到的几个功能分别对应dependency:analyzedependency:treedependency:list

冒汗前面是插件前缀,冒号后面是该插件的目标。

类似的还有:surefire:test这个是maven-surefile-plugin的test目标。

7.4 插件绑定

Maven的生命周期与插件相互绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务。

内置绑定

为了能让用户几乎不用任何配置就能构建Maven项目,Maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。

clean生命周期仅有的pre-cleancleanpost-clean三个阶段,其中的cleanmaven-clean-plugin:clean绑定。

自定义绑定(重点)

除了内置绑定以外,用户还能够自己选择将某个插件绑定到生命周期的某个阶段,这种自定义绑定方法能让maven项目在构建过程中执行更多任务。

一个常见的例子是创建项目的源码jar包,内置的插件绑定关系中并没有涉及这一任务,因此需要用户自行配置。maven-source-plugin可以帮助我们完成该任务,它的jar-no-fork目标能够将项目的主代码打包成jar文件。

<build>
	<plugins>
    	<plugin>
        	<GAV/>
            <executions>
            	<execution>
                	<id>task1</id>
                    <phase>varify</phase>
                    <goals>
                    	<goal/>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>

</build>

在POM的build元素下的<plugins>字元素中声明插件的使用。对于自定义绑定的插件,用户总是应该声明一个非快照版本,这样可以避免由于版本变化造成的构建不稳定性。

​ 在上述配置中,除了基本的插件坐标声明外,还有插件执行配置,<executions>下每个execution资源时可以用来配置执行一个任务。该例子中配置了一个id为task1的任务,通过phrase配置,将其绑定到verify声明周期阶段上,再通过goals配置指定要执行的插件目标。至此,自定义插件绑定完成。运行mvn verify就能看到如下输出。

我们知道,当插件目标被绑定到不同的生命周期阶段的时候,其执行顺序会由生命周期阶段的先后顺序决定。如果多个目标被绑定到同一个阶段,这些插件声明的先后顺序决定了目标的执行顺序。

7.5 插件配置

完成了插件和生命周期的绑定之后,用户还可以配置插件目标的参数,进一步调整插件目标所执行的任务,以满足项目的需求。

命令行插件配置

用户可以在maven命令中使用-D参数,并伴随一个参数键=参数值的形式来配置插件目标的参数。

比如在maven-surefile-plugin中提供了maven.test.skip参数:

mvn install -Dmaven.test.skip=true

-D是用来在启动一个java程序时设置系统属性值的。如果该值是一个字符串且包含空格,那么需要包在一对双引号中。

POM中插件全局配置

用户可以在声明插件的时候,对此插件进行一个全局的配置。例如我们需要配置maven-compiler-plugin告诉它编译Java 1.5版本的源文件,生成与JVM1.5兼容的字节码:

<build>
	<plugins>
   		<plugin>
        	<groupId>org.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.1</version>
            <configuration>
            	<source>1.5</source>
                <target>1.5</target>
            </configuration>
        </plugin>
    </plugins>
</build>

这样,不管绑定到compile阶段的maven-compiler-plugin:compile任务,还是绑定到test-compiler阶段的maven-compiler-plugin:testCompiler任务,就都能够使用该配置,基于1.5版本进行编译。

POM中插件任务配置

7.6 获取插件信息

在线插件信息

基本上所有主要的maven插件都来自Apache和codehaus

网址:https://maven.apache.org/plugins/index.html

托管于Codehaus上的Mojo项目也提供了大量maven插件。但是该网址已经关停了

使用maven-help-plugin描述插件

mvn help:describe -Dplugin = org.apache.maven.plugins:maven-compiler-plugin:2.1

执行了maven-help-plugindescribe目标。在参数plugin中输入需要描述插件的GAV。

【略】

常见插件

插件名称 用于 来源
maven-clean-plugin
maven-compiler-plugin
deploy
install
resources 处理资源文件
maven-site-plugin 生成站点
maven-surefire-plugin 执行测试
maven-jar-plugin 构建jar
maven-javadoc-plugin 生成javadoc文档
maven-pmd-plugin 生成PMD报告
maven-assembly-plugin 构建自定义格式的分发包
maven-enforcer-plugin 定义规则并强制要求项目遵循
maven-source-plugin 生成源码包
properties-maven-plugin 从properties文件读写maven属性
jetty-maven-plugin 集成Jetty容器,实现快速开发测试

7.8 插件解析机制

为了方便用户使用和配置插件,maven不需要用户提供完整的插件坐标信息,就可以解析得到正确的插件,maven的这一特性是一把双刃剑,虽然它简化了插件的使用和配置,可一旦插件的行为出现异常,用户就很难快速定位到出现问题的构建。比如

mvn help:system

这一条命令,它到底执行了什么插件?下面介绍原理。

插件仓库

与依赖构件一样,插件构件同样基于坐标存储在maven仓库中。在需要的时候,maven会从本地查找,如果没有去远程操作找。

但是maven会区别对待依赖的远程仓库和插件的远程仓库。当maven需要的依赖在本地仓库不存在时,它会去所配置的远程仓库查找,可是当maven需要的插件在本地仓库不存在时,它就不会去这些远程仓库查找。

插件的远程仓库使用pluginRepositories和pluginRepository配置。

插件的默认groupId

在POM中配置插件的时候,如果该插件是maven的官方插件,就可以忽略groupId配置。

<build>
	<plugins>
    	<plugin>
        	<artifactId>maven-compiler-plugin</artifactId>
            <version>2.1</version>
            <configuration>
            	<source>1.5</source>
                <target>1.5</target>
            </configuration>
        </plugin>
    </plugins>
</build>

上述配置就省略了groupId,maven在解析该插件的时候,会自动用默认groupId:org.apache.maven.plugins补齐。【不推荐】

解析插件版本

同样是为了简化插件的配置和使用,在用户没有提供版本的情况下,maven会自动解析插件版本。

首先,maven在超级POM中为所有核心插件设定了版本,超级POM是所有maven项目的父POM。所有项目都继承这个超级POM配置。因此,即使用户不加任何配置,maven使用核心插件的时候,它们的版本就已经确定了。

如果用户使用某个插件时,没有设定版本,而这个插件又不属于核心插件的范畴,maven就会去检查所有仓库中可用的版本,然后做出选择。

maven遍历本地仓库和所有远程插件仓库,将该路径下的仓库元数据归并后,就能计算出latest和release的值。然后使用release而不是latest,避免由于快照频繁更新导而导致的插件行为不稳定。

【但是不设定版本是不推荐的, 同样超级POM中为核心插件已经设定了版本】

解析插件前缀

插件前缀与groupId:artifactoryId是一一对应的,这种匹配关系存储在仓库元数据中。与依赖的groupId/artifactoryId/maven-metadata.xml不同,这里的仓库元数据为groupId/maven-metadata.xml

Maven在解析插件仓库元数据的时候,会默认使用org.apache.maven.pluginsorg.codehaus.mojo两个groupId。

也可以通过配置settings.xml让maven检查其他groupId上的插件仓库元数据:

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

基于该配置,maven就不仅仅会检查这两个地方的xml了。还会检查com/ssozh/plugins/maven-metadata.xml

<metadata>
	<plugins>
    	<plugin>
        	<name>Maven Dependency Plugin</name>
            <prefix>dependency</prefix>
            <artifactId>mavne-dependency-plugin</artifactId>
        </plugin>
    </plugins>
</metadata>

上述内容中可以看出,当Maven解析到dependency:tree这样的命令的时候,它首先基于默认的groupId归并所有插件仓库的元数据,其次检查归并后的元数据,找到对应的artifactId。然后结合当前元数据的groupId。最后使用上述方法获取version。这时就得到了完整的插件坐标。

如果该maven-metadata.xml没有记录该插件前缀,就去mojo然后去自定义,如果都没有,则报错。

7.9 小结

本章介绍了maven的生命周期和插件这两个重要的概念。不仅解释了生命周期背后的理念,还详细阐述了clean、default、site三套生命周期各自的内容。此外,本章还重点介绍了maven插件如何与生命周期绑定,以及如何配置插件行为,如何获取插件信息。

补充

generate-source:

上一篇:读 Clean Code,关于变量命名和可维护代码


下一篇:Linux系统加速提升Firefox浏览器速度的7中方法