多阶段构建处理多模块项目的核心在于解耦构建逻辑与部署结构,分步聚焦编译验证、依赖聚合和组装发布,利用maven reactor自动按依赖顺序构建,避免硬编码路径和fat-jar大包,最终产出轻量、可复现、环境干净的交付物。
多阶段构建处理多模块项目,核心在于解耦构建逻辑与部署结构,避免把所有模块的编译、依赖、资源一股脑塞进一个包里。它不追求“一次打包全搞定”,而是分步聚焦:先各自编译验证,再按需组装,最终产出轻量、可复现、环境干净的交付物。
明确各阶段职责,避免交叉污染
多模块项目天然存在编译依赖(如 service 依赖 common)、运行时依赖(如 web 模块需要 service.jar + common.jar)和启动依赖(如 Spring Boot 的 fat-jar 或分层目录结构)。多阶段构建将这些拆开:
-
编译阶段:只做源码编译、测试、生成各模块的原始构件(如 *.jar),不安装、不打包成可运行格式;使用
mvn compile package -DskipTests控制粒度 -
依赖聚合阶段:收集本项目产出的 jar(通过
target/*.jar)、指定 scope 的第三方依赖(如runtime或compile)、配置文件和脚本;可用maven-dependency-plugin拷贝,或maven-assembly-plugin定义 layout -
组装/发布阶段:把上一阶段整理好的内容,按目标格式(tar、zip、deb、docker image)封装;例如用
fpm -s dir -t deb ... runtime-stage/=/opt/myapp,或用 Dockerfile 的COPY --from=builder
利用 Maven 的 reactor 特性控制顺序,而非硬编码路径
多模块项目不是靠手动 cd 进子目录执行 mvn,而是靠 mvn 自身的 reactor 机制自动排序。只要 pom.xml 中 <modules></modules> 声明正确、<dependency></dependency> 关系清晰,执行 mvn clean package 在父目录下,Maven 就会按拓扑序依次构建依赖链最底层的模块(如 common → dao → service → web)。
- 确保父 POM 的
<packaging>pom</packaging>,子模块为jar或war - 避免在子模块中写死其他模块的本地路径(如
../common/target/common-1.0.jar),全部走 Maven 坐标依赖 - 若需跳过某模块参与构建,用
-pl !module-name,而非删掉<module></module>
针对 Spring Boot 项目,慎用 spring-boot-maven-plugin 的默认 fat-jar
直接对 web 模块执行 mvn spring-boot:repackage 会把所有依赖(包括其他子模块的 jar)打成一个几百 MB 的大 jar——这违背了多阶段“分而治之”的初衷,也难以热更新单个模块。
- 推荐改为
layout=ZIP或自定义 layout,让启动类 jar 只含自身字节码,依赖放在lib/目录下 - 在组装阶段统一拷贝所有模块的
target/*.jar到lib/,再把启动脚本、配置、application.yml放到对应位置(如conf/、bin/) - 这样后续升级 common 模块时,只需替换
lib/common-1.0.jar,无需重打整个应用包
结合 fpm 或 Docker 实现跨平台交付一致性
fpm 本身不内置“多阶段”,但它的 -s dir 类型配合临时目录,就是天然的阶段产物中转站:
- 第一阶段:用 Maven 构建所有模块,输出到
./build/stage1/ - 第二阶段:用 shell 脚本筛选
runtime-stage/,只保留*-service.jar、*-web.jar、conf/、bin/start.sh - 第三阶段:用
fpm -s dir -t rpm -n myapp -v 2.1.0 runtime-stage/=/opt/myapp打出 RPM 包 - 同理,Dockerfile 中
FROM maven:3.9 AS builder构建,再FROM openjdk:17-jre-slim复制产物,实现最小运行镜像










