Maven 多模块工程踩坑:没 install 就跑,打出来的 jar 还是旧代码(附排查命令)
老炮踩坑录 · 番外篇 · 翻车现场系列基于「基础企业档案」真实项目代码复盘一次 “IDEA 里正常、打成 jar 就回到旧逻辑” 的怪事关键词多模块 · mvn install · 本地仓库 · reactor · -am 欢迎阅读个人主页知守观我的专栏老炮踩坑录当前内容文件上传大小限制文章目录前言改动和现象排查旧代码到底从哪进的包原因reactor 里有没有它决定 Maven 去哪拿两种改法IDEA 为什么是对的怎么确认自己跑的是哪份代码这个坑为什么难排查自查清单老炮点评前言上一篇讲了“基础企业档案” 项目换 IDEA 版本启动失败的事故。启动问题解决之后项目能跑了我顺手拿它改点代码验证记忆里的一些逻辑。然后踩了第二个坑。common 模块里有个工具方法我改了它的实现。改动很小一行的事。在 IDEA 里 Run行为符合预期自测通过。但是打包丢到测试环境结果跟没改一样。同一份源码IDEA 里是新逻辑jar 包里是旧逻辑。改动和现象改动的是基础企业档案项目里的一个工具类publicclassAreaCodeUtils{publicstaticStringnormalize(Stringcode){if(codenull){return;}returncode.trim();}}这是改完的版本改动前没有trim()入参什么样就原样返回。web 模块有个 Service 调用它处理行政区划编码。在 IDEA 里跑传带空格的编码输出是 trim 过的新逻辑生效。打成 jar 跑传同样的入参空格原样带出来还是旧逻辑。排查旧代码到底从哪进的包第一个怀疑对象是测试环境——是不是部署脚本没换包。本地java -jar跑了一遍刚打出来的 jar还是旧行为。环境因素可以排除说明jar包本身就是旧的。把 jar 解开看。Spring Boot 打出来的 fat jar业务模块的依赖在BOOT-INF/lib/下面找到base-archives-common-0.0.1-SNAPSHOT.jar把里面的AreaCodeUtils.class拖进 IDEA 看反编译结果publicstaticStringnormalize(Stringcode){if(codenull){return;}returncode;}没有调用 trim方法。打进包里的确实是旧代码。打包这个动作发生在本机旧代码的来源只可能在本机。于是去看本地仓库dirD:\Maven\repertory\cn\xiaomayi\base-archives-common\0.0.1-SNAPSHOT\输出目录: D:\Maven\repertory\cn\xiaomayi\base-archives-common\0.0.1-SNAPSHOT Mode LastWriteTime Length Name ---- ----------------- ------ -------------------- -a---- 2026/10/5 14:23 15234 base-archives-common-0.0.1-SNAPSHOT.jar -a---- 2026/10/5 14:23 2841 base-archives-common-0.0.1-SNAPSHOT.pomdir是Get-ChildItem的别名默认输出里就有 LastWriteTime 这一列即文件最后修改时间。jar 的时间停在上周。我改代码是今天。本地仓库里的 common jar是上周 install 的那一份。我今天的改动只存在于源码和target/classes里从来没有进过本地仓库。原因reactor 里有没有它决定 Maven 去哪拿先解释下 reactor 是什么。多模块工程执行 mvn 命令时Maven 会先算一遍这次构建有哪些模块参与、按什么依赖顺序构建。这个动态算出来的模块清单就叫 reactor——每次构建都可能不一样根目录全量构建时所有模块都在加-pl就只留指定的模块。reactor 内外的解析方式完全不同。Maven 构建时解析依赖有一个固定的顺序参与本次构建的模块reactor 里的模块优先直接用各模块target/classes的输出不在 reactor 里的依赖去本地仓库找本地仓库没有才去远程仓库下载。第二条是关键哪怕是你自己项目的兄弟模块只要没进 reactor一样按外部依赖处理去本地仓库拿 jar。我当时用的构建命令mvn clean package-plbase-archives-web-DskipTests-pl指定只构建 web 模块。这次构建的 reactor 里只有 web 自己common 不在场。web 对 common 的依赖按顺序落到第二步——从本地仓库拿拿到的就是上周那份旧 jar原样打进 fat jar。reactor 里有谁不用猜Maven 每次构建开头都会打印出来。我当时那条命令打出来的列表[INFO] Reactor Build Order: [INFO] [INFO] base-archives [INFO] base-archives-api [INFO] base-archives-web只有两个模块common 不在不参与本次构建。改动的代码在base-archives-common/target/classes里编译得好好的但没有任何一个环节把它送到 web 的打包路径上。两种改法# 方案一改了 common/api先 install 进本地仓库mvn cleaninstall-plbase-archives-common,base-archives-api-DskipTests# 方案二构建 web 时带上 -am把它依赖的模块拉进同一次构建mvn clean package-plbase-archives-web-am-DskipTests两种改法的区别值得说清楚。-am–also-make把 web 依赖的 common、api 一起拉进这次构建common 进了 reactor按解析顺序直接用target/classes新代码进包。但-am的产物只服务这一次构建不会写进本地仓库。下次你再单独构建 web本地仓库那份旧 jar 还在原地等着。install 才能真正更新本地仓库。-am管这一次打包install 管本地仓库的长期状态。-am 管这一次打包install 管本地仓库的长期状态。后来翻项目团队内部的协作约定文档发现根目录的 CLAUDE.md 里一直写着这条命令注释是修改 common/api 后需执行。写下这行字的人踩过同样的坑把做法记在了文档里。我没看或者说看了没当回事。IDEA 为什么是对的整个事情里最让人想不通的是IDEA 里明明跑出了新逻辑。IDEA 对同一工程内的 Maven 模块默认做工作区解析——web 对 common 的依赖直接指向base-archives-common/target/classes跳过本地仓库。所以 IDEA 里 Run加载的是刚编译的新代码。打包走的是 Maven 的解析逻辑两套路径两个结果。这个行为有前提模块得作为 Maven 模块正确导入同一个工程。如果 common 没导进工程或者在设置里开了 Delegate IDE build/run to Maven解析路径又会变。具体到哪些配置组合走哪条路我没法穷举跟 IDEA 版本也有一定的关系。能确认的只有一件事IDEA 里是对的这个结论证明不了打包产物是对的。“IDEA 里是对的”证明不了打包产物是对的。两套解析路径两个结果。怎么确认自己跑的是哪份代码三个办法成本从低到高。1、看本地仓库 jar 的修改时间。早于你的代码改动时间依赖它打出来的包就是旧的。2、想知道某个类实际从哪加载一行代码System.out.println(AreaCodeUtils.class.getProtectionDomain().getCodeSource().getLocation());输出指向本地仓库的 jar还是指向target/classes直接见分晓。排查加载的到底是哪份这类问题这一行比翻半天配置快。3、较真的时候对比 class 哈希。jar 本质是 zip改后缀就能解压Get-FileHashbase-archives-common\target\classes\cn\xiaomayi\achives\common\AreaCodeUtils.classGet-FileHash解压目录\cn\xiaomayi\achives\common\AreaCodeUtils.class哈希不一致就不是同一份代码没什么好争的。这个坑为什么难排查全程不报错。应用正常启动接口正常响应唯独行为是旧版本的。没有异常、没有堆栈日志里找不到任何线索。IDEA 验证通过给了人确定感。“代码没问题” 这个结论一旦建立怀疑方向就被推到部署、环境、缓存上很少人回头怀疑打包时的依赖解析问题。用旧 jar 对着新源码 debug 时断点行号对不上单步走着会跳变量值和源码对不上。这个症状大多数 Java 工程师都见过第一反应通常是 IDEA 出问题了清缓存、重启折腾一圈问题原样保留。自查清单检查项怎么查危险信号改了公共模块行为没有变化看本地仓库对应 jar 的修改时间时间早于你的代码改动IDEA 里正常、jar 包行为是旧逻辑检查构建命令单独-pl构建子模块且没 install、没带-am想确认某个类从哪加载getProtectionDomain().getCodeSource().getLocation()路径指向本地仓库旧 jardebug 行号漂移、源码与运行不匹配IDEA 的提示信息运行的 class 和打开的源文件不是同一份协作文档里的构建命令逐条读尤其是带注释的修改 xx 后需执行这类注释背后都有坑老炮点评“改了代码和改的代码被运行”中间隔着编译、install、打包好几个环节。Maven 把每个环节都做得很顺顺到 install 这一步经常被忘掉——反正 IDEA 里是对的。部署文档里那些看着多余的命令值得逐条把它当回事。因为写下它们的人多半交过学费命令能留在文档里说明坑确实踩过。上一篇番外篇讲了feign-core换IDEA版本启动失败这篇讲多模块install两篇都是 “基础企业档案” 项目里的真实排查。下期预告《同事的 SNAPSHOT 更新了我三天后才拉到》同事的 common 模块 deploy 了新 SNAPSHOT让我本地验证。我这边怎么构建都还是旧代码mvn clean清了一遍又一遍没用。updatePolicy、maven-metadata.xml、-U参数还有一个很少人提的_remote.repositories文件。下期讲 SNAPSHOT 的更新机制以及为什么-U有时候也不管用。如果本文对你有帮助欢迎 点赞 ⭐ 收藏 关注 留言我是老炮Java 老兵仍在一线。关注「Java老炮踩坑录」看真实案例少踩坑。