Reactor构建顺序
本章承接入门教程中"聚合的基础概念",深入讲解聚合之后 Maven 如何决定多模块的构建顺序。理解 Reactor 的排序机制,是排查"为什么模块 A 在模块 B 之前编译"、"为什么修改了模块却未生效"等问题的关键。
核心机制
当你在一个聚合项目的根目录执行 mvn clean install 时,Maven 并不会简单地按照 pom.xml 中 <modules> 标签的声明顺序逐个构建。它会先启动一个名为 Reactor(反应堆)的内部引擎,对整个多模块项目的依赖关系图进行一次拓扑排序,然后按照计算出的顺序执行构建。
Reactor 的官方定位是 Maven 的项目构建编排器。它的职责不是编译代码,而是读懂模块之间的依赖关系,生成一个不会导致"依赖缺失"的构建序列。换句话说,Reactor 确保:如果模块 B 依赖模块 A 的产物,那么模块 A 一定在模块 B 之前被构建和安装到本地仓库。
构建顺序的决定因素
Reactor 的排序逻辑由两个因素共同决定,依赖关系优先于声明顺序:
| 排序因素 | 优先级 | 说明 |
|---|---|---|
| 模块间依赖关系 | 高 | 如果模块 B 依赖模块 A,A 必须在 B 之前构建 |
<modules> 声明顺序 | 低 | 无依赖关系的模块之间,按声明顺序排列 |
这意味着:即使你在父 POM 里先写了 <module>web-ui</module>、后写了 <module>core-api</module>,只要 web-ui 依赖 core-api,Reactor 也会强制把 core-api 排在前面。
Reactor Build Order 输出解读
执行多模块构建时,Maven 会在日志开头打印 Reactor 计算出的构建顺序:
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] ------------------------------------------------------------------------
[INFO] feixiang-parent [pom]
[INFO] feixiang-core-api [jar]
[INFO] feixiang-service [jar]
[INFO] feixiang-web-ui [war]
[INFO] ------------------------------------------------------------------------
这五行输出的含义:
feixiang-parent:聚合根 POM,本身没有代码,但 Reactor 会把它纳入序列(用于统一版本和插件管理)feixiang-core-api:被其他模块依赖的基础模块,Reactor 通过依赖分析将其前置feixiang-service:业务服务层,依赖core-apifeixiang-web-ui:前端 Web 层,依赖service和core-api,被排在最后
关键认知:Reactor Build Order 不是"建议顺序",而是强制顺序。Maven 会严格按照这个列表从上到下执行 clean、compile、test、package、install 等生命周期阶段。
生活类比:餐厅后厨的出菜顺序
想象飞翔科技楼下有一家大型餐厅,后厨同时准备一桌宴席的 4 道菜:
- 汤(core-api):需要提前 2 小时慢炖,其他菜都要用它做汤底
- 主菜(service):需要汤底才能烹饪
- 配菜(web-ui):需要主菜和汤底一起摆盘
- 菜单(parent):本身不是菜,但规定了所有菜的调味标准
即使服务员给后厨的订单上先写了"配菜"、后写了"汤",主厨(Reactor)也会重新排单:汤 → 主菜 → 配菜。菜单虽然不烹饪,但主厨会先看一眼菜单确认标准。这就是 Reactor 的逻辑——依赖关系决定物理顺序,声明顺序只在无依赖时生效。
图示
上图展示了 Reactor 排序的核心逻辑:左侧是 pom.xml 中 <modules> 的声明顺序(web-ui 在前),右侧是 Reactor 实际计算出的构建顺序(core-api 在前)。下方的依赖关系图揭示了排序反转的原因——依赖关系压倒了声明顺序。Reactor 读取所有子模块的 POM,解析它们之间的依赖边,然后执行拓扑排序,确保没有任何模块在其依赖项之前被构建。
完整示例
场景
飞翔科技正在开发一套电商中台系统。CTO 大翔要求项目拆分为 4 个模块:
feixiang-parent:统一版本管理(packaging = pom)feixiang-core-api:公共 DTO 和接口(被所有模块依赖)feixiang-service:业务逻辑层(依赖 core-api)feixiang-web-ui:Web 控制器和前端资源(依赖 service 和 core-api)
架构师白歌在 parent 的 pom.xml 中按"从上层到下层"的思路声明了模块顺序:
操作前的配置
feixiang-parent/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-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>feixiang-web-ui</module>
<module>feixiang-service</module>
<module>feixiang-core-api</module>
</modules>
</project>
注意:白歌把 feixiang-web-ui 放在了第一个,因为他觉得"Web 层是入口,应该优先"。
feixiang-web-ui/pom.xml 中的依赖声明:
<dependencies>
<dependency>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-core-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
feixiang-service/pom.xml 中的依赖声明:
<dependencies>
<dependency>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-core-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
操作步骤
后端工程师小崔在 feixiang-parent 目录下执行:
mvn clean install
操作结果
Maven 输出的 Reactor Build Order 如下:
[INFO] --------------------< com.feixiang:feixiang-parent >--------------------
[INFO] Building feixiang-parent 1.0.0-SNAPSHOT [1/4]
[INFO] --------------------------------[ pom ]---------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] ------------------------------------------------------------------------
[INFO] feixiang-parent [pom]
[INFO] feixiang-core-api [jar]
[INFO] feixiang-service [jar]
[INFO] feixiang-web-ui [war]
[INFO] ------------------------------------------------------------------------
...
[INFO] -----------------< com.feixiang:feixiang-core-api >-------------------
[INFO] Building feixiang-core-api 1.0.0-SNAPSHOT [2/4]
...
[INFO] -----------------< com.feixiang:feixiang-service >--------------------
[INFO] Building feixiang-service 1.0.0-SNAPSHOT [3/4]
...
[INFO] ------------------< com.feixiang:feixiang-web-ui >--------------------
[INFO] Building feixiang-web-ui 1.0.0-SNAPSHOT [4/4]
变化分析:
- 尽管
pom.xml中<modules>的顺序是web-ui → service → core-api,Reactor 实际构建顺序却是parent → core-api → service → web-ui core-api被强制前置,因为service和web-ui都声明了对它的依赖service排在web-ui之前,因为web-ui依赖service- 如果小崔在
core-api中修改了一个 DTO 类,重新执行mvn clean install后,service和web-ui都会自动基于最新版本重新编译
易错点与常见问题
误区一:Reactor 顺序就是 <modules> 的声明顺序
错误认知:"我在父 POM 里把模块 A 写在模块 B 前面,所以 A 一定先构建。"
纠正:<modules> 的声明顺序只在模块之间没有依赖关系时才生效。一旦存在依赖关系,Reactor 会完全忽略声明顺序,按照拓扑排序结果执行。如果你把 web-ui 写在第一个,但 web-ui 依赖 core-api,Reactor 依然会把 core-api 排在前面。
误区二:Reactor 会并行构建无依赖关系的模块
错误认知:"core-api 和 parent 没有代码依赖,它们应该可以并行编译。"
纠正:默认情况下,Maven 的 Reactor 是串行执行的。即使两个模块之间没有依赖关系,Maven 也会按顺序逐个构建。如果你需要并行构建,必须显式启用 -T 参数(如 mvn -T 4 clean install),且并行度受限于模块间的依赖边。Reactor 的拓扑排序结果仍然是并行调度的前提——没有依赖关系的模块才可能被并行,有依赖关系的模块绝对不能并行。
误区三:修改了父 POM 版本,子模块会自动使用新版本
错误认知:"我把 parent 的版本从 1.0.0-SNAPSHOT 改成了 1.0.0,所有子模块应该自动跟着变。"
纠正:Reactor 负责排序和调度,不负责版本传播。子模块的 <parent> 标签中显式声明了 <version>,这个版本必须手动更新(或通过 CI/CD 脚本批量替换)。Reactor 在构建时如果发现子模块引用的 parent 版本与当前目录中的 parent POM 版本不一致,会先去本地仓库或远程仓库查找对应版本的 parent,而不是直接使用当前目录下的 parent 文件。这是多模块构建中最常见的"修改了 parent 却不生效"问题的根源。
小结
Reactor 是 Maven 多模块构建的隐形导演。它不显式配置,却决定了所有模块的出场顺序。其核心规则是:依赖关系优先于声明顺序。开发者应当学会阅读 Reactor Build Order 输出,将其作为验证模块依赖关系是否正确的工具——如果某个模块的位置和你预期不符,往往意味着它的 pom.xml 中多声明或少声明了一条依赖。
本章与全局的关系:本章回答了"聚合后模块按什么顺序构建"。下一章"模块间依赖"将深入讲解子模块之间如何通过 GAV 坐标相互引用,以及循环依赖的检测与规避。