Maven Build Cache Extension 深入实战:让 Maven 多模块构建快起来

欢迎你来读这篇博客,这篇博客主要是关于 Maven Build Cache Extension 的使用、原理、配置、远程缓存、CI 落地和常见问题排查。

如果你的项目是一个比较大的 Maven 多模块工程,每次 mvn clean install 都像是在泡一壶很慢的茶,那么 Maven Build Cache
Extension 就值得认真看看。它不是银弹,但在合适的工程里,确实能把构建时间从“等到想摸鱼”压到“刚想摸鱼就结束”。

序言

Maven 本身已经有本地仓库缓存,例如 ~/.m2/repository,它会缓存依赖包、插件包、父 POM 等内容。但
Maven Build Cache Extension 解决的不是“依赖下载慢”的问题,而是“重复构建慢”的问题。

它的核心思路是:

如果一个模块的源码、资源、依赖、effective POM、关键插件参数都没有变化,那么这个模块之前构建出来的结果就可以复用,不需要重新编译、测试、打包。

也就是说,它缓存的是“构建结果”,而不是“依赖包”。

官方文档当前版本为 1.2.3,要求 Maven 3.9.0+,并且兼容 Maven 4。本文基于官方文档整理,并结合实际 Java / Spring Boot
多模块项目的落地方式进行说明。

正文

一、Maven Build Cache Extension 是什么

Maven Build Cache Extension 是 Apache Maven 官方提供的构建缓存扩展。

它会在 Maven 构建过程中介入,根据当前模块的输入信息生成一个缓存 key。如果缓存中已经存在相同 key 的构建结果,就直接恢复构建产物;如果不存在,就按照
Maven 原有逻辑正常构建,并在构建完成后写入缓存。

简单理解:

flowchart LR
    A[开始 Maven 构建] --> B[收集模块输入]
    B --> C[计算缓存 Key]
    C --> D{缓存是否命中}
    D -->|命中| E[恢复构建产物]
    D -->|未命中| F[正常编译/测试/打包]
    F --> G[保存构建结果到缓存]
    E --> H[继续后续模块]
    G --> H

这里的“模块输入”通常包括:

  • 源码文件,例如 src/main/javasrc/test/java
  • 资源文件,例如 application.ymlmapper.xml
  • Maven 依赖
  • effective POM
  • 插件配置和关键运行参数
  • 额外声明的输入目录,例如 protosqlgraphqlavro

二、它和普通 Maven 本地仓库缓存有什么区别

很多人第一次看到这个插件,会误以为它只是又搞了一个 Maven 仓库缓存。其实不是。

对比项 Maven 本地仓库 Maven Build Cache Extension
缓存对象 依赖包、插件包、POM 模块构建结果
默认目录 ~/.m2/repository ~/.m2/build-cache 或自定义目录
解决问题 避免重复下载依赖 避免重复编译、测试、打包
是否关注源码变化 不关注 关注
是否关注插件参数 不关注 关注
是否适合 CI 提速 一般 很适合

举个例子:

一个多模块项目:

1
2
3
4
5
6
mall-parent
├── mall-common
├── mall-domain
├── mall-infrastructure
├── mall-application
└── mall-adapter

你只改了 mall-adapter 的一个 Controller。正常情况下,Maven 可能仍然会经过很多模块的生命周期。启用 Build Cache
后,如果前面几个模块的输入没有变化,它们可以直接从缓存恢复产物,构建会明显变快。

三、适合什么场景

它比较适合这些场景:

  • 大型 Maven 多模块项目
  • Spring Boot / Spring Cloud 微服务工程
  • CI 中频繁跑 mvn verifymvn packagemvn install
  • PR 构建、MR 构建、分支构建耗时较长
  • 本地开发经常需要构建整套工程
  • 有代码生成步骤,例如 Protobuf、MapStruct、OpenAPI、JPA Metamodel
  • 有比较重的测试、打包或资源处理流程

