🎯 问题背景
在 Maven 多模块项目中,你可能会看到项目根目录和各个子模块下都存在一个 .flattened-pom.xml 文件,这个文件是做什么的?能删除吗?
问题的本质
这个问题涉及到 Maven 的一个核心矛盾:
开发时的灵活性 VS 发布时的兼容性
↓ ↓
使用变量、继承 需要具体值、独立性
🔍 核心概念
什么是 .flattened-pom.xml?
.flattened-pom.xml 是由 Maven Flatten Plugin 自动生成的扁平化 POM 文件。
简单类比:
- 原始 pom.xml = 带公式的 Excel 表格(如
=SUM(A1:A10)) - .flattened-pom.xml = 计算后的结果表格(只有具体数值)
为什么需要"扁平化"?
场景 1:版本号变量问题
xml
<!-- 👎 原始 pom.xml(开发时方便) -->
<groupId>org.gycoder</groupId>
<artifactId>smart-common</artifactId>
<version>${revision}</version> <!-- 变量 -->
<properties>
<revision>1.0.0-SNAPSHOT</revision>
</properties>
问题:当你把这个 JAR 发布到 Maven 中央仓库后,别人依赖你的项目时:
xml
<!-- 其他开发者的项目 -->
<dependency>
<groupId>org.gycoder</groupId>
<artifactId>smart-common</artifactId>
<version>${revision}</version> <!-- ❌ 别人的项目没有定义这个变量! -->
</dependency>
Maven 报错:
Could not resolve dependencies:
Failed to collect dependencies for org.gycoder:smart-common:jar:${revision}
解决方案:扁平化
xml
<!-- ✅ .flattened-pom.xml(发布时使用) -->
<groupId>org.gycoder</groupId>
<artifactId>smart-common</artifactId>
<version>1.0.0-SNAPSHOT</version> <!-- 变量已被替换为具体值 -->
现在别人就可以正常依赖了:
xml
<dependency>
<groupId>org.gycoder</groupId>
<artifactId>smart-common</artifactId>
<version>1.0.0-SNAPSHOT</version> <!-- ✅ 清晰明确 -->
</dependency>
⚙️ 工作原理
插件配置(你的项目中)
xml
<!-- pom.xml:187-212 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.7.2</version>
<configuration>
<!-- 只解析 CI Friendly 变量 -->
<flattenMode>resolveCiFriendliesOnly</flattenMode>
<!-- 更新 POM 文件(生成 .flattened-pom.xml) -->
<updatePomFile>true</updatePomFile>
</configuration>
<executions>
<!-- 在处理资源阶段生成扁平化 POM -->
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<!-- 在清理阶段删除扁平化 POM -->
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
执行流程
graph LR
A[mvn install] --> B[process-resources 阶段]
B --> C[flatten-maven-plugin 执行]
C --> D[读取 pom.xml]
D --> E[解析 ${revision} = 1.0.0-SNAPSHOT]
E --> F[生成 .flattened-pom.xml]
F --> G[后续构建使用扁平化 POM]
H[mvn clean] --> I[clean 阶段]
I --> J[flatten-maven-plugin clean]
J --> K[删除 .flattened-pom.xml]
核心转换逻辑
| 原始 pom.xml | .flattened-pom.xml | 说明 |
|---|---|---|
<version>${revision}</version> |
<version>1.0.0-SNAPSHOT</version> |
变量替换 |
<parent>...</parent> |
(可能被移除或简化) | 继承展开 |
<properties>...</properties> |
(可能被移除) | 属性内联化 |
<dependencyManagement> |
(根据模式处理) | 依赖管理优化 |
🔬 项目实战分析
你的项目使用情况
1. 父 POM(smart-service-bot-parent)
xml
<!-- pom.xml:10 -->
<version>${revision}</version>
<!-- pom.xml:31 -->
<properties>
<revision>1.0.0-SNAPSHOT</revision>
</properties>
分析:
- ✅ 使用了
${revision}变量统一管理版本 - ✅ 这是 CI Friendly Versions 的标准用法
- ⚠️ 必须依赖 flatten-maven-plugin 才能正常工作
2. 依赖管理引用
xml
<!-- pom.xml:52-58 -->
<dependency>
<groupId>org.gycoder</groupId>
<artifactId>smart-dependencies</artifactId>
<version>${revision}</version> <!-- 引用同一个变量 -->
<type>pom</type>
<scope>import</scope>
</dependency>
分析:
- ✅ 确保所有模块版本一致
- ⚠️ 如果没有扁平化,其他模块无法解析这个依赖
3. 子模块继承
xml
<!-- 子模块 smart-common/pom.xml(假设) -->
<parent>
<groupId>org.gycoder</groupId>
<artifactId>smart-service-bot-parent</artifactId>
<version>${revision}</version> <!-- 继承父版本 -->
</parent>
分析:
- ✅ 子模块自动继承父模块版本
- ⚠️ 发布时必须替换为具体版本号
删除插件的后果模拟
场景测试:删除 flatten-maven-plugin 后执行构建
bash
# 1. 删除插件配置
# 2. 执行构建
mvn clean install
预期报错:
[ERROR] Failed to execute goal on project smart-common:
Could not resolve dependencies for project org.gycoder:smart-common:jar:${revision}:
Failed to collect dependencies at org.gycoder:smart-dependencies:pom:${revision}:
Failed to read artifact descriptor for org.gycoder:smart-dependencies:pom:${revision}:
Could not transfer artifact org.gycoder:smart-dependencies:pom:${revision}
from/to central (https://repo.maven.apache.org/maven2):
Illegal character in path at index 57:
https://repo.maven.apache.org/maven2/org/gycoder/smart-dependencies/${revision}/...
错误原因:
- Maven 尝试从本地仓库读取
smart-dependencies:${revision} - 变量
${revision}没有被解析,直接作为字符串使用 - 路径中包含非法字符
${},导致失败
💡 深度理解
CI Friendly Versions 的设计理念
传统版本管理的痛点
项目结构:
smart-service-bot-parent (1.0.0-SNAPSHOT)
├── smart-common (1.0.0-SNAPSHOT)
├── smart-auth (1.0.0-SNAPSHOT)
├── smart-api (1.0.1-SNAPSHOT) ← 版本不一致!
└── ...
问题:
- ❌ 需要手动修改每个模块的 pom.xml
- ❌ 容易遗漏某个模块,导致版本混乱
- ❌ 发布新版本时要改动 N 个文件
CI Friendly 解决方案
xml
<!-- 所有模块统一使用 -->
<version>${revision}</version>
<!-- 只需在父 POM 修改一处 -->
<properties>
<revision>2.0.0-RELEASE</revision> <!-- 所有模块同步升级 -->
</properties>
优势:
- ✅ 单点修改,全局生效
- ✅ CI/CD 可通过命令行参数动态修改版本
- ✅ 避免版本不一致问题
CI/CD 集成示例
yaml
# GitLab CI 示例
build:
script:
# 动态生成版本号:主版本.次版本.构建号
- VERSION="2.0.${CI_PIPELINE_ID}"
# 通过 -D 参数覆盖 revision 变量
- mvn clean deploy -Drevision=${VERSION}
# 发布到 Maven 仓库时,版本号为 2.0.1234
对比传统方式:
bash
# 传统方式:需要用 mvn versions 插件修改所有 pom.xml
mvn versions:set -DnewVersion=2.0.1234
mvn versions:commit
git add .
git commit -m "Bump version to 2.0.1234"
# 然后才能构建
mvn clean deploy
Maven 依赖解析机制
本地构建时的解析
1. Maven Reactor 构建顺序:
smart-service-bot-parent
├── 解析 ${revision} = 1.0.0-SNAPSHOT(从当前 POM properties)
├── smart-common (version = ${revision})
│ └── Maven Reactor 知道 ${revision} = 1.0.0-SNAPSHOT
└── smart-auth (version = ${revision})
└── 依赖 smart-common:${revision}
└── Maven Reactor 在内存中已有 smart-common:1.0.0-SNAPSHOT
结论:本地多模块构建时,Maven Reactor 能正确解析 ${revision}
发布到仓库后的解析
1. smart-common 发布到 Maven 仓库:
repo/org/gycoder/smart-common/
├── 1.0.0-SNAPSHOT/
│ ├── smart-common-1.0.0-SNAPSHOT.jar
│ └── smart-common-1.0.0-SNAPSHOT.pom ← 如果没有扁平化,包含 ${revision}
2. 其他项目依赖 smart-common:
<dependency>
<groupId>org.gycoder</groupId>
<artifactId>smart-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
3. Maven 下载并读取 POM:
❌ 如果 POM 中有 <version>${revision}</version>
→ Maven 无法解析(没有这个变量的定义)
→ 构建失败
✅ 如果使用了 .flattened-pom.xml(已替换为具体值)
→ Maven 可以正确解析
→ 构建成功
flattenMode 配置详解
xml
<flattenMode>resolveCiFriendliesOnly</flattenMode>
| flattenMode | 说明 | 适用场景 |
|---|---|---|
resolveCiFriendliesOnly |
只解析 ${revision}, ${sha1}, ${changelist} 三个变量 |
推荐:你的项目使用此模式 |
oss |
更激进:移除 <parent>、<properties> 等 |
发布到 Maven Central |
bom |
仅保留 <dependencyManagement> |
BOM (Bill of Materials) 项目 |
fatJar |
为 fat JAR 优化 | Spring Boot 可执行 JAR |
defaults |
默认模式,平衡处理 | 一般项目 |
你的项目为什么选择 resolveCiFriendliesOnly?
xml
<!-- 原始 pom.xml -->
<parent>...</parent> <!-- 保留:子模块需要知道父模块信息 -->
<properties>
<revision>1.0.0-SNAPSHOT</revision> <!-- 仅解析这个 -->
<java.version>17</java.version> <!-- 保留:其他属性不动 -->
</properties>
xml
<!-- .flattened-pom.xml -->
<parent>...</parent> <!-- ✅ 保留 -->
<version>1.0.0-SNAPSHOT</version> <!-- ✅ ${revision} 被替换 -->
<properties>
<java.version>17</java.version> <!-- ✅ 保留 -->
</properties>
✅ 最佳实践
1. Git 版本控制
bash
# .gitignore
.flattened-pom.xml
**/.flattened-pom.xml
原因:
.flattened-pom.xml是构建产物,不是源代码- 每次构建都会重新生成,提交没有意义
- 不同开发者本地路径可能不同,提交会导致冲突
2. 持续集成配置
yaml
# .gitlab-ci.yml 示例
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
build:
stage: build
script:
- mvn clean install -Drevision=1.0.${CI_PIPELINE_ID}
artifacts:
paths:
- target/*.jar
# ❌ 不要包含 .flattened-pom.xml
3. 版本号命名规范
xml
<!-- 开发分支 -->
<revision>1.0.0-SNAPSHOT</revision>
<!-- 发布候选 -->
<revision>1.0.0-RC1</revision>
<!-- 正式发布 -->
<revision>1.0.0</revision>
<!-- 紧急修复 -->
<revision>1.0.1-HOTFIX</revision>
CI/CD 动态覆盖:
bash
# 开发构建
mvn clean install -Drevision=1.0.0-DEV-${BUILD_NUMBER}
# 生产发布
mvn clean deploy -Drevision=1.0.0
4. 多模块版本策略
策略 A:所有模块统一版本(推荐)
xml
<!-- 父 POM -->
<revision>2.0.0</revision>
<!-- 所有子模块 -->
smart-common:2.0.0
smart-auth:2.0.0
smart-api:2.0.0
优点:
- ✅ 版本一致,易于管理
- ✅ 发布时一起升级,原子性操作
缺点:
- ❌ 某个模块小改动也要升级整个版本
策略 B:模块独立版本
xml
<!-- 父 POM -->
<revision>1.0.0</revision>
<!-- 各模块独立定义 -->
smart-common:1.0.0
smart-auth:1.2.0 <!-- 认证模块有更新 -->
smart-api:1.1.0 <!-- API 有小改动 -->
优点:
- ✅ 模块独立演进
缺点:
- ❌ 版本管理复杂
- ❌ 不能使用
${revision},失去 CI Friendly 优势
建议 :对于你的项目,使用策略 A(已采用)。
5. 插件配置最佳实践
xml
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.7.2</version>
<configuration>
<!-- 只解析 CI Friendly 变量,保留其他配置 -->
<flattenMode>resolveCiFriendliesOnly</flattenMode>
<!-- 更新 POM 文件(生成 .flattened-pom.xml) -->
<updatePomFile>true</updatePomFile>
<!-- 扁平化后的依赖范围(可选) -->
<flattenDependencyMode>direct</flattenDependencyMode>
<!-- 输出目录(默认是项目根目录) -->
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
<executions>
<!-- 构建时生成 -->
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<!-- 清理时删除 -->
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
⚠️ 常见误区
误区 1:认为可以手动编辑 .flattened-pom.xml
❌ 错误做法:
bash
# 手动修改 .flattened-pom.xml
vim .flattened-pom.xml
✅ 正确做法:
- 永远不要手动编辑此文件
- 修改原始
pom.xml,插件会自动重新生成
原因:
- 每次构建都会覆盖
- 手动修改会丢失
误区 2:把 .flattened-pom.xml 提交到 Git
❌ 错误做法:
bash
git add .flattened-pom.xml
git commit -m "Add flattened pom"
✅ 正确做法:
bash
# .gitignore
.flattened-pom.xml
**/.flattened-pom.xml
原因:
- 这是构建产物,不是源代码
- 每个开发者构建结果可能不同
- 会导致 Git 冲突
误区 3:误以为删除插件后项目还能正常工作
场景:
xml
<!-- 删除 flatten-maven-plugin 配置 -->
<!-- 但保留 ${revision} 变量 -->
结果:
bash
mvn clean install
# ❌ 构建失败!
教训:
- 如果使用了
${revision}等 CI Friendly 变量 - 就必须配置 flatten-maven-plugin
- 两者是配套的,不能单独删除插件
误区 4:不理解 flattenMode,使用错误的模式
错误示例:
xml
<!-- ❌ 错误:对于多模块项目使用 oss 模式 -->
<flattenMode>oss</flattenMode>
后果:
xml
<!-- .flattened-pom.xml -->
<!-- ❌ <parent> 被移除,子模块无法识别父模块 -->
<!-- ❌ <properties> 全部被移除,丢失重要配置 -->
正确做法:
xml
<!-- ✅ 多模块项目推荐 -->
<flattenMode>resolveCiFriendliesOnly</flattenMode>
误区 5:本地能构建,就认为发布也没问题
场景:
bash
# 本地构建成功
mvn clean install
# ✅ 成功!
# 发布到 Maven 仓库
mvn clean deploy
# ✅ 成功!
# 其他项目依赖
<dependency>
<groupId>org.gycoder</groupId>
<artifactId>smart-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
# ❌ 失败!(如果没有使用 flatten)
原因:
- 本地多模块构建:Maven Reactor 在内存中管理所有模块
- 发布后依赖:Maven 从远程仓库下载 POM,无法解析
${revision}
教训:
- 本地测试不能完全模拟真实依赖场景
- 必须测试"安装到本地仓库 → 新项目依赖"的完整流程
📊 总结对比表
| 特性 | 不使用 flatten-maven-plugin | 使用 flatten-maven-plugin |
|---|---|---|
| 版本号定义 | <version>1.0.0-SNAPSHOT</version> |
<version>${revision}</version> |
| 版本修改 | 需手动修改每个模块 | 只需修改一处 <revision> |
| CI/CD 动态版本 | 需要使用 versions:set 插件 |
通过 -Drevision=xxx 参数 |
| 本地构建 | ✅ 正常 | ✅ 正常 |
| 发布到仓库 | ✅ 正常 | ✅ 正常(使用扁平化 POM) |
| 其他项目依赖 | ✅ 正常 | ✅ 正常(变量已被替换) |
| 版本一致性 | ❌ 容易不一致 | ✅ 强制一致 |
| Yudao 技术栈对齐 | ❌ 不符合 | ✅ 符合最佳实践 |
🎓 核心要点总结
关键认知
-
.flattened-pom.xml是什么?- Maven Flatten Plugin 的构建产物
- 将包含变量的 POM 转换为具体值的 POM
- 用于发布到 Maven 仓库,供其他项目依赖
-
为什么需要它?
- 开发时:需要灵活性(变量、继承、属性)
- 发布时:需要确定性(具体值、独立性、兼容性)
- 扁平化是连接两者的桥梁
-
你的项目必须使用它
- ✅ 已使用
${revision}变量 - ✅ 符合 Yudao 技术栈最佳实践
- ✅ 多模块版本统一管理
- ⚠️ 删除插件会导致构建失败
- ✅ 已使用
实操建议
| 操作 | 建议 | 原因 |
|---|---|---|
| 删除 .flattened-pom.xml | ❌ 不要手动删除 | 会自动重新生成 |
| 编辑 .flattened-pom.xml | ❌ 永远不要编辑 | 构建时会覆盖 |
| 提交到 Git | ❌ 添加到 .gitignore | 这是构建产物 |
| 删除 flatten-maven-plugin | ❌ 必须保留 | 项目依赖此插件 |
| 修改 flattenMode | ⚠️ 保持 resolveCiFriendliesOnly | 适合你的项目 |
深刻理解
Maven 依赖解析的本质问题:
本地构建 远程依赖
↓ ↓
Maven Reactor 独立 POM 文件
(内存管理) (文件读取)
↓ ↓
可解析 ${revision} 无法解析 ${revision}
↓ ↓
构建成功 ✅ 构建失败 ❌
解决方案:flatten-maven-plugin
原始 pom.xml .flattened-pom.xml
↓ ↓
${revision} 变量 1.0.0-SNAPSHOT 具体值
↓ ↓
本地开发使用 发布到仓库使用