Maven SNAPSHOT 更新排查:-U 无效、clean 无用,问题在 _remote.repositories(附完整链路)

老炮踩坑录 · 番外篇 · 翻车现场系列

基于「基础企业档案」真实项目代码,复盘一次"同事 deploy 了新 SNAPSHOT,我本地怎么构建都是旧代码"的排查

关键词:SNAPSHOT · updatePolicy · maven-metadata.xml · -U · _remote.repositories

👋 欢迎阅读

🏠个人主页: 知守观

📘我的专栏: 老炮踩坑录

💻当前内容:Maven更新策略

文章目录

前言

番外篇讲了改完 common 模块忘了 install、打出来的包是旧逻辑的事。install 解决的是本地源码到本地仓库这一段。本地仓库再往上游走,还有一段路:同事的代码到我的本地仓库,中间隔着公司私服。

这一段我翻过车,翻了三天。

还是 base-archives 那个项目,还是 D:\Maven\repertory 那个仓库。时间线接着番外篇往后两周。

现象:他说 deploy 了,我这边没变

当时 common 模块有个 bug,行政区划编码在某种入参下处理不对,同事领了修复任务。下午四点多,他在群里说改完了,deploy 到私服了,版本号不变,还是 0.0.1-SNAPSHOT,让我本地验证一下。

我拉了他最新代码确认改动内容,然后构建我自己的工程,跑验证用例。

bug 还在。

同事的第一反应是自己没 deploy 成功,又 deploy 了一次,并截图发我,BUILD SUCCESS。我这边重新构建,结果 bug 还在。

到下班我也没搞明白。第二天我继续折腾,-U 也用了,还是没用。第三天上午我把本地仓库里 common 的目录整个删了,重新构建,新逻辑出来了。

问题解决了,但解决得莫名其妙。我把删除前备份的目录翻出来对比,才搞明白这三天卡在哪里。

排查:clean、强制更新、等,挨个试了一遍

mvn clean 和这件事没关系

第一天下班前,我执行得最多的命令是 mvn clean package。直觉上"没拿到新代码"就该 clean 一下。

clean 只清各模块的 target 目录,编译产物删掉重来。本地仓库在 D:\Maven\repertory,clean 根本不碰它------执行完 clean,本地仓库里的 jar 一个都不会少。

这么想的不止我一个,一起排查的同事当时也建议我"再 clean 一次试试"。

第二天:daily 策略该过期了,还是没变

当晚我回家想了下,SNAPSHOT 有个更新检查策略 updatePolicy,默认 daily,24 小时内不重复检查远程。第一天的现象用它解释得通:我早上构建过一次,检查过了,下午同事 deploy,还在 24 小时窗口内,不检查,继续用本地缓存。

那睡一觉到第二天,超过 24 小时,总该检查了吧。

第二天上午构建,bug 还在。

-U 强制更新,日志里什么动静都没有

不等了,上强制参数:

复制代码
mvn clean package -U -DskipTests

-U 即 --update-snapshots,跳过 updatePolicy 的时间窗口,每次都检查远程 SNAPSHOT 有没有更新。

构建成功。跑验证,还是旧行为,bug还在。

回头看构建日志,关于 common 这个依赖,一行下载记录都没有。正常情况下 -U 检测到远程有更新,日志里会有 Downloading 和 Downloaded。我这里连 metadata 的下载记录都没有,Maven 压根没去远程问。

到这一步可以确定:问题不在时间策略,在更新前面的某个环节,Maven 直接跳过了整个更新检查。

定位:本地仓库目录下的三个文件

去 D:\Maven\repertory\cn\xiaomayi\base-archives-common\0.0.1-SNAPSHOT 看,SNAPSHOT 目录下除了 jar 和 pom,还有几个平时注意不到的文件:

复制代码
maven-metadata-nexus.xml
maven-metadata-nexus.xml.sha1
resolver-status.properties
_remote.repositories
base-archives-common-0.0.1-SNAPSHOT.jar
base-archives-common-0.0.1-SNAPSHOT.pom

挨个打开看。

maven-metadata-nexus.xml 是远程私服的元数据缓存,记录这个 SNAPSHOT 当前最新版本的 timestamp 和 buildNumber。里面的时间戳是上周的------同事 deploy 的新版本信息根本没出现在这里,坐实了"Maven 没去远程访问过"。

resolver-status.properties 记录上次检查时间:

复制代码
maven-metadata-nexus.xml.lastUpdated=1665478800000

daily 策略就靠它判断要不要重新检查。用 -U 之后这个时间戳刷新了,说明检查动作本身执行了,但检查完没去下载。

_remote.repositories 的内容是关键:

复制代码
base-archives-common-0.0.1-SNAPSHOT.jar>old-nexus=
base-archives-common-0.0.1-SNAPSHOT.pom>old-nexus=

