聚合
本章承接 parent 继承,讲解 Maven 多模块项目的另一种组织方式:聚合(Aggregation)。如果说继承是"集团总部给分店发手册",聚合就是"集团总部统一调度所有分店同时营业"。在实际项目中,继承与聚合通常配合使用,但理解它们的独立概念是正确使用的前提。
核心机制
聚合项目本身不生产代码,它的唯一职责是统一管理一组模块的构建顺序和构建行为。当你在一个聚合项目的根目录执行 mvn clean install,Maven 会自动按依赖顺序构建所有子模块。
聚合 POM 通过 <modules> 标签声明它管理的子模块:
关键规则:
- 聚合 POM 的
<packaging>必须是pom - 每个
<module>的值是子模块目录的相对路径(通常是目录名) - Maven 会进入每个模块目录,找到其中的
pom.xml并执行构建 - 构建顺序由模块间的依赖关系自动计算,而非
<modules>的声明顺序
聚合与继承的区别
这是最容易混淆的两个概念,必须清晰区分:
| 维度 | 继承(Inheritance) | 聚合(Aggregation) |
|---|---|---|
| 关系方向 | 子 POM 指向父 POM(<parent>) | 父 POM 指向子模块(<modules>) |
| 核心目的 | 共享配置(版本、插件、属性) | 统一构建(一次命令构建多个模块) |
| packaging | 父 POM 必须是 pom | 聚合 POM 必须是 pom |
| 是否必须同时用 | 否 | 否 |
| 典型场景 | 统一依赖版本 | 一键构建微服务全家桶 |
关键结论:继承解决"配置复用"问题,聚合解决"构建协调"问题。两者可以独立存在,也可以在一个 POM 中同时存在(最常见)。
多模块项目的典型结构
feixiang-platform/ # 聚合 + 继承 POM(根目录)
├── pom.xml # 定义 modules、parent、共享配置
├── employee-system/ # 子模块1
│ └── pom.xml
├── payroll-service/ # 子模块2
│ └── pom.xml
└── employee-web/ # 子模块3
└── pom.xml
在这个结构中,根 pom.xml 同时扮演三个角色:
- 聚合器:通过
<modules>管理三个子模块 - 父 POM:通过
<parent>被三个子模块继承(如果子模块指向它) - 构建入口:开发者只在根目录执行 Maven 命令
生活类比:交响乐团指挥
想象飞翔科技的技术团队是一支交响乐团:
- 聚合 POM = 指挥家。指挥家自己不拉小提琴、不吹长笛,他的职责是举起指挥棒,让所有乐手(子模块)在同一时刻开始演奏。
- 继承 = 乐谱规范。所有小提琴手用的乐谱格式相同(继承父 POM 的配置),但每个声部演奏的音符不同(子模块自己的依赖)。
mvn clean install= 演出开始。指挥棒一挥,弦乐组(employee-system)、木管组(payroll-service)、铜管组(employee-web)按预定顺序依次进入,最终合成一首完整的交响曲(可部署的系统)。
如果没有指挥家(聚合),你需要分别走到每个乐手面前说"请开始"——这就是没有聚合时,逐个进入子目录执行 mvn install 的痛苦。
图示
上图展示了聚合与继承的配合关系:根 POM 通过 <modules> 聚合三个子模块(实线),同时通过 parent 机制被三个子模块继承配置(虚线)。开发者只需在根目录执行一次 Maven 命令,所有模块按依赖顺序自动构建。
完整示例
场景
飞翔科技有三个服务:员工系统 employee-system、薪资服务 payroll-service、Web 门户 employee-web。其中 employee-web 依赖 employee-system 的 API 模块。CTO 大翔要求:一键构建所有服务,且构建顺序必须正确(被依赖的先构建)。
操作前:各自为政
没有聚合时,小崔需要逐个目录执行构建:
$ cd employee-system && mvn clean install
$ cd ../payroll-service && mvn clean install
$ cd ../employee-web && mvn clean install
问题:
- 如果
employee-web依赖employee-system,但小崔忘了先构建后者,employee-web会报依赖找不到 - 黄俪想一次性构建整个平台,不得不写复杂的 shell 脚本
- 李眉的 Jenkins 流水线需要配置多个步骤,维护成本高
操作步骤:创建聚合项目
第一步:在根目录创建 feixiang-platform/pom.xml
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-platform</artifactId>
<version>2.0.0</version>
<packaging>pom</packaging>
<name>飞翔科技技术平台</name>
<description>统一聚合所有业务模块</description>
<modules>
<module>employee-system</module>
<module>payroll-service</module>
<module>employee-web</module>
</modules>
<!-- 同时作为父 POM,提供共享配置 -->
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.feixiang</groupId>
<artifactId>employee-system</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
第二步:确保子模块的 pom.xml 指向父 POM
以 employee-web/pom.xml 为例:
<project>
<parent>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-platform</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>employee-web</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.feixiang</groupId>
<artifactId>employee-system</artifactId>
<!-- 版本由父 POM 的 dependencyManagement 管理 -->
</dependency>
</dependencies>
</project>
操作结果:一键构建
在根目录执行:
$ cd feixiang-platform
$ mvn clean install
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] feixiang-platform [pom]
[INFO] employee-system [jar]
[INFO] payroll-service [jar]
[INFO] employee-web [war]
[INFO]
[INFO] --- maven-clean-plugin:3.2.0:clean (default-clean) @ feixiang-platform ---
...
[INFO] --- maven-install-plugin:3.1.1:install (default-install) @ employee-web ---
[INFO] Installing target\employee-web-2.0.0.war to ...\repository\com\feixiang\employee-web\2.0.0\employee-web-2.0.0.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Reactor Build Order 分析:
- Maven 自动计算了构建顺序:
feixiang-platform(根)→employee-system→payroll-service→employee-web - 虽然
<modules>中employee-web写在第三位,但 Maven 检测到它依赖employee-system,确保被依赖模块先构建 - 小崔只需执行一条命令,四个模块全部构建并安装到本地仓库
- 李眉的 Jenkins 流水线只需配置一个步骤:
mvn clean install
易错点与常见问题
误区一:聚合 POM 的 packaging 不是 pom
错误配置:
<artifactId>feixiang-platform</artifactId>
<packaging>jar</packaging> <!-- 错误! -->
后果:Maven 尝试为聚合项目编译源代码、生成 JAR,但聚合项目通常没有 src/main/java。构建会失败或产生无意义的空 JAR。聚合 POM 和父 POM 一样,packaging 必须是 pom。
误区二:module 路径写错
错误配置:
<modules>
<module>./employee-system</module> <!-- 多余的前缀 ./ -->
</modules>
或
<modules>
<module>employee-system/pom.xml</module> <!-- 不应该写 pom.xml -->
</modules>
纠正:<module> 的值应该是子模块目录的相对路径,默认就是目录名。不要加 ./、../(除非确实在父目录之外),也不要加 /pom.xml。Maven 会自动在指定目录下寻找 pom.xml。
误区三:聚合和继承混为一谈,认为用了聚合就自动继承
错误认知:"我在根 POM 里写了 <modules>,子模块就会自动继承根 POM 的配置。"
纠正:不会。聚合和继承是两个独立的机制。根 POM 写 <modules> 只是告诉 Maven"我要构建这些子模块";子模块是否继承根 POM 的配置,取决于子模块自己的 <parent> 标签是否指向根 POM。
常见做法(推荐):让根 POM 同时扮演聚合器和父 POM 两个角色,即根 POM 写 <modules>,子 POM 写 <parent> 指向根 POM。这样既能一键构建,又能共享配置。
小结
聚合(Aggregation)是 Maven 管理多模块项目的构建协调机制。聚合 POM 通过 <modules> 声明管理的子模块,通过 packaging=pom 表明自己不产生代码。继承解决"配置复用",聚合解决"构建协调",两者通常配合使用。在聚合根目录执行一次 Maven 命令,所有模块按依赖关系自动排序构建,极大提升了多服务项目的开发效率。
本章与全局的关系:本章讲解了多模块项目的横向组织方式。下一节将深入讲解 properties 标签,它是父 POM 统一管理和子 POM 灵活引用的核心工具。