彻底理解Maven

更新于:2024-10-29     浏览:51351 次  

在 Maven 出现之前,项目开发犹如是刀耕火种的上古时代,我们先来看看一个 Java 项目需要的东西。首先,我们要确定项目的目录结构。例如,src 目录存放 Java 源码,resources 目录存放配置文件,bin 目录存放编译生成的 .class 文件。其次,我们需要确定引入哪些依赖包。例如,如果我们需要用到 commons logging,我们就必须把 commons logging 的 jar 包放入 classpath 。如果我们还需要 log4j,就需要把 log4j 相关的 jar 包都放到 classpath 中,这些就是依赖包的管理。


虽然这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的 Java 项目管理和构建工具。Maven 就是是专门为 Java 项目打造的管理和构建工具,它的主要功能有:

  • 提供了一套标准化的项目结构。使用 Maven 管理的 Java 项目都有着相同的项目结构:
    (1)有一个 pom.xml 用于维护当前项目都用了哪些 jar 包
    (2)所有的 java 功能代码都放在 src/main/java 下面
    (3)所有的 java 测试代码都放在 src/test/java 下面

  • 提供了一套标准化的构建流程(编译,测试,打包,发布……)。

  • 提供了一套依赖管理机制。

1、Maven基本概念

Maven 有两个最基本的概念:pom 和 lifecycle,这里的 pom 不是 Maven 构建过程中使用的 pom 文件,但它们之间有联系。


POM 全称为 Project Object Model,简单地说就是要对构建的项目进行建模,将要构建的项目看成是一个对象 Object,既然是一个对象,这个对象有哪些属性呢?在 Maven 中一个项目使用唯一的坐标来表示,包括 groupId、artifactId、version、classifier 和 type(也叫 packaging)这五部分。另外,一个项目肯定不是孤立存在的,可能会依赖其他项目,也就是说这个对象应该还有 dependencies 属性,用 PO 表示构建对象,使用 Java 代码描述这个对象的话:


class PO{
    private String groupId;
    private String artifactId;
    private String version;
    private String classifier;
    private String type;
    private Set<PO> dependencies;
}

xml 具有很强的表达能力,一个 Java 对象可以用 xml 来描述,用 xml 表达上面这个 Java 对象可以为:

<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 定义还不完整,我们知道在 Java 中类是可以继承的,PO 也有继承关系,PO 对象存在父类对象,用 parent 表示,它会继承父类对象的所有属性。 除此以外,一个项目可能根据不同职责分为多个模块(module),所有模块其实也就是一个单独的项目,只不过这些项目会使用其父对象的一些属性来进行构建。我们将这些新的属性加到 PO 的定义中去:

class PO{
    private String groupId;
    private String artifactId;
    private String version;
    private String classifier;
    private String type;
    private Set dependencies;
    private PO parent;
    private Set 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 其实就是对 PO 对象的 xml 描述!


2、构建

项目的构建过程对应的是 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 Ordinary 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之间的关系:



3、插件

上面我们提到,Maven 将所有项目的构建过程统一抽象成一套生命周期: 项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成 ...,几乎所有项目的构建都能映射到这一组生命周期上。但生命周期是抽象的(Maven的生命周期本身是不做任何实际工作), 任务执行均交由插件完成。其中每个构建步骤都可以绑定一个或多个插件的目标,而且 Maven 为大多数构建步骤都编写并绑定了默认插件。当用户有特殊需要的时候,也可以配置插件定制构建行为,甚至自己编写插件。


4、生命周期

Maven 拥有三套相互独立的生命周期: clean、default 和 site,而每个生命周期包含一些 phase 阶段,阶段是有顺序的,并且后面的阶段依赖于前面的阶段。而三套生命周期相互之间却并没有前后依赖关系,即调用 site 周期内的某个 phase 阶段并不会对 clean 产生任何影响。


clean


clean生命周期的目的是清理项目。例如:$ mvn clean


default


default生命周期定义了真正构建时所需要执行的所有步骤。例如:$ mvn clean install


site


site生命周期的目的是建立和发布项目站点。Maven 能够基于 POM 所包含的信息自动生成一个友好的站点,方便团队交流和发布项目信息。例如:$ mvn clean deploy site-deploy


这三个 lifecycle 定义了其包含的 phase。Maven 会在这三个 lifecycle 中匹配对应的 phase。当执行某个 phase 时,Maven 会依次执行在这个 phase 之前的 phase。


5、插件与生命周期

生命周期的阶段 phase 与插件的目标 goal 相互绑定,用以完成实际的构建任务。而对于插件本身,为了能够复用代码,它往往能够完成多个任务,这些功能聚集在一个插件里,每个功能就是一个目标。


例如:$ mvn compiler:compile。冒号前部分是插件前缀,后面部分是该插件目标,即: maven-compiler-plugin 的 compile目标。而该目标绑定了 default 生命周期的 compile 阶段。它们的绑定能够实现项目编译的目的。


提醒:Maven 命名有要求,Maven 团队维护官方插件的保留命名方式是 maven-<myplugin>-plugin。

6、插件的内置绑定

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






上图只列出了打包方式为jar且拥有插件绑定关系的阶段(packaging 定义了Maven项目打包方式, 通常打包方式与所生成构件扩展名对应,有jar(默认)、war、pom、maven-plugin等, 其他打包类型生命周期的默认绑定关系可参考: Built-in Lifecycle Bindings、Plugin Bindings for default Lifecycle Reference




7、插件的自定义绑定

除了内置绑定以外,用户还能够自定义将某个插件目标绑定到生命周期的某个阶段上。如创建项目的源码包,maven-source-plugin插件的jar-no-fork目标能够将项目的主代码打包成jar文件, 可以将其绑定到verify阶段上:


<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.0</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

executions下每个execution子元素可以用来配置执行一个任务。


8、聚合与继承

Maven 的聚合特性(aggregation)能够使项目的多个模块聚合在一起构建,而继承特性(inheritance)能够帮助抽取各模块相同的依赖、插件等配置,在简化模块配置的同时,保持各模块一致。


模块聚合


随着项目越来越复杂,需要解决的问题越来越多、功能越来越重,我们更倾向于将一个项目划分几个模块并行开发,如:将 feedcenter-push 项目划分为 client、core 和 web 三个模块,而我们又想一次构建所有模块,而不是针对各模块分别执行 $ mvn 命令。于是就有了 Maven 的模块聚合,将 feedcenter-push 作为聚合模块将其他模块聚集到一起构建。


聚合POM


聚合模块 POM 仅仅是帮助聚合其他模块构建的工具,本身并无实质内容。


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.vdian.feedcenter</groupId>
    <artifactId>feedcenter-push</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0.SNAPSHOT</version>

    <modules>
        <module>feedcenter-push-client</module>
        <module>feedcenter-push-core</module>
        <module>feedcenter-push-web</module>
    </modules>

</project>

通过在一个打包方式为 pom 的 Maven 项目中声明任意数量的 module 以实现模块聚合。

  • packaging: 打包为 pom,否则无法聚合构建。
  • modules: 实现聚合的核心,module 值为被聚合模块相对于聚合 POM 的相对路径,每个被聚合模块下还各自包含有 pom.xml、src/main/java、src/test/java等内容, 离开聚合 POM 也能够独立构建。

若<packaging>元素的内容是 jar,那么我们很好理解,也就是说这个项目最终会被打包成一个 jar 包,那 <packaging> 元素为 pom 又是什么意思呢?从字面上的意思来看,这个项目将打包成一个 pom 。我们不妨去 Maven 仓库里去瞧瞧(前提是已经在项目下运行了 mvn install 命令)。可以发现这个文件其实和项目中的 pom.xml 是同一个文件,这样做的目的是什么呢?上面我们说过 PO 对象也是有继承关系的,<packaging>pom</packaging> 的作用就在这里,这就是 Maven 中 project inheritance 的概念。当实际执行 Maven 命令的时候,会根据 project inheritance 关系对项目的 pom.xml 进行转化,得到真正执行时所用到的 pom.xml,即所谓的 effective pom,因此可以得到一个结论:所有 <packaging> 元素为 pom 的项目其实并不会输出一个可供外部使用,类似于 jar 包的东西。这类项目的作用有两个:管理子项目和管理继承属性。


管理子项目


例如,api 和 biz 是 echo 项目的两个 module。若没有 echo 这个父项目,我们需要到 api 和 biz 两个项目下分别执行 mvn install 命令才能完成整个构建过程,而有了 echo 这个父项目之后,我们只需在 echo 项目中执行 mvn install 即可,Maven 会解析 pom.xml,发现该项目有 api 和 biz 两个 module,它会分别到这两个项目下去执行 mvn install 命令。当 module 数量比较多的时候,能大大提高构建的效率


管理继承属性


比如A和B都需要某个依赖,那么在父类项目的 pom.xml 中声明即可,因为根据 PO 对象的继承关系,A和B项目会继承父类项目的依赖,这样就可以减少一些重复的输入。


effective pom 包含了当前项目的 PO 对象,直到 Super POM 对应的 PO 对象中的信息。要看一个项目的 effective pom,只需在项目中执行命令即可查看:

mvn help:effective-pom

这里顺带说一句,有的人可能不理解上面这个命令是什么意思。Maven 命令的语法为:

mvn [plugin-name]:[goal-name]

这个命令采用了缩写的形式,其全称是这样的:

org.apache.maven.plugins:maven-help-plugin:2.2:effective-pom

此命令以分号为分隔符,包含了 groupId,artifactId,version,goal 四部分。若 groupId 为 org.apache.maven.plugins 则可以使用上述的简写形式,也就是说和下面的命令是等价的:

mvn help:effective-pom
mvn org.apache.maven.plugins:maven-help-plugin:2.2:effective-pom

都是执行了 maven-help-plugin 这个 plugin 中的 effective-pom 这个 goal。


我们知道一个 plugin 中可以包含多 个goal,goal 可以绑定到 lifecycle 中的某一个 phase,这样在执行这个 phase 的时候就会调用该 goal。那些没有绑定到 phase 上的 goal 应该如何执行呢?这就是 mvn [goal(s)]


这里的 goal 也就是官方文档中所说的 standalone goal,也就是说若一个 plugin 中的某个 goal 没有和一个 phase 进行绑定,可以通过这种方式来执行。可能有的人使用过:

mvn dependency:tree

这条命令其实就是单独执行一个 goal,这个 goal 的作用是分析该工程的依赖并使用树状的形式打印出来。这里的 dependency:tree 其实是一个简写的形式,其完整形式是:

mvn org.apache.maven.plugins:maven-dependency-plugin:<版本号信息>:tree

也就是说单独执行一个 goal 的方式是:

mvn <groupId>:<artifactId>:<version>:<goal>

每次都要敲这么长一串命令是很繁琐的,因此才有了上述的简写的形式。


模块继承


在面向对象中, 可以通过类继承实现复用,在 Maven 中同样也可以创建 POM 的父子结构, 通过在父 POM 中声明一些配置供子 POM 继承来实现复用与消除重复。


父 POM


与聚合类似,父 POM 的打包方式也是 pom,因此可以继续复用聚合模块的 POM ,这也是在开发中常用的方式:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.vdian.feedcenter</groupId>
    <artifactId>feedcenter-push</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0.SNAPSHOT</version>

    <modules>
        <module>feedcenter-push-client</module>
        <module>feedcenter-push-core</module>
        <module>feedcenter-push-web</module>
    </modules>

    <properties>
        <finalName>feedcenter-push</finalName>
        <warName>${finalName}.war</warName>
        <spring.version>4.0.6.RELEASE</spring.version>
        <junit.version>4.12</junit.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <warExplodedDirectory>exploded/${warName}</warExplodedDirectory>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>

           <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <version>3.0.0</version>
                    <executions>
                        <execution>
                            <id>attach-sources</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>jar-no-fork</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

在项目中遇到一些 jar 包冲突的问题,还有很多人分不清楚 dependencies 与 dependencyManagement 的区别:


dependencies,即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(全部继承)


dependencyManagement,只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且 version 和 scope 都读取自父 pom。另外,如果子项目中指定了版本号,那么会使用子项目中指定的 jar 版本。


使用 dependency Management ,能让子 POM 继承父 POM 的配置的同时, 又能够保证子模块的灵活性。在父 POM 中 dependencyManagement 元素配置的依赖声明不会实际引入子模块中, 但能够约束子模块 dependencies 下的依赖的使用,子模块只需配置groupId与artifactId。


pluginManagement 与 dependencyManagement 类似,配置的插件不会造成实际插件的调用行为,只有当子 POM 中配置了相关 plugin 元素,才会影响实际的插件行为。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>com.vdian.feedcenter</groupId>
        <artifactId>feedcenter-push</artifactId>
        <version>1.0.0.SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>feedcenter-push-client</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

元素继承


可以看到,子 POM 中并未定义模块 groupId 与 version,这是因为子 POM 默认会从父 POM 继承了如下元素:


groupId、version
dependencies
developers and contributors
plugin lists (including reports)
plugin executions with matching ids
plugin configuration
resources 

因此,所有的 springframework 都省去了 version、junit还省去了scope, 而且插件还省去了 executions 与 configuration 配置,因为完整的声明已经包含在父POM中。


当依赖、插件的版本、配置等信息在父 POM 中声明之后,子模块在使用时就无须声明这些信息,也就不会出现多个子模块使用的依赖版本不一致的情况,这就降低了依赖冲突的几率。 另外,如果子模块不显式声明依赖与插件的使用,即使已经在父 POM 的 dependencyManagement、pluginManagement 中配置了,也不会产生实际的效果。


建议:模块继承与模块聚合同时进行,这意味着,你可以为自己的所有模块指定一个父工程,同时父工程中可以指定其余的 Maven 模块作为它的聚合模块。但需要遵循以下三条规则:

  • 在所有子 POM 中指定它们的父 POM;
  • 将父 POM 的 packaging 值设为 pom;
  • 在父 POM 中指定子模块/子POM的目录;

parent元素内还包含一个 relativePath 元素, 用于指定父 POM 的相对路径,默认../pom.xml


9、超级POM--约定优先于配置

任何一个Maven项目都隐式地继承自超级POM, 因此超级POM的大量配置都会被所有的Maven项目继承, 这些配置也成为了Maven所提倡的约定.


<!-- START SNIPPET: superpom -->
<project>
  <modelVersion>4.0.0</modelVersion>

  <!-- 定义了中央仓库以及插件仓库, 均为:https://repo.maven.apache.org/maven2 -->
  <repositories>
    <repository>
      <id>central</id>
      <name>Central Repository</name>
      <url>https://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

  <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>

  <!-- 依次定义了各类代码、资源、输出目录及最终构件名称格式, 这就是Maven项目结构的约定 -->
  <build>
    <directory>${project.basedir}/target</directory>
    <outputDirectory>${project.build.directory}/classes</outputDirectory>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
    <scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/resources</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>${project.basedir}/src/test/resources</directory>
      </testResource>
    </testResources>

    <!-- 为核心插件设定版本 -->
    <pluginManagement>
      <!-- NOTE: These plugins will be removed from future versions of the super POM -->
      <!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
      <plugins>
        <plugin>
          <artifactId>maven-antrun-plugin</artifactId>
          <version>1.3</version>
        </plugin>
        <plugin>
          <artifactId>maven-assembly-plugin</artifactId>
          <version>2.2-beta-5</version>
        </plugin>
        <plugin>
          <artifactId>maven-dependency-plugin</artifactId>
          <version>2.8</version>
        </plugin>
        <plugin>
          <artifactId>maven-release-plugin</artifactId>
          <version>2.3.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <!-- 定义项目报告输出路径 -->
  <reporting>
    <outputDirectory>${project.build.directory}/site</outputDirectory>
  </reporting>

  <!-- 定义release-profile, 为构件附上源码与文档 -->
  <profiles>
    <!-- NOTE: The release profile will be removed from future versions of the super POM -->
    <profile>
      <id>release-profile</id>

      <activation>
        <property>
          <name>performRelease</name>
          <value>true</value>
        </property>
      </activation>

      <build>
        <plugins>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-source-plugin</artifactId>
            <executions>
              <execution>
                <id>attach-sources</id>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-javadoc-plugin</artifactId>
            <executions>
              <execution>
                <id>attach-javadocs</id>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-deploy-plugin</artifactId>
            <configuration>
              <updateReleaseInfo>true</updateReleaseInfo>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

</project>
<!-- END SNIPPET: superpom -->