`.flattened-pom.xml` 深度解析

🎯 问题背景

在 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}/...

错误原因

  1. Maven 尝试从本地仓库读取 smart-dependencies:${revision}
  2. 变量 ${revision} 没有被解析,直接作为字符串使用
  3. 路径中包含非法字符 ${},导致失败

💡 深度理解

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 技术栈对齐 ❌ 不符合 ✅ 符合最佳实践

🎓 核心要点总结

关键认知

  1. .flattened-pom.xml 是什么?

    • Maven Flatten Plugin 的构建产物
    • 将包含变量的 POM 转换为具体值的 POM
    • 用于发布到 Maven 仓库,供其他项目依赖
  2. 为什么需要它?

    • 开发时:需要灵活性(变量、继承、属性)
    • 发布时:需要确定性(具体值、独立性、兼容性)
    • 扁平化是连接两者的桥梁
  3. 你的项目必须使用它

    • ✅ 已使用 ${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 具体值
                      ↓                              ↓
                本地开发使用                      发布到仓库使用
相关推荐
小李飞飞砖3 小时前
RemoteViews的layout无法设置xml类型的Drawable,会报错
xml
武子康3 小时前
Java-209 Spring AMQP 整合 RabbitMQ 实战:XML 配置直连交换机、RabbitAdmin 自动声明与收发闭环
xml·java·spring·rabbitmq·java-rabbitmq·java-activemq
利刃大大3 天前
【Mybatis】Mybatis入门 && 基础操作 && XML配置文件开发 && 多表查询 && 注入问题 && 数据库连接池
xml·数据库·mybatis
墨痕诉清风4 天前
java漏洞集合工具(Struts2、Fastjson、Weblogic(xml)、Shiro、Log4j、Jboss、SpringCloud)
xml·java·struts·安全·web安全·spring cloud·log4j
Lbwnb丶4 天前
failure: repodata/repomd.xml from base: [Errno 256] No more mirrors to try.
xml
拾忆,想起4 天前
设计模式三大分类完全解析:构建高质量软件的基石
xml·微服务·设计模式·性能优化·服务发现
前网易架构师-高司机4 天前
汽车充电插口识别数据集,可识别快充,慢充插口,支持yolo,coco json,pascal voc xml格式的标注数据集
xml·yolo·汽车·快充·充电·m慢充·插口
武藤一雄4 天前
彻底吃透.NET中序列化反序列化
xml·微软·c#·json·.net·.netcore
spencer_tseng4 天前
org.eclipse.wst.common.project.facet.core.xml could not be read.
xml·java·eclipse