它不太适合这些场景:

  • 项目很小,构建本来只要几秒
  • 构建过程强依赖当前机器环境
  • 构建产物高度不确定,例如构建过程中生成随机文件、时间戳文件、临时日志
  • 正式生产发布链路要求完全干净构建

我个人建议:

日常开发和 CI 可以使用 Build Cache;正式 release 构建建议关闭缓存,走干净构建。

原因很简单:提效归提效,生产发布链路还是要保守一点。工程上最怕的不是慢一点,而是快得让你不敢信。

四、前置条件

先确认 Maven 版本:

1
mvn -v

官方要求:

1
Apache Maven 3.9.0+

如果版本低于 3.9.0,建议先升级 Maven。

然后确认项目根目录是否有 .mvn 目录。如果没有,创建即可:

1
2
3
4
your-project
├── .mvn
├── pom.xml
└── ...

五、第一步:启用 Maven Build Cache Extension

推荐在项目根目录创建:

1
.mvn/extensions.xml

内容如下:

1
2
3
4
5
6
7
8
9
10

<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.1.0 https://maven.apache.org/xsd/core-extensions-1.1.0.xsd">
<extension>
<groupId>org.apache.maven.extensions</groupId>
<artifactId>maven-build-cache-extension</artifactId>
<version>1.2.3</version>
</extension>
</extensions>

虽然也可以写在 pom.xml<build><extensions> 里,但更推荐 .mvn/extensions.xml。因为它属于 Maven Core Extension,越早介入
Maven 构建过程,对构建缓存这种能力越合适。

六、第二步:添加缓存配置文件

在项目根目录创建:

1
.mvn/maven-build-cache-config.xml

先给一个适合大多数 Java / Spring Boot 项目的基础配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://maven.apache.org/BUILD-CACHE-CONFIG/1.3.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/BUILD-CACHE-CONFIG/1.3.0 https://maven.apache.org/xsd/build-cache-config-1.3.0.xsd">

<configuration>
<enabled>true</enabled>
<hashAlgorithm>XX</hashAlgorithm>

<local>
<maxBuildsCached>3</maxBuildsCached>
</local>
</configuration>

<input>
<global>
<glob>{*.java,*.xml,*.properties,*.yml,*.yaml,*.proto,*.sql}</glob>
<excludes>
<exclude>.idea</exclude>
<exclude>.vscode</exclude>
<exclude>target</exclude>
<exclude glob="*.log"></exclude>
<exclude glob="*.tmp"></exclude>
</excludes>
</global>
</input>

<executionControl>
<runAlways>
<goalsLists>
<goalsList artifactId="maven-install-plugin">
<goals>
<goal>install</goal>
</goals>
</goalsList>
</goalsLists>
</runAlways>

<reconcile>
<plugins>
<plugin artifactId="maven-surefire-plugin" goal="test">
<reconciles>
<reconcile propertyName="skip" skipValue="true"/>
<reconcile propertyName="skipExec" skipValue="true"/>
<reconcile propertyName="skipTests" skipValue="true"/>
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
</reconciles>
</plugin>
</plugins>
</reconcile>
</executionControl>
</cache>

这个配置里最重要的是四块:

  1. configuration
  2. input
  3. executionControl/runAlways
  4. executionControl/reconcile

下面逐个解释。

七、configuration 配置说明

1
2
3
4
5
6
7
8
9

<configuration>
<enabled>true</enabled>
<hashAlgorithm>XX</hashAlgorithm>

<local>
<maxBuildsCached>3</maxBuildsCached>
</local>
</configuration>

enabled 表示是否启用缓存。

hashAlgorithm 表示 hash 算法。官方默认是 XX,也就是 xxHash,速度快,适合大多数场景。

local.maxBuildsCached 表示每个项目本地最多保留多少份缓存构建记录,默认值也是 3。如果磁盘空间充足,可以适当调大;如果本地空间紧张,保持默认即可。

如果要指定本地缓存目录,可以这样:

1
2
3
4
5

<local>
<location>/data/maven-build-cache</location>
<maxBuildsCached>5</maxBuildsCached>
</local>

也可以通过命令行临时指定:

1
mvn verify -Dmaven.build.cache.location=/data/maven-build-cache

八、input 配置说明

1
2
3
4
5
6
7
8
9
10
11
12
13

<input>
<global>
<glob>{*.java,*.xml,*.properties,*.yml,*.yaml,*.proto,*.sql}</glob>
<excludes>
<exclude>.idea</exclude>
<exclude>.vscode</exclude>
<exclude>target</exclude>
<exclude glob="*.log"></exclude>
<exclude glob="*.tmp"></exclude>
</excludes>
</global>
</input>

input 决定哪些文件参与缓存 key 的计算。

这个配置非常关键。因为 Build Cache 的判断逻辑可以简化成:

1
2
输入一致 => key 一致 => 可以复用缓存
输入变化 => key 变化 => 重新构建

对于普通 Spring Boot 项目,建议至少包含:

1
2
3
4
5
*.java
*.xml
*.properties
*.yml
*.yaml

如果项目里有 MyBatis XML,需要 *.xml

如果项目里有 Protobuf,需要 *.proto

如果项目里有 SQL 脚本参与构建或测试,需要 *.sql

如果项目里有 GraphQL schema,可以加:

1
2

<glob>{*.java,*.xml,*.properties,*.yml,*.yaml,*.proto,*.sql,*.graphqls}</glob>

如果项目里有 Avro schema,可以加:

1
2

<glob>{*.java,*.xml,*.properties,*.yml,*.yaml,*.proto,*.sql,*.avsc}</glob>

九、为什么要排除临时文件

临时文件、日志文件、IDE 文件如果参与 key 计算,会导致缓存频繁失效。

例如:

1
2
3
target/build.log
.idea/workspace.xml
debug-output.tmp

这些文件变化并不一定代表源码逻辑变化。如果它们参与缓存 key,会导致:

1
代码没变,但缓存不命中

所以要排除。

常见排除项:

1
2
3
4
5
6
7
8
9

<excludes>
<exclude>.idea</exclude>
<exclude>.vscode</exclude>
<exclude>target</exclude>
<exclude glob="*.log"></exclude>
<exclude glob="*.tmp"></exclude>
<exclude glob="*.iml"></exclude>
</excludes>

十、runAlways:哪些插件必须总是执行

有些 Maven 插件不适合完全被缓存跳过,比如 maven-install-plugin

假设你执行:

1
mvn install

如果某个模块直接从缓存恢复了构建产物,但 install 阶段没有真正执行,就可能出现:

1
target 里看起来有产物,但 ~/.m2/repository 没有更新

所以可以让 install 总是执行:

1
2
3
4
5
6
7
8
9
10
11
12

<executionControl>
<runAlways>
<goalsLists>
<goalsList artifactId="maven-install-plugin">
<goals>
<goal>install</goal>
</goals>
</goalsList>
</goalsLists>
</runAlways>
</executionControl>

如果你有一些强依赖当前环境的插件,也可以考虑放入 runAlways,例如某些部署、发布、报告生成插件。

不过不要滥用。放太多插件进 runAlways,缓存收益会下降。

十一、reconcile:防止 skipTests 污染缓存

这是一个很容易踩的坑。

假设某次你执行:

1
mvn verify -DskipTests=true

如果没有做好运行时参数校验,后续构建可能复用这次“跳过测试”的结果。这样就会出现一种非常危险的情况:

1
你以为测试跑了,其实缓存恢复的是跳过测试的构建结果。

所以要对 Surefire 的关键参数做 reconcile:

1
2
3
4
5
6
7
8
9
10
11
12
13

<reconcile>
<plugins>
<plugin artifactId="maven-surefire-plugin" goal="test">
<reconciles>
<reconcile propertyName="skip" skipValue="true"/>
<reconcile propertyName="skipExec" skipValue="true"/>
<reconcile propertyName="skipTests" skipValue="true"/>
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
</reconciles>
</plugin>
</plugins>
</reconcile>

这段配置的意义是:

当测试相关参数发生关键变化时,不要盲目复用缓存。

如果你的项目使用 maven-failsafe-plugin 跑集成测试,也建议加上:

1
2
3
4
5
6
7
8
9

<plugin artifactId="maven-failsafe-plugin" goal="integration-test">
<reconciles>
<reconcile propertyName="skip" skipValue="true"/>
<reconcile propertyName="skipITs" skipValue="true"/>
<reconcile propertyName="skipTests" skipValue="true"/>
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
</reconciles>
</plugin>

十二、第一次运行和验证

配置完成后,先执行一次完整构建:

1
mvn clean verify

然后再执行一次:

1
mvn verify

如果缓存启动成功,日志中应该能看到类似内容:

1
[INFO] Loading cache configuration from <project dir>/.mvn/maven-build-cache-config.xml

同时,本地 Maven 仓库旁边会出现:

1
~/.m2/build-cache

里面会保存构建缓存记录。

你还可以找 buildinfo.xml 看某个模块到底把哪些内容纳入了 key 计算。

常见检查点:

  • 是否真的加载了 .mvn/maven-build-cache-config.xml
  • 是否生成了 ~/.m2/build-cache
  • 是否生成了 buildinfo.xml
  • 第二次构建是否明显变快
  • 修改一个模块后,是否只有受影响模块重新构建

十三、常用命令参数

临时关闭缓存:

1
mvn verify -Dmaven.build.cache.enabled=false

只是不读缓存,但允许写入缓存:

1
mvn verify -Dmaven.build.cache.skipCache=true

读取缓存,但不写入缓存:

1
mvn verify -Dmaven.build.cache.skipSave=true

只在 package 及之后阶段缓存,不缓存 compile / test-compile

1
mvn verify -Dmaven.build.cache.cacheCompile=false

指定本地缓存目录:

1
mvn verify -Dmaven.build.cache.location=/data/maven-build-cache

远程缓存读取开关:

1
mvn verify -Dmaven.build.cache.remote.enabled=true

允许保存到远程缓存:

1
mvn verify -Dmaven.build.cache.remote.save.enabled=true

建议只让 CI 写远程缓存,本地开发机只读远程缓存。

十四、远程缓存是什么

本地缓存只能加速自己的机器。

远程缓存可以让 CI 和其他开发者共享构建结果。

典型流程是:

flowchart TB
    A[CI 构建] --> B[计算缓存 Key]
    B --> C{远程缓存是否存在}
    C -->|存在| D[下载并恢复缓存]
    C -->|不存在| E[正常构建]
    E --> F[上传构建结果到远程缓存]
    G[开发者本地构建] --> H[读取远程缓存]
    H --> I[快速恢复未变化模块]

官方提到,远程缓存可以使用支持 GETPUTHEAD 的 HTTP 存储。例如:

  • Nexus Repository 的 Raw Repository
  • Artifactory 的 Generic Repository
  • Nginx
  • 其他 Maven Resolver / Wagon 支持的存储

十五、配置远程缓存

.mvn/maven-build-cache-config.xml 中增加:

1
2
3
4
5
6
7
8
9

<configuration>
<enabled>true</enabled>
<hashAlgorithm>XX</hashAlgorithm>

<remote enabled="true" id="maven-build-cache">
<url>https://your-cache-server/cache</url>
</remote>
</configuration>

如果需要账号密码,在 ~/.m2/settings.xml 中配置:

1
2
3
4
5
6
7
8
9
10

<settings>
<servers>
<server>
<id>maven-build-cache</id>
<username>cache-user</username>
<password>cache-password</password>
</server>
</servers>
</settings>

注意:

1
2

<id>maven-build-cache</id>

要和配置文件里的:

1
2

<remote enabled="true" id="maven-build-cache">

保持一致。

十六、CI 推荐配置

推荐策略:

环境 是否读缓存 是否写缓存
开发者本地
PR / MR 构建 可选
主干分支 CI
Release 构建

本地开发:

1
2
3
mvn verify \
-Dmaven.build.cache.remote.enabled=true \
-Dmaven.build.cache.skipSave=true

CI 主干构建:

1
2
3
mvn clean verify \
-Dmaven.build.cache.remote.enabled=true \
-Dmaven.build.cache.remote.save.enabled=true

Release 构建:

1
2
mvn clean deploy \
-Dmaven.build.cache.enabled=false

这个策略比较稳:

  • 本地开发能享受远程缓存加速
  • CI 主干负责产出可信缓存
  • Release 不依赖缓存,保证构建链路干净

十七、Spring Boot 多模块项目推荐配置

假设项目结构如下:

1
2
3
4
5
6
7
finance-parent
├── finance-common
├── finance-domain
├── finance-infrastructure
├── finance-application
├── finance-adapter
└── finance-bootstrap

推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://maven.apache.org/BUILD-CACHE-CONFIG/1.3.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/BUILD-CACHE-CONFIG/1.3.0 https://maven.apache.org/xsd/build-cache-config-1.3.0.xsd">

<configuration>
<enabled>true</enabled>
<hashAlgorithm>XX</hashAlgorithm>
<mandatoryClean>false</mandatoryClean>

<local>
<maxBuildsCached>5</maxBuildsCached>
</local>

<!-- 本地接入阶段可以先不启用 remote,稳定后再打开 -->
<!--
<remote enabled="true" id="maven-build-cache">
<url>https://your-cache-server/cache</url>
</remote>
-->
</configuration>

<input>
<global>
<glob>{*.java,*.xml,*.properties,*.yml,*.yaml,*.proto,*.sql,*.graphqls}</glob>
<includes>
<include>src/main/java</include>
<include>src/main/resources</include>
<include>src/test/java</include>
<include>src/test/resources</include>
</includes>
<excludes>
<exclude>.idea</exclude>
<exclude>.vscode</exclude>
<exclude>target</exclude>
<exclude>logs</exclude>
<exclude glob="*.log"></exclude>
<exclude glob="*.tmp"></exclude>
<exclude glob="*.iml"></exclude>
</excludes>
</global>
</input>

<executionControl>
<runAlways>
<goalsLists>
<goalsList artifactId="maven-install-plugin">
<goals>
<goal>install</goal>
</goals>
</goalsList>
</goalsLists>
</runAlways>

<reconcile>
<plugins>
<plugin artifactId="maven-compiler-plugin" goal="compile">
<reconciles>
<reconcile propertyName="source"/>
<reconcile propertyName="target"/>
<reconcile propertyName="release"/>
</reconciles>
</plugin>

<plugin artifactId="maven-surefire-plugin" goal="test">
<reconciles>
<reconcile propertyName="skip" skipValue="true"/>
<reconcile propertyName="skipExec" skipValue="true"/>
<reconcile propertyName="skipTests" skipValue="true"/>
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
</reconciles>
</plugin>

<plugin artifactId="maven-failsafe-plugin" goal="integration-test">
<reconciles>
<reconcile propertyName="skip" skipValue="true"/>
<reconcile propertyName="skipITs" skipValue="true"/>
<reconcile propertyName="skipTests" skipValue="true"/>
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
</reconciles>
</plugin>
</plugins>
</reconcile>
</executionControl>
</cache>

十八、Protobuf / gRPC 项目的配置补充

如果项目使用 Protobuf,例如:

1
src/main/proto

并且使用 protoc-maven-plugin 或类似插件生成代码,那么 *.proto 一定要纳入输入。

可以增加:

1
2
3
4
5
6
7
8
9

<input>
<global>
<glob>{*.java,*.xml,*.properties,*.yml,*.yaml,*.proto}</glob>
<includes>
<include>src/main/proto</include>
</includes>
</global>
</input>

如果插件配置里有自定义 proto 目录,可以针对插件做目录扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<input>
<plugins>
<plugin artifactId="protoc-maven-plugin">
<dirScan mode="auto">
<tagScanConfigs>
<tagScanConfig tagName="protoBaseDirectory"
recursive="true"
glob="{*.proto}"/>
</tagScanConfigs>
</dirScan>
</plugin>
</plugins>
</input>

这样可以避免 proto 文件变化后缓存仍然误命中。

十九、MapStruct / Lombok / 代码生成场景

如果项目使用 MapStruct、Lombok、JPA Metamodel、QueryDSL 等注解处理器,通常构建产物会出现在:

1
target/generated-sources/annotations

默认情况下,Build Cache 会处理常见输出。但如果你的项目有额外生成目录,可以使用 attachedOutputs

例如:

1
2
3
4
5
6
7
8
9

<configuration>
<attachedOutputs preservePermissions="true">
<dirNames>
<dirName>generated-sources/annotations</dirName>
<dirName>generated-test-sources/test-annotations</dirName>
</dirNames>
</attachedOutputs>
</configuration>

注意,dirName 是相对于 target 的路径。

二十、排查缓存为什么不命中

缓存不命中通常有几类原因。

1. 源码或资源确实变了

这是正常情况。

例如:

1
2
3
src/main/java/UserService.java
src/main/resources/application.yml
src/main/resources/mapper/UserMapper.xml

这些文件变化,缓存 key 改变,重新构建是合理的。

2. 换行符不一致

官方文档特别提醒:当前实现没有内置换行符归一化,文件 hash 是按原始字节计算的。

也就是说,同一个文件:

1
2
Windows: CRLF
Linux/macOS: LF

会被认为是不同输入。

建议在项目根目录添加 .gitattributes

1
2
3
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf

然后统一团队工作区换行符。

3. CI 和本地 profile 不一致

比如 CI 激活了:

1
-Pci

本地没有激活。

如果 profile 会增删插件、改变插件配置、改变依赖,那么 effective POM 就不一致,缓存 key 也会不一致。

建议:

不要通过 profile 大量增删插件,而是尽量通过属性控制插件是否跳过。

例如,不推荐:

1
2
3
4
5
6
7
8
9
10
11

<profile>
<id>ci</id>
<build>
<plugins>
<plugin>
<artifactId>some-report-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>

更推荐:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

<properties>
<report.skip>true</report.skip>
</properties>

<build>
<plugins>
<plugin>
<artifactId>some-report-plugin</artifactId>
<configuration>
<skip>${report.skip}</skip>
</configuration>
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>ci</id>
<properties>
<report.skip>false</report.skip>
</properties>
</profile>
</profiles>

这样 effective POM 更稳定。

4. 环境相关参数进入 effective POM

比如某些插件配置里有:

1
2

<argLine>${some.local.path}</argLine>

本地和 CI 的路径不一样,就会导致 effective POM 不一致。

可以排除某些环境相关属性:

1
2
3
4
5
6
7
8
9
10
11
12

<input>
<plugins>
<plugin artifactId="maven-surefire-plugin">
<effectivePom>
<excludeProperties>
<excludeProperty>argLine</excludeProperty>
</excludeProperties>
</effectivePom>
</plugin>
</plugins>
</input>

但是要小心:

排除某个属性,意味着这个属性不同也可能复用同一个缓存。

所以只有当你确认这个属性不影响构建产物等价性时,才应该排除。

5. 临时文件参与了 key 计算

比如某个插件在源码目录旁边生成:

1
2
3
build-time.txt
generated.log
tempfile.out

这些文件每次都变,缓存自然每次都失效。

解决方式:

1
2
3
4
5
6

<excludes>
<exclude>tempfile.out</exclude>
<exclude glob="*.log"></exclude>
<exclude glob="build-time.txt"></exclude>
</excludes>

二十一、使用 failFast 和 baselineUrl 排查远程缓存

远程缓存接入后,本地和 CI 可能因为环境差异导致不命中。

官方推荐可以用:

1
2
3
mvn verify \
-Dmaven.build.cache.failFast=true \
-Dmaven.build.cache.baselineUrl=https://your-cache-url/xxx/build-cache-report.xml

这个命令的作用是:

  • 使用远程 CI 的缓存报告作为 baseline
  • 本地构建时发现第一个差异就失败
  • 输出差异文件,帮助定位为什么不能复用远程缓存

通常会在:

1
target/incremental-maven

看到类似文件:

1
2
3
buildinfo-baseline-xxx.xml
buildinfo-yyy.xml
buildsdiff.xml

重点看 buildsdiff.xml,它会告诉你是哪个文件、哪个插件参数、哪个 effective POM 部分导致不一致。

二十二、性能优化建议

1. 不要缓存巨大压缩包

如果项目会生成很大的 zip、war、ear,有时候上传下载缓存比本地重新打包还慢。

可以排除:

1
2
3
4
5
6
7
8
9
10

<output>
<exclude>
<patterns>
<pattern>.*\.zip</pattern>
<pattern>.*\.war</pattern>
<pattern>.*\.ear</pattern>
</patterns>
</exclude>
</output>

是否排除要看项目情况。比如普通 Spring Boot jar 不一定需要排除;但特别大的前端静态资源包、集成包,就要评估一下。

2. 开启 lazy restore

默认情况下,缓存会比较积极地恢复项目产物。

可以尝试:

1
mvn verify -Dmaven.build.cache.lazyRestore=true

lazyRestore 适合小改动比较多的场景,可以减少不必要的下载和恢复。

3. CI 中可以关闭 generated sources 恢复

如果 CI 不需要把生成源码恢复到项目目录,可以:

1
mvn verify -Dmaven.build.cache.restoreGeneratedSources=false

4. 谨慎使用 memory-mapped hash

官方支持 XXMM 等 memory-mapped hash 算法,大代码库可能更快。

但 JDK 17+ 需要在 .mvn/jvm.config 中增加:

1
--add-opens java.base/sun.nio.ch=ALL-UNNAMED

一般项目直接用默认的 XX 就行,不要一上来就把复杂度拉满。构建优化也是工程活,不是玄学仪式。

二十三、接入路线建议

不要一上来就远程缓存、CI、所有模块一起上。推荐分阶段。

flowchart TB
    A[阶段一:本地启用] --> B[确认扩展加载成功]
B --> C[观察 buildinfo.xml]
C --> D[调整 input/excludes]
D --> E[阶段二:CI 只读远程缓存]
E --> F[阶段三:主干 CI 写远程缓存]
F --> G[阶段四:开发机只读远程缓存]
G --> H[阶段五:监控命中率和构建耗时]

推荐顺序:

  1. 本地启用扩展
  2. mvn clean verify
  3. 再跑 mvn verify
  4. 观察是否命中
  5. 修改一个模块,观察影响范围
  6. 调整 inputexcludesreconcile
  7. 接入 CI
  8. 接入远程缓存
  9. 让主干 CI 写缓存
  10. 让开发机只读缓存

二十四、完整目录示例

最终你的项目可能是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
your-project
├── .gitattributes
├── .mvn
│ ├── extensions.xml
│ ├── jvm.config
│ └── maven-build-cache-config.xml
├── pom.xml
├── module-a
│ └── pom.xml
├── module-b
│ └── pom.xml
└── module-c
└── pom.xml

其中最核心的是:

1
2
.mvn/extensions.xml
.mvn/maven-build-cache-config.xml

二十五、推荐的 Git 提交内容

建议提交到代码仓库:

1
2
3
.mvn/extensions.xml
.mvn/maven-build-cache-config.xml
.gitattributes

不建议提交:

1
2
~/.m2/build-cache
target

如果你有 .gitignore,可以确认这些内容已经排除:

1
2
3
4
5
6
target/
*.log
*.tmp
.idea/
.vscode/
*.iml

二十六、最佳实践总结

第一,先本地跑通,再接远程缓存。

第二,CI 写缓存,本地只读缓存。

第三,正式 release 构建关闭缓存。

