Maven实战(四)- 生命周期和插件
文章目录
- Maven实战(四)- 生命周期和插件
- 1.何为生命周期
- 2.生命周期
- 2.1.Clean生命周期
- 2.1.Default生命周期
- 2.3.Site生命周期
- 3.Maven插件
- 3.1.插件目标
- 3.2.插件绑定
- 3.2.1.内置插件
- 3.2.2.自定义插件
- 4.插件配置
- 4.1.命令行配置
- 4.2.POM中全局配置
- 4.3.POM中任务配置
- 5.插件解析机制
- 5.1.插件仓库
- 5.2.插件默认的groupId
- 5.3.解析插件版本
- 5.4.解析插件前缀
1.何为生命周期
在Maven出现之前,项目构建的生命周期存在着各式各样的方式,不同公司、不同项目往往采用自己独特的构建方式。手工执行编译、测试和部署的方法被广泛使用,同时也有一些项目编写了自动化脚本来执行这些任务。尽管手工方式和自动化脚本都在不断地进行构建工作,但由于缺乏统一的构建工具,每个项目的方式都略有差异。
这样的情况带来了一些挑战和问题。新加入项目的开发人员需要耗费时间来学习并适应项目的构建流程和脚本。随着时间的推移,构建脚本和流程可能变得复杂且难以维护。当不同项目需要进行集成时,由于构建方式的不一致,可能需要解决一些兼容性和配置问题。
幸运的是,Maven的出现解决了这些问题,它提供了一种标准化的项目构建和管理方式。Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。
Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务(如编译源代码)都交由插件来完成。这种思想与设计模式中的模板方法非常相似。模板方法模式在父类中定义算法的整体结构,子类可以通过实现或者重写父类的方法来控制实际的行为,这样既保证了算法有足够的可扩展性,又能够严格控制算法的整体结构。
2.生命周期
Maven的生命周期指的是Maven构建过程中的一系列阶段和插件目标的顺序执行流程。它定义了一组固定的阶段,每个阶段对应一个或多个插件目标。
Maven的生命周期包括三个固定的阶段:clean、default和site。
2.1.Clean生命周期
该生命周期用于清理项目,删除先前构建生成的文件。包括以下三个阶段:
- pre-clean: 在清理之前执行一些准备工作。
- clean: 清理生成的文件目录,将项目恢复到初始状态。
- post-clean: 在清理之后执行一些额外的工作。
2.1.Default生命周期
该生命周期是Maven的主要构建生命周期,用于构建和部署项目。它包括了项目的编译、测试、打包等一系列阶段,其中一些阶段是必需的,一些是可选的。默认生命周期包括以下阶段:
- validate: 对项目进行验证,确保项目正确。
- initialize: 执行一些初始化的任务,准备构建环境。
- generate-sources: 根据项目的配置和注解处理器(如lombok、JPA等)生成源代码。
- process-sources: 在这个阶段,Maven会处理源代码,如对生成的代码进行一些额外的操作或处理。其他插件可以在这个阶段执行额外的源代码处理任务,例如静态代码分析、代码生成或替换。
- generate-resources: 生成项目中的一些资源,如配置文件、属性文件等。
- process-resources: 处理项目的资源文件,例如复制、替换等操作。
- compile: 编译项目的源代码。
- process-classes: 对编译生成的字节码进行一些额外的处理。这可能包括字节码增强、字节码转换、资源文件的处理等。
- generate-test-sources:
- process-test-sources:
- generate-test-resources:
- process-test-resources:
- test-compile:
- process-test-classes:
- test: 运行项目的单元测试。
- package: 将编译后的代码打包成可分发的格式,如jar或war。
- pre-intergration-test: 集成测试前。
- intergration-test: 执行项目的集成测试。集成测试是指将各个模块或组件整合在一起进行测试,以验证它们之间的协作和交互是否正常。
- post-intergration-test: 集成测试后。
- verify: 对集成测试的结果进行验证,以保证质量。
- install: 将包安装到本地仓库,供其他项目使用。
- deploy: 将最终的包复制到远程仓库,供其他开发人员或项目使用。
2.3.Site生命周期
该生命周期用于生成项目的站点文档和报告。包括以下阶段:
- site: 生成项目文档的站点。
- post-site: 在站点生成之后执行一些额外的工作。
- site-deploy: 将生成的站点发布到远程服务器。
每个生命周期阶段都与插件目标关联,这些目标在执行阶段时被调用。当执行某个阶段时,Maven会依次调用与该阶段关联的插件目标。 通过定义和配置插件目标和生命周期,开发人员可以利用Maven的生命周期来管理项目的构建过程,自动执行各种构建任务,提高开发效率和项目质量。
3.Maven插件
3.1.插件目标
在进一步讨论插件和生命周期的绑定关系之前,有必要先了解插件目标(Plugin Goal)的概念。Maven的核心仅定义了抽象的生命周期,具体的任务是由插件完成的,并且插件以独立的构件形式存在。因此,Maven核心的分发包大小仅为不到3MB。在需要时,Maven会下载并使用插件。
插件本身往往能够完成多个任务,以便复用代码。例如,maven-dependency-plugin
可以基于项目的依赖关系执行多个任务,如分析项目依赖并帮助找出潜在的无用依赖、列出项目的依赖树以帮助分析依赖来源、列出项目所有已解析的依赖等等。如果为每个功能编写独立的插件将是不可取的,因为这些任务背后有很多可复用的代码。因此,这些功能被集成在一个插件中,每个功能对应一个插件目标。
3.2.插件绑定
3.2.1.内置插件
为了能让用户几乎不用任何配置就能构建Maven项目,Maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。
clean生命周期仅有pre-clean、clean和post-clean三个阶段,其中的clean与maven-clean-plugin:clean
绑定。maven-clean-plugin仅有clean这一个目标,其作用就是删除项目的输出目录。
生命周期阶段 | 插件目标 |
---|---|
pre-clean | |
clean | maven-clean-plugin:clean |
post-clean |
site生命周期有pre-site、site、post-site 和site-deploy四个阶段,其中,site和maven-site- plugin:site
相互绑定,site-deploy和maven-site-plugin:depoy
相互绑定。maven-site-plugin有很多目标,其中,site目标用来生成项目站点,deploy目标用来将项目站点部署到远程服务器上。
生命周期阶段 | 插件目标 |
---|---|
pre-site | |
site | maven-site-plugin:site |
post-site | |
site-deploy | maven-site-plugin:deploy |
相对于clean和site生命周期来说,default生命周期与插件目标的绑定关系就显得复杂一些。这是因为对于任何项目来说,例如jar项目和war项目,它们的项目清理和站点生成任务是一样的,不过构建过程会有区别。例如jar项目需要打成JAR包,而war项目需要打成WAR包。
生命周期阶段 | 插件目标 |
---|---|
process-resources | maven-resources-plugin:resources |
compile | maven-compile-plugin:compile |
process-test-resources | maven-resources-plugin:testResources |
test-compile | maven-compile-plugin:testCompile |
test | maven-surefire-plugin:test |
package | maven-jar-plugin:jar |
install | maven-install-plugin:install |
deploy | maven-deploy-plugin:deploy |
3.2.2.自定义插件
除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构建过程中执行更多更富特色的任务。
一个常见的例子是创建项目的源码jar包,内置的插件绑定关系中并没有涉及这一任务,因此需要用户自行配置。maven-source-plugin可以帮助我们完成该任务,它的jar-no-fork目标能够将项目的主代码打包成jar文件,可以将其绑定到default生命周期的verify阶段上,在执行完集成测试后和安装构件之前创建源码jar包。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在POM的build元素下的plugins子元素中声明插件的使用,该别中用到的是maven-source-plugin。其groupId为org.apache.maven.plugins
,这也是Maven官方插件的groupId,紧
接着artifactId为maven-source-plugin
,version为2.1.1
。对于自定义绑定的插件,用户总是
应该声明一个非快照版本,这样可以避免由于插件版本变化造成的构建不稳定性。
上述配置中,除了基本的插件坐标声明外,还有插件执行配置,executions下每个execution子元素可以用来配置执行一个任务。该例中配置了一个id为attach-sources的任务,通过phrase配置,将其绑定到verify生命周期阶段上,再通过godals配置指定要执行的插件目标。至此,自定义插件绑定完成。运行mvn verify
就能看到如下输出。
可以看到,当执行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的如下信息。
Bound to phase: package
表示默认的绑定生命周期阶段为package。
4.插件配置
在完成插件和生命周期的绑定之后,用户还可以配置插件目标的参数,进一步调整插件目标执行的任务,以满足项目的需求。几乎所有的Maven插件目标都具有一些可配置的参数,用户可以通过命令行或者POM配置等方式来进行参数配置。
4.1.命令行配置
许多插件目标的参数都支持从命令行进行配置,用户可以在Maven命令中使用-D
参数,并携带参数键=参数值的形式来配置插件目标的参数。 例如,maven-surefire-plugin提供了一个名为maven.test.skip
的参数,当其值为true时,就会跳过执行测试。
mvn install -Dmaven.test.skip=true
参数-D
是Java自带的,其作用是通过命令行设置一个Java系统属性。Maven简单地重用了该参数,在准备插件时检查系统属性的值,从而实现了插件参数的配置。
4.2.POM中全局配置
并不是所有的插件参数都适合从命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,对于这种情况,在POM文件中一次性配置就显然比重复在命令行输入要方便。
用户可以在声明插件的时候,对此插件进行一个全局的配置。也就是说,所有该基于该插件目标的任务,都会使用这些配置。例如,我们通常会需要配置maven-compiler-plugin告诉它编译Java8版本的源文件,生成与JVM8兼容的字节码文件。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
这样,不管绑定到compile阶段的maven-compiler-plugin:compile
任务,还是绑定到test-compiler阶段的maven-compiler-plugin:testCompiler
任务,就都能够使用该配置,基于Java8版本进行编译。
4.3.POM中任务配置
除了为插件配置全局的参数,用户还可以为某个插件任务配置特定的参数。以 maven-antrumn-plugin 为例,它有一个目标 run,可以用来在 Maven 中调用 Ant 任务。用户将 maven-antrun-plugin:run
绑定到多个生命周期阶段上,再加以不同的配置,就可以让 Maven 在不同的生命阶段执行不同的任务。
<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 阶段。
5.插件解析机制
为了方便用户使用和配置插件,Maven不需要用户提供完整的插件坐标信息,就可以解析得到正确的插件,Maven的这一特性是一把双刃剑,虽然它简化了插件的使用和配置,可一旦插件的行为出现异常,用户就很难快速定位到出问题的插件构件。例如mvn help:system
这样一条命令,它到底执行了什么插件?该插件的groupld、artifactId和version分别是什么?这个构件是从哪里来的?
5.1.插件仓库
与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中。在需要的时候,Maven会从本地仓库寻找插件,如果不存在,则从远程仓库查找。找到到插件之后,再下载到本地仓库使用。
不同于repositories及其repository子元素,插件的远程仓库使用pluginRepositories和pluginRepository配置。例如,Maven内置了如下的插件远程仓库配置。
<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>
除了pluginRepositories和pluginRepository标签不同之外,其余子元素表达的含义与之前《Maven实战(三)- Maven仓库》中讲解的完全一致。
5.2.插件默认的groupId
在POM中配置插件的时候,如果该插件是Maven的官方插件(即如果其groupld为org.apache.maven.plugins
),就可以省略groupId配置。
5.3.解析插件版本
同样是为了简化插件的配置和使用,在用户没有提供插件版本的情况下,Maven会自动解析插件版本。解析过程与《Maven实战(三)- Maven仓库》中依赖解析基本一致。
5.4.解析插件前缀
插件前缀与groupld:artifactId
是一一对应的,这种匹配关系存诸在仓库元数据中。与之前提到的groupId/artifactld/maven-metadata.xml
不同,这里的仓库元数据为groupld/maven-metadata.xml
。
基本上所有主要的Maven插件都来自Apache和Codehaus。
Maven在解析插件仓库元数据的时候,会默认使用org.apache.maven.plugins和org.codehaus.mojo两个groupId。也可以通过配置 settings.xml 让Maven检查其他groupld上的插件仓库元数据。
<settings>
<pluginGroups>
<plugin>com.xxx.plugins</plugin>
</pluginGroups>
</settings>
基于该配置,Maven就不仅仅会检查org/apache/maven/plugins/maven-metadata.xml
和
org/codehaus/mojo/maven-metadata.xml
,还会检查 com/your/plugkins/maven-metadata. xml
。
通过这个链接https://repo1.maven.org/maven2/org/apache/maven/plugins/可以查看插件仓库元数据的内容。
<metadata>
<plugins>
<plugin>
<name>Apache Maven Clean Plugin</name>
<prefix>clean</prefix>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
<plugin>
<name>Apache Maven Compiler Plugin</name>
<prefix>compiler</prefix>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</metadata>
上述内容是从中央仓库的org.apache.maven.plugins groupld
下插件仓库元数据中截取的一些片段,从这段数据中就能看到maven-clean-plugin的前缀为clean,maven-compiler-plugin的前缀为compiler。
>注:内容源于《Maven实战》(许晓斌著)