Maven内部运行原理

Posted tooy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Maven内部运行原理相关的知识,希望对你有一定的参考价值。

maven至今还是Java编程语言构建的事实标准,大部分项目还在使用maven来进行构建,因此了解maven内部运行的原理对定位和分析问题还是很有裨益的。本篇文章主要介绍一些maven内部运行过程中的一些基本概念,相信看完后,对那么些刚刚接触maven的读者来说maven将不再陌生。
  在具体分析项目构建的过程前,需要了解maven的一些基本概念,这些概念十分重要,请务必理解清楚后再看下文。基本概念主要有:POM,Lifecycle。这两个概念又会包含一些小的概念,下文会逐步讲解。
  POM: 注意这里的POM不是maven中构建过程使用的配置文件pom.xml,但他们之间还是有联系的。POM的全称是Project Object Model,用通俗点的话说就是对要构建的项目进行建模,将要构建的项目看成是一个对象(Object),后文就使用PO来指代这个对象。既然是一个对象,那么PO有哪些属性呢?在maven中一个项目都是用一个唯一的坐标(coordinate)来表示,坐标由groupId, artifactId, version, classifier, type这五部分组成。这样来说PO应该也要具备坐标属性。另外一方面,一个项目肯定不是孤立存在的,可能依赖于其他的一些项目,那么也就是说PO应该具备dependencies这个属性,用来表示其所依赖的外部项目。我们可以尝试一下用Java代码来描述下PO这个对象:
class PO{ 
    private String groupId;
    private String artifactId; 
    private String version; 
    private String classifier; 
    private String type; 
    private Set<PO> dependencies; 
}

我们知道XML的表达能力是很强大的,一个Java中的对象是可以用XML来描述,例如一个上面定义的PO对象则可以用下面的XML来描述(表达有不规范之处,理解其中的含义即可):

<PO> 
    <groupId></groupId> 
    <artifactId></artifactId> 
    <version></version> 
    <classifier><classifier> 
    <type></type> 
    <dependencies> 
        <PO></PO> <PO></PO> ... 
    </dependencies> 
</PO>
是不是好像和什么有点类似?对,就是pom.xml。pom.xml就是PO对象的XML描述。上面的PO定义还不完整,我们继续看下PO还有什么其他的属性。我们知道在Java中类是可以继承的,一个对象在创建的时候会同时创建其父类对象,类似的,PO对象也有其父对象,用parent属性来表示,并且PO对象会继承其父对象的所有属性。另外一方面,一个项目可能根据不同职责分为多个模块(module),所有模块其实也就是一个单独的项目,只不过这些项目会使用其父对象的一些属性来进行构建。我们将这些新的属性加到PO的定义中去:
class PO{ 
    private String groupId; 
    private String artifactId; 
    private String version; 
    private String classifier; 
    private String type; 
    private Set<PO> dependencies; 
    private PO parent; 
    private Set<PO> modules; 
}

再将这个定义用XML语言表示一下:

<PO>
    <parent></parent>
    <groupId></groupId>
    <artifactId></artifactId>
    <version></version>
    <classifier><classifier>
    <type></type>
    <dependencies>
        <PO></PO> <PO></PO> ...
    </dependencies>
    <modules> ... </modules>
</PO>

是不是越来越像pom.xml了?对,这就是pom.xml的由来。再说一遍:pom.xml就是PO对象的XML描述。到此为止,相信你再看pom.xml文件时,会有一个全新的认识。

  上面说的这些PO属性是项目的一些固有属性,到目前为止,我们还没有涉及项目的构建过程。构建过程对应的是PO对象的build属性,那么你应该马上想到,在pom.xml中就是<build>元素中的内容。这里就有引入maven中第二个核心概念:Lifecycle。Lifecycle直译过来就是生命周期。我们平常会接触到哪些周期呢?一年中春夏秋冬就是一个周期。一个周期中可能分为多个阶段,比如这里的春夏秋冬。在maven中一个构建过程就对应一个Lifecycle,这个Lifecycle也分为多个阶段,每个阶段叫做phase。你可能会问,那这个Lifecycle中包含多少个phase呢?一个标准的构建Lifecycle包含了如下的phase:

validate: 用于验证项目的有效性和其项目所需要的内容是否具备 
initialize:初始化操作,比如创建一些构建所需要的目录等。 
generate-sources:用于生成一些源代码,这些源代码在compile phase中需要使用到 
process-sources:对源代码进行一些操作,例如过滤一些源代码 
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:进行单元测试 
prepare-package:打包前置操作 
package:打包 
pre-integration-test:集成测试前置操作 
integration-test:集成测试 
post-integration-test:集成测试后置操作 
install:将打包产物安装到本地maven仓库 
deploy:将打包产物安装到远程仓库
在maven中,你执行任何一个phase时,maven会将其之前的phase都执行。例如 mvn install,那么maven会将deploy之外的所有phase按照他们出现的顺序一次执行。
  Lifecycle还牵涉到另外一个非常重要的概念:goal。注意上面Lifecycle的定义,也就是说maven为程序的构建定义了一套规范流程:第一步需要validate,第二步需要initialize... ... compile,test,package,... ... install,deploy,但是并没有定义每一个phase具体应该如何操作。这里的phase的作用有点类似于Java语言中的接口,只协商了一个契约,但并没有定义具体的动作。比如说compile这个phase定义了在构建流程中需要经过编译这个阶段,但没有定义应该怎么编译(编译的输入是什么?用什么编译javac/gcc?)。这里具体的动作就是由goal来定义,一个goal在maven中就是一个Mojo(Maven old java object)。Mojo抽象类中定义了一个execute()方法,一个goal的具体动作就是在execute()方法中实现。实现的Mojo类应该放在哪里呢?答案是maven plugin里,所谓的plugin其实也就是一个maven项目,只不过这个项目会引用maven的一些API,plugin项目也具备maven坐标。
  在执行具体的构建时,我们需要为lifecycle的每个phase都绑定一个goal,这样才能够在每个步骤执行一些具体的动作。比如在lifecycle中有个compile phase规定了构建的流程需要经过编译这个步骤,而maven-compile-plugin这个plugin有个compile goal就是用javac来将源文件编译为class文件的,我们需要做的就是将compile这个phase和maven-compile-plugin中的compile这个goal进行绑定,这样就可以实现Java源代码的编译了。有点绕,多看几遍。那么有人就会问,在哪里绑定呢?答案是在pom.xml<build>元素中配置即可。例如:
<build> 
    <plugins> 
        <plugin> 
            <artifactId>maven-myquery-plugin</artifactId> 
            <version>1.0</version> 
            <executions> 
                <execution> 
                    <id>execution1</id> 
                    <phase>test</phase> 
                    <configuration> 
                        <url>http://www.foo.com/query</url> 
                        <timeout>10</timeout> 
                        <options> 
                            <option>one</option> 
                            <option>two</option> 
                            <option>three</option> 
                        </options> 
                    </configuration> 
                    <goals> 
                        <goal>query</goal> 
                    </goals> 
                </execution> 
            </executions> 
        </plugin> 
    </plugins> 
</build>
就将maven-myquery-plugin中的query这个goal绑定到了test这个phase,后续在maven执行到test phase时就会执行query goal。还有有人可能会问,我都没有指定Java源文件的位置,编译啥?这就引出了maven的design principle。在maven中,有一个非常著名的principle就是convention over configuration(约定优于配置)。这一点和ant有非常大的区别,例如使用ant来进行编译时,我们需要指定源文件的位置,输出文件的位置,javac的位置,classpath... ...在maven中这些都是不需要,若没有手动配置,maven默认从<项目根目录>/src/main/java这个目录去查找Java源文件,编译后的class文件会保存在<项目根目录>/target/classes目录。在maven中,所有的PO都有一个根对象,就是Super POM。Super POM中定义了所有的默认的配置项。Super POM对应的pom.xml文件可以在maven安装目录下lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml中找到。用一张图来表示maven Lifecycle,phase,goal之间的关系:

 

以上是关于Maven内部运行原理的主要内容,如果未能解决你的问题,请参考以下文章

EasyClick 运行代码片段出Null

EasyClick 运行代码片段出Null

ForegroundService没有从片段开始?

Maven实战与原理分析:Maven插件运行原理 + 常见插件汇总

片段内部静态类和gradle问题

在节点上的函数内部运行方法