第四,把真正影响构建结果的文件纳入 input。

第五,把临时文件、日志、IDE 文件排除掉。

第六,统一团队换行符,避免 Windows / Linux / macOS 因 CRLF 和 LF 导致缓存不命中。

第七,谨慎排除 effective POM 属性。排除属性可以提高命中率,但也可能降低正确性。

第八,对 skipTestsskipITstestFailureIgnore 这类参数做 reconcile。

第九,install 阶段建议总是执行,避免本地仓库没有更新。

第十,持续观察缓存命中率和构建耗时,不要靠感觉调优。

二十七、一份最终推荐配置

下面这份可以作为 Java / Spring Boot 多模块项目的起始配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://maven.apache.org/BUILD-CACHE-CONFIG/1.3.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/BUILD-CACHE-CONFIG/1.3.0 https://maven.apache.org/xsd/build-cache-config-1.3.0.xsd">

<configuration>
<enabled>true</enabled>
<hashAlgorithm>XX</hashAlgorithm>
<mandatoryClean>false</mandatoryClean>

<local>
<maxBuildsCached>5</maxBuildsCached>
</local>

<!-- 稳定后再打开远程缓存 -->
<!--
<remote enabled="true" id="maven-build-cache">
<url>https://your-cache-server/cache</url>
</remote>
-->
</configuration>

<input>
<global>
<glob>{*.java,*.xml,*.properties,*.yml,*.yaml,*.proto,*.sql,*.graphqls,*.avsc}</glob>
<includes>
<include>src/main/java</include>
<include>src/main/resources</include>
<include>src/test/java</include>
<include>src/test/resources</include>
<include>src/main/proto</include>
</includes>
<excludes>
<exclude>.idea</exclude>
<exclude>.vscode</exclude>
<exclude>target</exclude>
<exclude>logs</exclude>
<exclude glob="*.log"></exclude>
<exclude glob="*.tmp"></exclude>
<exclude glob="*.iml"></exclude>
</excludes>
</global>
</input>

<executionControl>
<runAlways>
<goalsLists>
<goalsList artifactId="maven-install-plugin">
<goals>
<goal>install</goal>
</goals>
</goalsList>
</goalsLists>
</runAlways>

<reconcile>
<plugins>
<plugin artifactId="maven-compiler-plugin" goal="compile">
<reconciles>
<reconcile propertyName="source"/>
<reconcile propertyName="target"/>
<reconcile propertyName="release"/>
</reconciles>
</plugin>

<plugin artifactId="maven-surefire-plugin" goal="test">
<reconciles>
<reconcile propertyName="skip" skipValue="true"/>
<reconcile propertyName="skipExec" skipValue="true"/>
<reconcile propertyName="skipTests" skipValue="true"/>
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
</reconciles>
</plugin>

<plugin artifactId="maven-failsafe-plugin" goal="integration-test">
<reconciles>
<reconcile propertyName="skip" skipValue="true"/>
<reconcile propertyName="skipITs" skipValue="true"/>
<reconcile propertyName="skipTests" skipValue="true"/>
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
</reconciles>
</plugin>
</plugins>
</reconcile>
</executionControl>
</cache>

参考资料

启示录

Maven Build Cache Extension 的价值,不只是“让构建快一点”,而是让构建系统开始具备一种更工程化的能力:识别哪些东西真的变了,哪些东西只是重复劳动。

但缓存系统永远有两面:一面是速度,一面是正确性。

真正好的落地方式,不是把缓存开关一打开就完事,而是逐步让项目的输入、输出、插件参数、CI
环境都变得更稳定、更可解释。构建缓存不是魔法,它更像一面镜子:项目构建越规范,它越好用;项目构建越随缘,它越容易把问题照出来。

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


Maven Build Cache Extension 深入实战:让 Maven 多模块构建快起来
https://allendericdalexander.github.io/2026/06/12/scm/maven-build-cache-extension/
作者
AtLuoFu
发布于
2026年6月12日
许可协议