maven
大家都很熟悉,平时在开发中我们用它来管理项目结构、引入依赖以及项目构建,在开发完成之后也可有用它来打包并将制品发布到私服,也可以根据选择进行站点发布以版本发布(release),maven
虽然并不能在代码层面给予帮助,但是它却能做一些最一些其他很酷的事,所以熟练的掌握maven
也是至关重要的。
最近在工作中遇到了maven
相关问题,发现对maven
的认识有一些偏差,于是又重新研究了一下关于生命周期、插件以及profile
的相关内容,收获颇多,特此记录。
我们平时会使用到一些指令,如mvn clean
,但很有可能仅仅停留在知道这些指令能做什么,但并不了解背后的原理,在工作中如要对修改项目maven
配置,或者自己开发一些插件就不知道怎么办了。这些指令涉及到的知识点主要有两个:生命周期以及插件,本文主要介绍他们的部分精华内容。
maven
的生命周期共有三个,分别是clean
、default
以及site
,每个生命周期由不同的phase
(阶段)构成,而每个phase
按照约定又都是按照固定顺序执行的。
该生命周期主要负责处理项目的清理,共有三个phase
,按照顺序分别如下:
pre-clean
:clean
:执行清理操作,删除上一次构建生成的文件。post-clean
pre
的意思为在…之前执行,post
为在…之后执行,其他周期类似的也为同样的概念。
该生命周期主要构建操作,如编译、打包、部署等,包含的phase
共23个,常用的按照顺序分别如下:(完整phase见这里)
compile
:编译项目源文件。test-compile
:编译项目测试源文件。test
:执行单元测试。package
:将编译后的文件按照指定的形式打包,如jar
、war
等,其中不包含测试文件。install
:将打包后的制品安装到本地仓库,可供本地其他项目使用。deploy
:将打包后的制品发布到指定远程仓库,可供其他项目使用。 该生命周期主要负责站点的创建。共有四个phase
,按照顺序分别如下:
pre-site
site
:在本地生成站点文档。post-site
site-deploy
:将生成的站点文档部署到指定服务器。 执行同一个生命周期的phase
会按照顺序先执行排在它之前的其他phase
,如执行default
周期中的package
,会自动按照顺序执行compile
、test-compile
、test
等,如果都顺利执行成功,那么最后才会执行package
,所以平时在执行指令时,同一周期的phase
不需要依次指定,只需要执行排在最后边的周期即可。
所有的生命周期定义以及顺序,都在maven
源码中maven-core/src/main/resources/META-INF/plexus/components.xml
中。
以上我们又重新的回顾了一遍生命周期以及phase
,其中phase
并不会真正的做一些工作,真正执行工作的是插件(plugin
)。
Maven is - at its heart - a plugin execution framework; all work is done by plugins.
如同上述官方文档中描述的一样,maven
可以看作是一个插件执行框架,因为maven
中所有的工作都是由插件来完成的。我们平时执行的指令最终也都是执行对应的插件,如mvn clean
,最终的工作是由maven-clean-plugin
插件来完成的。
插件的本质也都是jar,如同项目中引用的其他依赖一样,插件的坐标也与普通坐标相同,如org.apache.maven.plugins:maven-clean:plugin:3.1.0
。maven
的官方插件都在本地仓库的org/apache/maven/plugins
下。
下图为maven-clean-plugin
插件源代码截图:
不通版本的maven
中插件的默认版本也不同,会在maven
源码中进行指定,其中clean
和site
生命周期的插件定义在maven-core/src/main/resources/META-INF/plexus/components.xml
中;而default
生命周期的插件,由于package
选项的不同可能对应不同的插件,它定义在maven-core/src/main/resources/META-INF/default-bindings.xmll
中。
如maven-3.8.4
中指定的maven-clean-plugin
版本为2.5.0
而不是最新的3.1.0
,如下图所示:
不同与普通依赖的引用,项目中插件的引用在pom.xml
中是通过
下的
中进行配置的,可以配置插件的版本、参数以及一些其他相关配置。
如通过IDEA
创建maven
项目时,如选择选择create from archetype
,那么生成的pom.xml
可能如下,该配置主要指定了插件版本:
可以看出执行clean
指令pmaven-clean-plugin:3.1.0
了。
插件的坐标有时候会经常出现在命令行中,如果输入完整坐标,那会显得太过冗长,如:
mvn org.apache.maven.plugins:maven-clean-plugin:3.1.0:clean
为了方便使用,maven
提供了前缀的概念,分为自动识别
以及手动指定
。
如果插件的artifactId
符合以下模式,那么maven
会自动识别,将该插件与插件进行映射:
maven
官方插件:maven-${prefix}-plugin
。(此命名规则为maven
官方插件预留,自定义插件禁止使用)${prefix}-maven-plugin
如官方插件apache-clean-plugin
、apache-compile-plugin
等,我们就可以直接使用clean
、compile
,而不需要指定冗长的坐标。
再如有自定义插件hello-maven-plugin
,那么则可以使用hello
来方便使用该插件。
手动识别是针对我们自己开发的插件,可以通过配置去指定该插件的前缀。由于本文的内容不是介绍自定义插件的开发,固只列出相关前缀的配置
通过如上配置,在使用时就可以使用myPrefix
来替代坐标了。
一个插件可以拥有若干个goal
(目标),这些goal
对于插件来说,就好像方法于类。
调用的语法为:
mvn PLUGIN:GOAL
如常用的mvn release:prepare
,mvn dependency:analyze
等,此时就会直接执行release
插件中名为prepare
的goal
来执行任务。
除了可以直接调用外,goal
也可以绑定在一个或多个phase
上。如若此,则执行到该phase
时会执行该goal
。
而相对于phase
来说,如果该phase
未绑定goal
,那么该phase
不会执行。如果绑定了一个或多个goal
,那么就会按照顺序依次执行他们。
maven
内置了一些goal
的绑定,也就是这些goal
会内置的绑定在某个phase
上,我们在执行phase
的时候也就会自动执行这些goal
,一些常用的绑定关系如下:
phase | goal |
---|---|
clean | clean:clean |
phase | goal |
---|---|
compile | compile:compile |
test-compile | compile:testCompile |
package | ejb:ejb or ejb3:ejb3 or jar:jar or par:par or rar:rar or war:war |
install | install:install |
deploy | deploy:deploy |
phase | goal |
---|---|
site | site:site |
site-deploy | site:deploy |
内置goal的绑定同上述的插件一起定义在源码中的maven-core/src/main/resources/META-INF/plexus/components.xml
以及maven-core/src/main/resources/META-INF/plexus/default-bindings.xml
中,这也就是为什么项目中可能没有配置如maven-clean-plugin
、maven-compile-plugin
等插件,在执行指令时依旧可以正常执行的原因。
前文说过,如果phase
没有绑定goal
那么该phase
不会执行,再由上面的默认绑定关系可知,诸如pre-clean
以及post-clean
等phase
默认情况下不会执行,因为没有绑定goal
。这些phase
可有理解为预留,可以根据需要在pom
中去配置相应绑定的goal
,来达到在某行为之前或之后执行我们的目标任务。
在声明的
标签下进行插件的具体配置,可能如下所示:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-antrun-pluginartifactId>
<version>1.8version>
<executions>
<execution>
<id>manifestid>
<phase>generate-resourcesphase>
<goals>
<goal>rungoal>
goals>
<configuration>
<tasks>
<manifest file="src/main/resources/META-INF/MANIFEST.MF">
<attribute name="Project-Version" value="${project.version}" />
<attribute name="Application-Name" value="My Application" />
manifest>
tasks>
configuration>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-javadoc-pluginartifactId>
<version>3.2.0version>
<executions>
<execution>
<phase>prepare-packagephase>
<goals>
<goal>javadocgoal>
goals>
execution>
executions>
plugin>
plugins>
build>
上述配置了两个插件,antrun
以及javadoc
(插播复习一个小知识:插件的前缀),想要执行的工作很简单,antrun
负责生成MANIFEST.MF
清单文件,并配置了生成的内容以及路径等;javadoc
负责生成java文档。
其中还有一部分配置将phase
以及goal
绑定了起来
<execution>
<phase>...phase>
<goals>
<goal>...goal>
<goal>...goal>
goals>
<execution>
也就是说,当mvn
执行generate-resources
阶段时会调用antrun:run
,而当执行prepare-package
阶段时会调用javadoc:javadoc
,如执行指令mvn compile
,像上文章讲解的一样,会优先执行compile
顺序之前的phase
,其中就包含generate-resources
,执行到该phase
时就会调用绑定的antrn:run
目标了。
再如执行mvn package
指令,会优先执行generate-resources
,之后在执行prepare-package
,并执行绑定的goal
这样一来我们在平时的编译、打包、部署等操作时,就能自动的执行一些我们想要的任务,而不需要我们手动去执行他们,可以极大程度的提高构建易用性与速度,一般实际的项目都会广泛的采用这种方式。
那么怎么样知道一个插件拥有哪些goal
呢
maven
官方插件,可以查看maven
官网,之后点击插件名,就可以看到具体的goal
有哪些了,还会有参数说明以及使用demo。 help:describe
来查看某一插件的具体情况,如# mvn help:describe -Dplugin=apache.javadoc.plugin
mvn help:describe -Dplugin=javadoc
有些情况下我们在执行某phase
时不想执行某些goal
,那么需要查看该goal
是否提供相应参数。如我们想在执行package
的时候跳过test
单元测试,那么我们需要查看test
绑定的goal
即surefile:test
是否有可以跳过的参数。文档中表示,可以-Dmaven.test.skip=true
或-DskipTests
来跳过单元测试,区别是前者即跳过测试又不编译测试文件,而后者只是跳过,但是会编译测试文件。
所以这里maven.test.skip
的test
指的其实并不是插件,而是参数名称为maven.test.skip
。类似的还有如跳过compile
插件需要使用参数-Dmaven.main.skip=true
来设置,具体其他的goal
需要查看官方文档。
也可以在pom.xml
中配置,这样就不用再输入指令时加参数了,会自动添加配置的参数,如下所示:
需要注意的是,跳过的永远是goal, 而不是phase。如同上文描述,一个phase
可能绑定很多goal
,跳过一个不代表所有的都跳过。
父子多模块项目,如一个父项目parent
以及三个子模块childA
、childB
以及childC
,在执行phase
时,有两个顺序需要明确:
模块的执行顺序由以下两部分决定:
1. 基础顺序,即在父pom
中声明模块的顺序,如我们在pom.xml
中配置如下
<modules>
<module>moduleAmodule>
<module>moduleBmodule>
<module>moduleCmodule>
modules>
可以看到在执行指令时,最优先执行是模块,其次会按照pom.xml中指定的模块顺序执行。
2. 在基础顺序之上的依赖关系。假设moduleA
依赖moduleB
,我们还按照上例声明的模块顺序进行试验,配置情况以及结果如下:
<artifactId>moduleAartifactId>
<dependencies>
<dependency>
<groupId>${project.groupId}groupId>
<artifactId>moduleBartifactId>
<version>${project.version}version>
dependency>
dependencies>
可有看到最终的结果是B
A
C
,也比较合理,比如打包等,不先把依赖的包编译好,可能就会有问题。
一个模块的所有phase
执行完之后,才会继续执行下一个模块的phase
,而不是执行所有模块的一个phase
,再执行所有模块的下个phase
,理解这点对于平时项目的构建是十分必要的。其流程如下图所示: