欢迎你来读这篇博客,这篇博客主要是关于 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/java、src/test/java
资源文件,例如 application.yml、mapper.xml
Maven 依赖
effective POM
插件配置和关键运行参数
额外声明的输入目录,例如 proto、sql、graphql、avro 等
二、它和普通 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 verify、mvn package、mvn install
PR 构建、MR 构建、分支构建耗时较长
本地开发经常需要构建整套工程
有代码生成步骤,例如 Protobuf、MapStruct、OpenAPI、JPA Metamodel
有比较重的测试、打包或资源处理流程
它不太适合这些场景:
项目很小,构建本来只要几秒
构建过程强依赖当前机器环境
构建产物高度不确定,例如构建过程中生成随机文件、时间戳文件、临时日志
正式生产发布链路要求完全干净构建
我个人建议:
日常开发和 CI 可以使用 Build Cache;正式 release 构建建议关闭缓存,走干净构建。
原因很简单:提效归提效,生产发布链路还是要保守一点。工程上最怕的不是慢一点,而是快得让你不敢信。
四、前置条件 先确认 Maven 版本:
官方要求:
如果版本低于 3.9.0,建议先升级 Maven。
然后确认项目根目录是否有 .mvn 目录。如果没有,创建即可:
1 2 3 4 your-project ├── .mvn ├── pom.xml └── ...
五、第一步:启用 Maven Build Cache Extension 推荐在项目根目录创建:
内容如下:
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 >
这个配置里最重要的是四块:
configuration
input
executionControl/runAlways
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
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 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。
假设你执行:
如果某个模块直接从缓存恢复了构建产物,但 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 [INFO] Loading cache configuration from <project dir>/.mvn/maven-build-cache-config.xml
同时,本地 Maven 仓库旁边会出现:
里面会保存构建缓存记录。
你还可以找 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[快速恢复未变化模块]
官方提到,远程缓存可以使用支持 GET、PUT、HEAD 的 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 > </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,例如:
并且使用 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 激活了:
本地没有激活。
如果 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[阶段五:监控命中率和构建耗时]
推荐顺序:
本地启用扩展
跑 mvn clean verify
再跑 mvn verify
观察是否命中
修改一个模块,观察影响范围
调整 input、excludes、reconcile
接入 CI
接入远程缓存
让主干 CI 写缓存
让开发机只读缓存
二十四、完整目录示例 最终你的项目可能是这样:
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 属性。排除属性可以提高命中率,但也可能降低正确性。
第八,对 skipTests、skipITs、testFailureIgnore 这类参数做 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 > </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 环境都变得更稳定、更可解释。构建缓存不是魔法,它更像一面镜子:项目构建越规范,它越好用;项目构建越随缘,它越容易把问题照出来。
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。