情景引入
现在有一个hello-world
项目,其由两个子模块构成,一个是hello-china
模块,一个是hello-france
模块,在构建这个多模块项目时,如何一次性构建其包含的两个子模块,而不是在两个子模块下分别运行mvn命令尼?当两个模块的pom文件中配置的依赖和插件有很多重复的时候,我们如何减少这种重复尼?为了解决以上问题,我们引入了聚合与继承的概念。
聚合
通过构建聚合模块,能够运行一条命令就能构建聚合模块所包含的所有模块。
配置聚合模块
根据前述情景可以创建出如下目录结构
- hello-world
- hello-china
- hello-france
- pom.xml
其中hello-china与hello-france作为被聚合模块,都是单独的Maven项目,有自己的pom文件和代码。hello-world则作为一个聚合模块被创建出来,它本身作为一个Maven项目,必须有自己的pom文件,不过pom文件的内容用来配置被聚合的模块,如下:
<project>
...
<groupId>com.maven.hello</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-snapshot<version>
<packaging>pom</packaging>
<name>Hello World</name>
<modules>
<module>hello-china</module>
<module>hello-france</module>
</modules>
<project>
其中groupId和version均来自被聚合模块(这些模块应该拥有相同的groupId和version),artifactId则表示聚合模块的名称,值得注意的是其package属性为pom如果是其他值,则无法构建。最重要的元素是module,它配置被聚合模块的目录,每个module的值都是一个当前pom的相对目录。如hello-world的pom文件存放在硬盘目录
d:\Code\MavenProject\hello-world\pom.xml
,则hello-china则代表目录d:\Code\MavenProject\hello-world\hello-china
,hello-france代表目录d:\Code\MavenProject\hello-world\hello-france
。这两个目录都自己包含了pom文件和代码,离开聚合模块也能够各自单独构建。通过观察可以发现,聚合模块仅仅是帮助聚合其他模块构建的工具,除了pom文件并没有其他实质的内容。此外,聚合模块与其他模块的目录关系也不一定是父子关系,也能是平行关系。如此例中
- hello-world
- pom.xml
- hello-china
- hello-france
在这种平行目录的聚合关系中,module的值也需要相应更改为
../hello-china
和../hello-france
。 配置完成后,在hello-world目录下运行 mvn clean install命令即可一次性构建hello-china和hello-france。- hello-world
继承
当hello-china与hello-france两个项目中的配置文件包含有大量的重复的时候,如groupId version 依赖与插件配置等,我们可以将这些重复的配置文件提取到一个父模块中,类似于java中的继承机制,以此来消除重复的配置。
配置父模块
在上述聚合模块的基础目录结构上,创建如下目录:
- hello-world
- hello-parent
- pom.xml
- hello-china
- hello-france
- pom.xml
hello-parent作为hello-china与hello-france的父模块,除了拥有pom文件外,没有其他实质性内容。其pom文件如下所示
<project>
...
<groupId>com.maven.hello</groupId>
<artifactId>hello-parent</artifactId>
<version>1.0-snapshot<version>
<packaging>pom</packaging>
<name>Hello Parent</name>
...
<project>
作为父模块,其packaging必须为pom,并拥有自己定义的groupId和version。当hello-china模块继承该父模块时,只需更改目录下的pom文件,如下:
<project>
...
<parent>
<groupId>com.maven.hello</groupId>
<artifactId>hello-parent</artifactId>
<version>1.0-snapshot<version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hello-china</artifactId>
...
</project>
其中parent元素声明父模块,并通过groupId、artifactid、versioin指定父模块的坐标,元素relativePath则表示父模块pom的相对路径,默认为
../pom.xml
,表示当前目录的父目录(上述pom文件中的此项配置可省略不写)。当构建项目时,Maven会首先在指定的relativePath中查找,若找不到则去本地仓库中查找。子模块从父模块继承了groupId、artifactid、version等其他配置,如果子模块想使用与父模块中不同的配置,可以在pom文件中显示声明来覆盖父模块中的配置。
将父模块配置完成后,还需将父模块hello-parent整合到聚合模块hello-world中,更改hello-world的pom文件如下:
<project> ... <groupId>com.maven.hello</groupId> <artifactId>hello-world</artifactId> <version>1.0-snapshot<version> <packaging>pom</packaging> <name>Hello World</name> <modules> <module>hello-parent</module> <module>hello-china</module> <module>hello-france</module> </modules> ... <project>
可继承的pom元素
元素名称 | 含义 |
---|---|
groupId | 项目组ID,项目坐标的核心元素 |
version | 项目版本, 项目坐标的核心元素 |
description | 项目的描述信息 |
organization | 项目的组织信息 |
inceptionYear | 项目的创始年份 |
url | 项目的URL地址 |
developers | 项目开发者信息 |
contributors | 项目的贡献者信息 |
distributionManagement | 项目的部署配置 |
issueManagement | 项目的缺陷跟踪系统信息 |
ciManagement | 项目的持续集成系统信息 |
scm | 项目的版本控制系统信息 |
mailingLists | 项目的邮件列表信息 |
properties | 自定义的maven属性 |
dependencies | 项目的依赖配置 |
dependencyManagement | 项目的依赖管理配置 |
repositories | 项目的仓库配置 |
build | 包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等 |
reporting | 包括项目的报告输出目录配置、报告插件配置等 |
依赖管理
当配置完父模块后,子模块便可以继承父模块中关于依赖的配置,但是如果新添加的子模块中,没有使用到父模块中配置的依赖,难道还是要继承父模块中的依赖吗?这显然是不合理的,因此Maven提供了dependencyManagement
元素,它既能让子模块继承到父模块的依赖配置,也能保持子模块的灵活性。
在dependencyManagement
元素下的依赖声明不会引入实际的依赖,但是它能约束dependency
下的依赖使用。例如在hello-parent的pom文件中加入如下dependencyManagement
配置。
<project>
...
<name>hello-parent</name>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
...
</project>
这里配置的依赖并不会给hello-parent引入实际的依赖,也不会给其子模块引入依赖,但是这段配置依然会被继承。现修改子模块hello-china的pom文件如下:
<project>
...
<parent>
<groupId>com.maven.hello</groupId>
<artifactId>hello-parent</artifactId>
<version>1.0-snapshot<version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hello-china</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
...
</project>
子模块只需配置依赖的groupId和artifactId,就能引入正确的依赖,完整的依赖声明包含在父pom中。由于依赖的版本信息存储在父pom中,这样能保证所有子模块引用的依赖版本一致,减少冲突。
子模块也可以不声明依赖的使用,这样即使父pom中已经配置了dependencyManagement,也不会对子模块产生实际效果。
使用import导入依赖管理。若新建项目想使用与hello-parent相同的依赖管理配置,除了复制和继承外,还能够通过import元素来导入依赖管理。只需在新项目的pom文件中加入如下配置:
<dependencyManagement> <dependencies> <dependency> <groupId>com.maven.hello</groupId> <artifactId>hello-parent</artifactId> <version>1.0-snapshot<version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
插件管理
与依赖管理的思想相同,Maven通过pluginManagement
元素在父pom中配置插件,配置部分如下:
<build>
<pluginManagement>
<plugins>
<plugin>...</plugin>
</plugins>
</pluginManagement>
</build>
当子模块需要使用该插件配置时,只需在插件配置部分声明该插件的groupId和artifactId。
聚合与继承的关系
- 聚合模块能够知道被聚合模块的存在,而被聚合模块不知道聚合模块的存在
- 继承关系中的父模块不知道子模块的存在,子模块则都必须知道父模块的存在。
- 两者的pom文件中packaging元素都必须是pom,且除了pom文件都没有其他实质性内容
通常,也可以将聚合模块与父模块合并。如本文中所述hello-world与hello-parent模块合并后,目录结构如下
- hello-world
- hello-china
- hello-france
- pom.xml
其中pom文件合并为:
<project>
...
<groupId>com.maven.hello</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-snapshot<version>
<packaging>pom</packaging>
<name>Hello World</name>
<modules>
<module>hello-china</module>
<module>hello-france</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<project>
小结
本文介绍了Maven项目中聚合的基本用法,详细介绍了继承关系中依赖和插件的配置,最后对聚合和继承的特点进行了简要的比较。