先说下这个文件的格式。每行一条登记信息:文件名>仓库id=。> 只是个分隔符,没有特殊含义;= 右边固定为空。这两行读作:jar 和 pom 是从 id 为 old-nexus 的仓库下载的。本地 mvn install 进去的 artifact,仓库 id 的位置是空的,形如 xxx.jar>=。

我打开当时用的 settings.xml,里面 mirror 配置的 id 是 nexus。

对不上。

原因:更新检查之前,还有一道来源校验

_remote.repositories 是 Maven 3 引入的本地仓库管理机制。每个从远程下载的 artifact,都会在这个文件里记录它是从哪个仓库 id 下载的。本地 mvn install 进去的,> 后面是空的。

Maven 对一个本地已有的 artifact 做更新检查之前,先看这个文件:标记的来源仓库 id,在不在当前构建配置的仓库列表里。在,正常走更新流程。不在,Maven 认为这个 artifact 和当前配置的仓库对不上号,整个更新检查跳过------日志不打,错误不报,静默用旧的。

两周前公司迁移私服,老 Nexus 下线,换了新地址。我按 wiki 换了 settings.xml,新配置的仓库 id 从 old-nexus 改成了 nexus。本地仓库里之前下载的所有 artifact,_remote.repositories 里记的还是 old-nexus。

两边的账从那天起就对不上了。大部分依赖是 release 版本,不更新,不触发检查,相安无事。common 这个 SNAPSHOT 天天在变,第一个撞上。

完整的更新链路,按顺序是四步:
#mermaid-svg-GTzp5iQoJ9gBKyFx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GTzp5iQoJ9gBKyFx .error-icon{fill:#552222;}#mermaid-svg-GTzp5iQoJ9gBKyFx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GTzp5iQoJ9gBKyFx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .marker.cross{stroke:#333333;}#mermaid-svg-GTzp5iQoJ9gBKyFx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GTzp5iQoJ9gBKyFx p{margin:0;}#mermaid-svg-GTzp5iQoJ9gBKyFx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .cluster-label text{fill:#333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .cluster-label span{color:#333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .cluster-label span p{background-color:transparent;}#mermaid-svg-GTzp5iQoJ9gBKyFx .label text,#mermaid-svg-GTzp5iQoJ9gBKyFx span{fill:#333;color:#333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .node rect,#mermaid-svg-GTzp5iQoJ9gBKyFx .node circle,#mermaid-svg-GTzp5iQoJ9gBKyFx .node ellipse,#mermaid-svg-GTzp5iQoJ9gBKyFx .node polygon,#mermaid-svg-GTzp5iQoJ9gBKyFx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GTzp5iQoJ9gBKyFx .rough-node .label text,#mermaid-svg-GTzp5iQoJ9gBKyFx .node .label text,#mermaid-svg-GTzp5iQoJ9gBKyFx .image-shape .label,#mermaid-svg-GTzp5iQoJ9gBKyFx .icon-shape .label{text-anchor:middle;}#mermaid-svg-GTzp5iQoJ9gBKyFx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GTzp5iQoJ9gBKyFx .rough-node .label,#mermaid-svg-GTzp5iQoJ9gBKyFx .node .label,#mermaid-svg-GTzp5iQoJ9gBKyFx .image-shape .label,#mermaid-svg-GTzp5iQoJ9gBKyFx .icon-shape .label{text-align:center;}#mermaid-svg-GTzp5iQoJ9gBKyFx .node.clickable{cursor:pointer;}#mermaid-svg-GTzp5iQoJ9gBKyFx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .arrowheadPath{fill:#333333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GTzp5iQoJ9gBKyFx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GTzp5iQoJ9gBKyFx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GTzp5iQoJ9gBKyFx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GTzp5iQoJ9gBKyFx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GTzp5iQoJ9gBKyFx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GTzp5iQoJ9gBKyFx .cluster text{fill:#333;}#mermaid-svg-GTzp5iQoJ9gBKyFx .cluster span{color:#333;}#mermaid-svg-GTzp5iQoJ9gBKyFx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GTzp5iQoJ9gBKyFx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GTzp5iQoJ9gBKyFx rect.text{fill:none;stroke-width:0;}#mermaid-svg-GTzp5iQoJ9gBKyFx .icon-shape,#mermaid-svg-GTzp5iQoJ9gBKyFx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GTzp5iQoJ9gBKyFx .icon-shape p,#mermaid-svg-GTzp5iQoJ9gBKyFx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GTzp5iQoJ9gBKyFx .icon-shape .label rect,#mermaid-svg-GTzp5iQoJ9gBKyFx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GTzp5iQoJ9gBKyFx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GTzp5iQoJ9gBKyFx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GTzp5iQoJ9gBKyFx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 未过期且无 -U
已过期或 -U
id 不在当前配置
id 匹配
远程无更新
远程有更新
开始构建
步骤1: 检查 updatePolicy 和 resolver-status.properties
使用本地缓存, 结束
步骤2: 检查 _remote.repositories 来源仓库 id
静默跳过更新, 使用本地旧 jar, 结束
步骤3: 下载远程 maven-metadata.xml 对比 timestamp
使用本地缓存, 结束
步骤4: 下载新 jar
构建成功, 使用新代码

网上的文章基本只讲第一步和第三步。第二步出事的时候,-U 绕得过时间窗口,绕不过来源校验。

另外补一个相关场景:如果你本地对 common 执行过 mvn install,_remote.repositories 里 > 后面是空的本机标记,Maven 把它当本地产物,同样不检查远程更新。自己 install 过一次,把同事 deploy 的版本晾在一边,这种翻车方式也常见。

这套匹配规则的具体行为跟 Maven 版本有关,我在 3.6.3 上验证的是"静默跳过更新"。其他版本是拒绝使用还是报警告,我没逐个试过,不替它们下结论。

修复

彻底的办法:删掉本地仓库里对应的模块目录,重新下载。新下载的 artifact 会用当前的仓库 id 重新登记。

复制代码
Remove-Item -Recurse -Force "D:\Maven\repertory\cn\xiaomayi\base-archives-common"

临时应急 :-llr(--legacy-local-repository),让 Maven 按 Maven 2 的老规则用本地仓库,忽略 _remote.repositories 的来源校验。

复制代码
mvn clean package -U -llr -DskipTests

这个参数适合救急,不适合长期用------来源校验本身防的是"仓库串号"的问题,长期绕开会把本地仓库搞乱。

还有一个教训层面的做法:迁移私服的时候,settings.xml 里仓库 id 保持不变,只改 url。id 一变,本地所有存量缓存的登记信息全部作废,坑就埋下了。

这个坑为什么难排查

全程没有任何报错。-U 执行了,BUILD SUCCESS,日志干净。Maven 跳过一个内部流程的时候没有任何提示,从输出上看,一切正常。

排查方向容易被第一天的时间差带偏。updatePolicy daily 是个众所周知的点,第一天的现象又正好符合它,第二天、第三天还用这个思路去解释,就越走越远。

_remote.repositories 这个文件几乎没人提。它藏在每个 artifact 目录里,纯文本,机制写在 Maven 源码里,官方文档里基本没提它。出问题之前,我不知道它的存在,出问题之后搜中文资料,讲它的文章也不多。

Maven 跳过一个内部流程的时候没有任何提示,从输出上看,一切正常。

自查清单

老炮点评

Maven 的缓存设计是层层"能不动就不动":时间窗口挡一次,来源校验挡一次,本地 install 再挡一次。每一层单独看都合理,叠在一起,"同事的代码三天到不了我手上"就是真实会发生的事。

排查缓存类问题,别满足于"换了个参数好了"或者"删了重下好了"。好了之后回头把机制搞清楚,下次再遇到不一样的症状,才不用又花三天。

上一篇番外篇讲了多模块没install导致打包还是旧代码,这篇讲SNAPSHOT拉不到,两篇都是Maven仓库机制的坑。


下期预告:《mirrorOf=* 把私服请求拦去了公网镜像》

settings.xml 里抄来一行 mirrorOf=*,所有仓库请求都被镜像接管,包括公司私服。本地能下载的依赖,CI 上 404;私服里明明有的包,Maven 偏偏去公网镜像找。

下期讲 mirror 的匹配规则,以及 ! 排除写法为什么一定要会。

如果本文对你有帮助,欢迎:

👍 点赞 | ⭐ 收藏 | 👤 关注| 💬 留言

我是老炮,Java 老兵,仍在一线。关注「Java老炮踩坑录」,看真实案例,少踩坑。

相关推荐
溪语流沙1 小时前
面试加分项!Manacher:O(n)求最长回文子串的对称之美
java·python·leetcode·面试·职场和发展
牛战士coding1 小时前
从一句\“取消订单\“到退款通知:AI 写操作完整业务闭环
java·人工智能
啵啵啵鱼1 小时前
第三章 栈、队列和数组相关结构
java·数据结构·栈·队列
瑶山1 小时前
stable-diffusion.cpp 参数全集整理(sd-cli.exe / sd-server.exe)
java·数据库·stable diffusion·参数全集
CallFay云起未来1 小时前
2026年AI客服能不能减少人工回复?从知识检索到人机协同的技术实现
java·大数据·前端·人工智能·架构·文心一言
IT_陈寒2 小时前
一文搞懂SpringBoot的核心概念
前端·人工智能·后端
伯恩bourne2 小时前
对比 @Configuration(Full) vs @Component(Lite)
java·开发语言
RuoyiOffice2 小时前
达梦 DM8、openGauss、人大金仓怎么选?RuoYi Office 国产数据库适配与验收指南
java·数据库·spring boot·架构·信创
修船大队长2 小时前
KL15 高电平请求 FullCom:AUTOSAR 项目调用流程
android·java·服务器