Maven聚合与继承

聚合

Maven聚合又称为多模块,该特性是为了一次构件多个项目,而不用到每个项目下分别执行mvn命令。且聚合模块其打包方式packaging必须为pom否则无法构建。

modules元素是实现聚合最核心的配置,用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合。且每一个module的值都是一个当前POM的相对目录。为了方便快速定位内容,模块所处目录名称应当与其artifactId一致

为了方便构建项目,通常将聚合模块放在项目目录最顶层,其他模块作为聚合模块的子目录,聚合模块仅仅是帮助聚合其他模块构件的工具,其本身并无实质内容。

Maven在构件聚合项目时,首先会解析聚合模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序,然后根据这个顺序依次构建各个模块,反应堆是所有模块组成的一个构建结构。

1
2
3
4
5
6
7
8
9
10
11
<modelVersion>4.0.0</modelVersion>
<groupId>com.long.mvnbook.account</groupId>
<artifactId>account-aggregator</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Aggregator</name>

<modules>
<module>account-email</module>
<module>account-persist</module>
</modules>

继承

继承是为了抽取出重复的配置,需要创建POM的父子结构,在父POM中声明一些配置供子POM继承,以实现一处声明多处使用。

与聚合一样父模块的POM其打包类型packaging必须为pom,父模块只是为了消除配置的重复,因此其本身不包含除POM以外的项目文件

1
2
3
4
5
6
<modelVersion>4.0.0</modelVersion>
<groupId>com.long.mvnbook.account</groupId>
<artifactId>account-aggregator</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Aggregator</name>

在子模块中使用parent元素声明父模块,groupIdartifactIdversion指定了父模块的坐标,且这三个元素是必须的,元素relativePath表示父模块POM的相对路径,当构建时Maven首先会根据relativePath检查父POM,若找不到则从本地仓库查找relativePath默认值是../pom.xml

1
2
3
4
5
6
<parent>
<artifactId>account-aggregator</artifactId>
<groupId>com.long.mvnbook.account</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

可继承的POM元素:

  • groupId:项目组ID,项目坐标的核心元素
  • version:项目版本号,项目坐标的核心元素
  • description:项目描述信息
  • organization:项目组织信息
  • inceptionYear:项目创始年份
  • url:项目url地址
  • developers:项目开发者信息
  • contributors:项目贡献者信息
  • distributionManagement:项目部署配置
  • issueManagement:项目缺陷跟踪系统信息
  • ciManagement:项目持续集成系统信息
  • scm:项目的版本控制系统信息
  • mailingLists:项目的邮件列表信息
  • properties:自定义的Maven属性
  • dependencies:项目的依赖配置
  • dependencyManagement:项目的依赖管理配置,
  • repositories:项目的仓库配置
  • build:包括项目的源码目录、输出目录、插件配置、插件管理配置等
  • reporting:包括项目的报告输出目录配置、报告插件配置等

依赖管理

dependencyManagement既能让子模块继承到父类模块的依赖配置又能保证子模块依赖使用的灵活性,该元素下的依赖声明不会引入实际的依赖,不过能约束dependencies下的依赖使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 父模快account-parent中声明 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 子模块account-email中使用 -->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

dependencies中的依赖配置比原来简单了,junit依赖省去了versionscope,这样能统一项目范围中依赖的版本,当依赖版本在父POM中声明之后,子模块使用依赖时无需声明版本号,也不会发生多个子模块使用依赖版本不一致的情况。子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明,也不会产生任何实际效果。

import依赖范围的依赖只在dependencyManagement元素下有效,使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。

若多个项目使用的依赖版本一致,则可以定义一个使用dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置。

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.long.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

插件管理

Maven提供了pluginManagement元素来帮助管理插件,在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,且其groupIdartifactIdpluginManagement中配置的插件匹配时,pluginManagement才会影响实际的插件行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- 父模快account-parent中声明 -->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.6.0.1398</version>
<executions>
<execution>
<id>sonar-scan</id>
<phase>test</phase>
<goals>
<goal>sonar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
<!-- 子模块account-email中使用 -->
<build>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

若子模块需要不同的插件配置,可以自行配置以覆盖父模块的pluginManagement配置,当项目中多个模块有相同的插件配置时,应当将配置移到父POM的pluginManagement元素中。

聚合与继承的关系

对于聚合模块,它知道被聚合的模块,但被聚合的模块不知道这个聚合模块的存在。

对于继承关系的父POM,它不知道哪些子模块继承了它,但子模块都必须知道自己的父POM

聚合POM与继承关系中的父POM的packaging都必须时pom,且都是除POM之外没有实际的内容

约定优于配置

Maven提倡约定优于配置,使用约定可以大量减少配置,遵循约定虽然损失一定的灵活性,但却能减少配置,且能帮助遵守构建标准

任何一个Maven项目都隐式地继承超级POM,Maven 3中超级POM文件在$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路径下。Maven 2中超级POM文件在$MAVEN_HOME/lib/maven-x.x.x-uber.jar中的org/apache/maven/project/pom-4.0.0.xml路径下。

超级POM定义了仓库及插件仓库,都关闭了SNAPSHOT的支持。

反应堆

在一个多模块的Maven项目中,反应堆时指所有模块组成的一个构建结构,对于单模块项目,反应堆就是该模块本身,多模块的项目,反应堆就包括了各个模块之间的继承与依赖关系,从而能自动计算出合理的模块构建顺序

Maven按序读取POM,若该POM没有依赖模块,则构建该模块,否则先构建依赖模块,若该依赖模块还依赖其他模块,则进一步先构建依赖的依赖

模块间的依赖关系会将反应堆构成一个有向非循环图,各个模块是该图的节点依赖关系构成了有向边,且该图不允许出现循环,当出现A模块依赖于B,B又依赖于A时,Maven会报错。

若仅仅构建完整反应堆中的某些模块,则需要实时的裁剪反应堆。Maven提供很多命令行选项支持裁剪反应堆:

  • -am, --also-make 同时构建所列模块的依赖模块
  • -amd -also-make-dependents 同时构建依赖于所列模块的模块
  • -pl, --project < arg > 构建指定的模块,模块间用逗号隔开
  • -rf -resume-from <args> 从指定模块回复反应堆