第七章 生命周期和插件

别来无恙 提交于 2019-11-30 19:54:24

  除了坐标、依赖和仓库之外,Maven另外两个核心概念是声明周期和插件。Maven的生命周期是抽象的,其实际行为都由插件来完成,如package阶段的任务可能有maven-jar-plugin完成。生命周期和插件两者协同工作,密不可分。 ###7.1 何为生命周期###   Maven的生命周期就是为了对所有的构建过程进行抽象和统一,生命周期包含了项目的清理,初始化,编译,测试,打包,集成测试,验证和站点生成等几乎所有构建步骤,几乎所有项目的构建,能够映射到这样一个生命周期上。生命周期是抽象的,这也意味着生命周期本身不做任何实际的工作,在Maven设计中,实际的任务都是交由插件完成的,这种思想和设计模式中的模板方法非常类似,模板方法模式在父类中定义算法的整体结构,子类可以通过实现或者重写父类的方法来控制实际的行为,这样既保证了算法有足够的可扩展性,又能够严格控制算法的整体结构,如下的模板方法抽象类能够很好的体现Maven生命周期的概念:

Public void build(){
initialize(); compile(); test(); packagee(); integrationTest();deploy();
}

protected abstract void initialize();
protected abstract void compile();
protected abstract void test();
protected abstract void packagee();
protected abstract void integrationTest();
protected abstract void deploy();

  build()方法定义了整个构建的过程,依次是初始化,编译,测试,打包,集成测试和部署,但是这个类没有具体实现,他们交由之类去实现。Maven为大多数构建步骤编写并绑定了默认插件,例如:针对编译的插件有maven-compiler-plugin,针对测试的插件有maven-surefire-plugin等。用户几乎察觉不到插件的存在,但实际上编译由maven-compiler-plugin完成的,而测试由maven-surefire-plugin完成的,当有特殊需要的时候,也可以配置插件定制构建行为,甚至自己编写插件。
  Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建,此外,该机制还提供了足够的扩展空间,用户可以通过配置现有插件或者自行编写插件来自定义构建行为。 ###7.2 生命周期详解### ####7.2.1 三套生命周期####   生命周期分为相互独立的三大生命周期:分别是clean,default和site,clean是清理项目,default是构建项目,site是建立项目站点,每个生命周期包含一些阶段,这些阶段是有顺序的,后面阶段依赖于前面的阶段,Maven就是调用了这些生命周期阶段。 ####7.2.2 clean生命周期####   clean生命周期的目的是清理项目,它包括如下三个阶段:

  • pre-clean:执行一些清理前需要完成的工作。
  • clean:清理上一次构建的文件。
  • post-clean:执行一些清理后需要完成的工作。
    ####7.2.3 default生命周期####   default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分。
  • validate:验证工程是否正确,所有需要的资源是否可用。
  • initialize
  • generate-sources
  • process-sources
  • generate-resources
  • process-resources
  • compile:编译项目的主源码,即编译src/main/java目录下的Java文件至项目输出的主classpath中。
  • process-classes
  • generate-test-sources
  • process-test-sources
  • 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项目使用。
    ####7.2.4 site生命周期####   site生命周期的目的是建立和发布项目站点。
  • pre-site:执行一些在生成项目站点之前需要完成的工作。
  • site:生成项目站点文档。
  • post-site:执行一些在生成项目站点之后需要完成的工作。
  • site-deploy:将生成的项目站点发布到服务器上。
    ####7.2.5 命令行与生命周期####   各个生命周期相互独立,一个生命周期的阶段前后依赖。举例如下:
  • mvn clean 调用clean生命周期的clean阶段,实际执行pre-clean和clean阶段
  • mvn test 调用default生命周期的test阶段,实际执行test以及之前所有阶段
  • mvn clean install 调用clean生命周期的clean阶段和default的install阶段,实际执行pre-clean和clean,install以及之前所有阶段 ###7.3 插件目标###   我们已经知道,Maven的核心仅仅定义了抽象的生命周期,具体的任务是交给插件完成的,插件以独立的构件形式存在的。对于插件本身,为了能够复用代码,它往往能够完成多个任务。每个任务就是一个插件目标。 ###7.4 插件绑定###   Maven的生命周期与插件是相互绑定的,用以完成实际的构建任务。具体而言,是生命周期的阶段和插件的目标相互绑定,以完成一项具体的任务。如:项目编译这一任务,它对应了default生命周期中的compile这一阶段,而maven-compiler-plugin这一插件的compile目标能够完成该任务。 ####7.4.1 内置绑定####   Maven的生命周期与插件相互绑定,以完成实际的构建任务,内置绑定是指Maven核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。相对于clean和site来说,default生命周期与插件目标关系就显得复杂了,对于任何项目例如jar项目和war项目,他们的清理很站点生成任务是一样的,不过构建过程会有区别,例如jar项目打包成JAR包,而war项目打包成WAR包。项目的打包类型会影响构建的过程,因此default生命周期的阶段与项目的绑定关系由项目打包类型所决定,打包类型通过POM中的packaging元素定义。
      生命周期的内置插件绑定关系是:例如:生命周期阶段是compile,而插件目标是maven-compiler-pluging:compile 执行任务是编译主代码至输出目录。
    Maven拥有插件绑定关系的阶段,default生命周期还有很多其他阶段,默认他们没有绑定任何插件,因此没有任何实际行为。常见的打包方式有:war,pom,maven-plugin,ear等。default生命周期与插件目标的绑定关系请参考 http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html ####7.4.2 自定义绑定####   除了内置绑定意外,用户还能够自己将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目的构建过程中执行更附特色的任务一个常见的例子是创建项目的源码jar包,内置插件没有绑定这个任务,需要自行设置,maven-source-plugin可以帮我们完成,他的jar-no-fork目标就是将项目的主代码打包成jar文件,可以将其绑定到default生命周期的verify阶段上,在执行完集成测试后很安装构建之前创建源码jar包:
<bulid>
  <plugins>
    <plugin>
	  <groupId>org.apache.maven.plugins</groupId>
	  <artifactId>maven-source-plugin</artifactId>
	  <version>2.1.1</version>
	  <executons>
	    <execution>
		  <id>attach-sources</id>
		  <phase>verify</phase>
		  <goals>
		    <goal>jar-no-fork</goal>
		  </goals>
		</execution>
      </executons>
    </plugin>
  </plugins>
</bulid>  

  在POM的build元素下的plugins子元素中声明插件的使用,该例中用到的是maven-source-plugin,其groupId为org.apache.maven.plugins,而artifactId为maven-source-plugin,version为2.1.1,对于自定义绑定的插件,用户总是应该声明一个非快照版本,这样可以避免由于插件版本变化造成的构建不稳定性。executions下每一个execution子元素可以用来配置执行一个任务,id为attach-sources的任务,phrase配置将其绑定到verify生命周期阶段上,再通过goals配置指定要执行的插件目标,接着运行mvn verify结果会创建一个-sources.jar结尾的源码文件包。有时候无需配置verify也能将jar-no-fork绑定到verify中,执行mvn verify也没有问题,出现这样的原因是在很多插件的目标在编写时已经定义默认绑定阶段,可以使用maven-help-plugin查看插件详细信息,查看插件的默认绑定规范,运行:mvn help:describe - Dplugin = org.apache.maven.plugins: maven-source-plugin:2.1.1-Ddetail 该命令输出对应的插件的详细信息,在输出中可以看到关于目标jar-no-fork信息如下:Bound to phase为package,也就是如果不指定则默认绑定到package阶段。当插件目标绑定到不同的生命周期阶段的时候,其执行顺序会由生命周期的先后顺序决定,如果多个目标绑定到同一阶段,他们的顺序为插件声明的先后顺序。 ###7.5 插件配置###   用户还可以配置插件目标的参数,进一步调整插件目标所执行的任务,几乎所有的Maven插件目标都可以配置参数。通过命令行和POM配置这些参数。 ####7.5.1 命令行插件配置####   在Maven命令行中使用-D参数,并伴随一个参数键=参数值的形式,来配置插件目标的参数。例如maven-surefire-plugin提供了maven.test.skip参数,为true则跳过执行测试,命令为:$mvn install -Dmaven.test.skip = true ####7.5.2 POM中插件全局变量配置####   有些参数的值从项目创建到发布都不会改变,此时在POM文件中一次性配置就比重复输入来的方便,用户也可以对此插件做一个全局的配置,那么所有该插件目标的任务都会使用这些配置,例如我们通常需要配置maven-compiler-plugin告诉他编译java1.5版本的源文件,生成与JVM1.5兼容的字节码文件:

<build>
  <plugins>
    <plugin>
	  <groupId>org.apache.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>	

  此时不管是主代码编译还是测试代码编译都能够使用该配置,基于java1.5版本进行编译。 ####7.5.3 POM中插件任务配置####   用户可以为某个插件任务配置特定的参数,以maven-antrun-plugin为例,他有一个目标run,可以用来在Maven中调用Ant任务,用户将这个插件的run绑定到多个生命周期阶段上,再加以不同的配置,就可以让Maven在不同的生命阶段执行不同的任务。

<build>
  <plugins>
    <plugin>
	  <groupId>org.apache.maven.plugins</groupId>
	  <artifactId>maven-antrun-plugin</artifactId>
	  <version>1.3</version>
	  <executions>
	    <execution>
		  <id>ant-validate</id>
		  <phase>validate</phase>
		  <goals>
		    <goal>run</goal>
		  </goals>
		  <configuration>
			<tasks>
			  <echo>I'M 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'M bound to validate phase</echo>
			</tasks>
		  </configuration>
		</execution>
	  </executions>
	</plugin>
  </plugins>
</build>  

  插件全局配置中的configuration元素位于plugin元素下面,而这里的configration元素则位于execution元素下,表示这是特定任务的配置,而非插件整体的配置。这个ant-validate任务配置了一个echo Ant任务,向命令行输出一段文字,表示该任务是绑定到validate阶段的。第二个任务的id为ant-verify,它绑定到了verify阶段,同样它也输出一段文字到命令行,告诉该任务绑定到了verify阶段。 ###7.6 获取插件信息###   当用户遇到一个构建任务的时候,用户需要知道去哪里寻找合适的插件,找到之后还要了解该插件的配置点,由于插件文档不多,使用正确的插件进行配置很不容易。 ####7.6.1 在线插件信息####   基本上Maven插件的信息来自Apache和Codehaus,地址为: http://maven.apache.org/plugins/index.html, 插件下载地址: http://repol.maven.org/maven2/org/apache/maven/plugins。还有就是Codehaus的 http://mojo.codehaus.org/plugins.html,下载地址为 http://repository.codehaus.org/org/codehaus/mojo/
####7.6.2 使用maven-help-plugin插件描述####   除了文档还可以借助maven-help-plugin来获取插件的详细信息,可以运行如下命令:$mvn help:describe -Dplugin = org.apache.maven.plugins:maven-compiler-plugin:2.1,也就是执行maven-help-plugin的describe目标,结果中列出了插件的坐标,目标前缀和目标等,目标前缀为了在命令行直接运行插件,maven-compiler-plugin的前缀为compiler,在描述插件时可以省去版本信息,进一步简化为 $mvn help:describe -Dplugin = compiler,只想要了解某个目标信息可以加上goal参数:如$mvn help:describe -Dplugin = compiler -Dgoal = compile 想要更详细信息则加上detail参数:$mvn help:describe -Dplugin = compiler -Ddetail ###7.7 从命令行调用插件###   通过命令行运行mvn -h 来显示mvn命令帮助,就会看到如下信息 usage :mvn [options] [<goal(s)>] [<phase(s)>] options表示可用的选项,命令可以添加一个或多个goal和phase分别指插件目标和生命周期阶段,我们知道可以通过mvn命令激活生命周期阶段,从而执行绑定在生命周期阶段上插件目标,Maven还支持命令行调用插件目标,因为有些任务不适合绑定在生命周期上,如maven-help-plugin:describe不需要描述插件信息,又如maven-dependency-plugin:tree不需要显示依赖树,因此插件目标应该通过如下方式使用$mvn help:describe-Dplugin = compiler 等同于$mvn org.apache.maven.plugins:maven-help-plugin:2.1:describe -Dplugin = compiler $mvn dependency: tree 等同于 $mvn org.apache.maven.plugins:maven-dependency-plugin:2.1:tree上面两行中前面的命令比较整洁,这就是Maven引入了前缀的概念,help是maven-help-plugin的目标前缀,dependency是maven-dependency-plugin的前缀,有了前缀Maven就能找到对应的artifactId,除了artifactIdMaven还需要得到groupId和version才能精确定位到某个插件,虽然简化了命令但是当执行mvn help:system这样的命令,就不知道他到底执行了什么插件,该插件的groupId,artifactId和version分别是什么,接下来介绍其插件仓库的机制 ###7.8 插件解析机制### ####7.8.1 插件仓库####   与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中。在需要的时候,Maven会从本地仓库寻找插件,如果不存在,则从远程插件仓库查找。找到插件之后,再下载到本地仓库使用。
注:依赖的远程仓库 != 插件的远程仓库,Maven会区别对待他们。

  • Maven需要的依赖在本地仓库中不存在时,Maven会去配置的远程仓库中查找
  • Maven需要的插件在本地仓库中不存在时,Maven不会去这些远程仓库查找。
      Maven的插件远程仓库使用<pluginRepositories>和><pluginRepository>进行配置。Maven内置的插件远程仓库配置如下:它关闭了对SNAPSHOT的支持,防止不稳定的构建:
<pluginRepositories>  
        <pluginRepository>  
            <id>central</id>  
            <name>Maven Plugin Repository</name>  
            <url>http://repo1.maven.org/maven2</url>  
            <layout>default</layout>  
            <snapshots>  
                <enabled>false</enabled>  
            </snapshots>  
            <releases>  
                <updatePolicy>never</updatePolicy>  
            </releases>  
        </pluginRepository>  
</pluginRepositories> 

####7.8.2 插件的默认groupId####   Maven针对其官方的插件提供了一种简单的配置策略,其官方的groupId为:org.apache.maven.plugins,在配置的时候可以省略该配置,Maven在解析该插件的时候,会自动用默认的groupId补齐。但是一般情况下不推荐此种用法,因为只剩下了一行配置,而且容易对新手造成费解。 ####7.8.3 解析插件版本###   在用户没有提供插件版本时,Maven会自动解析插件版本,以为Maven项目的POM默认的父类都是超级POM,超级POM中所有核心插件都设定了版本,即使用户不输入,也会默认继承父类的值。那如果这个插件没有设定版本同时也不属于核心插件的范畴,Maven就会去检查仓库中可用的版本,仓库的元数据为插件目录下的maven-metadata.xml,Maven遍历本地和所有远程仓库,进行合并,能算出latest和release,前者表示最新版本后者表示最新非快照版本,Maven会解析为仓库中的最新版本,而这个版本可能就是快照版,此时就会又潜在问题,为了解决这个问题,Maven调整了解析机制,当插件没有声明版本,则使用release,即使这样如果旧的版本和新的版本发生了很大变化,此时也可能导致构建失败,所有建议在使用插件的时候一直显示的设定版本,这也解释了Maven为什么要在超级POM中为核心插件设定版本。 ####7.8.4 解析插件前缀####   解释Maven如何通过插件前缀解析得到插件的坐标   插件前缀与groupId:artifactId是一一对应的,这种匹配关系储存在仓库元数据中,与前面提到的groupId/artifactId/maven-metadata.xml不同,这里的仓库元数据是groupId/maven-metadata.xml,这里的groupId是什么?因为主要的插件位于apache/maven/plugins/和repository.codehaus.org/org/codehaus/mojo/相应的,Maven在解析插件仓库元数据时候,会默认使用org.apache.maven/plugins和org.codehaus.mojo两个groupId,也可以通过配置settings.xml让Maven检查其他groupId上的插件仓库元数据:

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

  此时Maven不仅仅会查询之前的两个插件地址,还会检查com/your/plugins/maven-metadata.xml,仓库元数据内容:

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

  上面那行的内容可以看到maven-dependency-plugin的前缀就是dependency,当执行dependency:tree命令时,首先会基于默认的groupId归并所有插件仓库的元数据org/apache/maven/plugins/maven-metadata.xml,其次找到对应的artifactId为maven-dependency-plugin,然后结合当前元数据groupId org.apache.maven.plugins,最后解析得到version,这时候就得到了完整的插件坐标,如果没有记录则接着去org/codehaus/mojo/maven-metadata.xml,以及用户自定义的插件组,如果所有元数据中都不包含该前缀,就会报错。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!