Java运行排错:新代码旧Jar包问题原因与解决方法
时间:2026-08-18 | 作者:游戏探长 | 阅读:0一个知识库列表接口挂了。 前端照常请求:
GET /api/v1/knowledgepage=0&size=10
后端一把甩回来:
ja va.lang.NoSuchMethodError: 'com.mindflow.application.knowledge.KnowledgePage com.mindflow.application.knowledge.KnowledgeCrudUseCase.list(int, int, ja va.lang.String)'
NoSuchMethodError 这个名字很有误导性。
它不是编译器报的“找不到方法”。源码完全能编译通过。
它是 JVM 在运行时发现,某个 class 文件的二进制签名和调用方期望的不一致,于是抛出的一个 Error 子类。
这不是业务异常,而是 JVM 层面的问题。
这通常意味着:当前 classpath 上加载到的类,和编译时用的类,不是同一个版本。
刚才改了什么
这次改动很简单:给知识库列表加了一个关键词搜索参数。
原来分页接口是两参数:
KnowledgePage list(int page, int size);
加完变成三个:
KnowledgePage list(int page, int size, String query);
Controller 也同步改成了三参数调用。
逻辑不复杂,本地测试也通过了:
mvn --settings .mvn/settings.xml -pl mindflow-api -am test
这条命令的意思是:
-pl mindflow-api:只在mindflow-api这个模块跑测试。-am(also-make):让 Ma ven 先把它依赖的所有兄弟模块也编译一遍。
所以测试时,mindflow-application 等依赖模块,都是刚从源码编译出来的最新 class。
但启动的时候,走的不是这条路。
从最浅的地方开始排查
源码对不对?
先确认源码里的接口签名和调用方,确实都是三参数。
打开:
mindflow-application/.../KnowledgeCrudUseCase.ja va mindflow-application/.../KnowledgeApplicationService.ja va mindflow-api/.../KnowledgeController.ja va
三个文件都一致。
说明问题不在编辑器里。
编译产物对不对?
源码是对的,但也可能是 IDE 没保存,或者增量编译出了问题。
这时可以用 ja vap 直接看 target/classes 目录下的 class 文件。
ja vap 是 JDK 自带的 class 文件反编译工具。
它能直接读出 class 里的方法签名,不需要源码。
用法很简单:
ja vap -classpath<全限定类名>
跑一下:
ja vap -classpath mindflowbackendmindflow-applicationtargetclasses ` com.mindflow.application.knowledge.KnowledgeCrudUseCase
输出里确实有:
public abstract KnowledgePage list(int, int, ja va.lang.String);
这说明编译产物也是新的。
源码和编译结果这两个最容易想到的方向,都排除了。
端口上跑的是谁?
代码和编译都没问题,那就看看运行中的进程。
也许上午调试留下的 Ja va 进程还在,占着端口,请求根本没打到新启动的服务上。
Get-Process ja va netstat -ano | Select-String '8080|18080'
第一行列出所有 Ja va 进程。
第二行查 8080 和 18080 端口被哪些进程占用。
果然有旧进程。停掉:
Stop-Process -Id-Force
这一步解决的是调试时很容易忽略的噪音源。
你以为自己在看新版本,实际上浏览器连的还是残留进程。
本地开发的“幻觉”,很多都来自这里。
但进程清掉、重启之后,错误依旧。
那就不是残留进程的问题,得继续往下挖。
真正致命的裂缝:两套 classpath
这个项目的后端结构是这样的:
mindflow-domain mindflow-application mindflow-infrastructure mindflow-ai mindflow-api ← 启动入口在这里
mindflow-api 是 Spring Boot 入口,依赖上面四个模块。
启动命令长这样:
cd mindflowbackend mvn --settings .mvn/settings.xml -f mindflow-api/pom.xml spring-boot:run
-f mindflow-api/pom.xml 的意思是,只把这个子模块当“单模块项目”来启动,不去碰父 pom。
当时这么设计,是为了避开父 pom 找不到 main class 的配置问题。
但 spring-boot:run 作为一个 Ma ven goal,解析依赖的方式和 mvn test 不一样。
它不会自动编译兄弟模块。
它只看 mindflow-api 的 pom 里声明依赖的 GA V(groupId、artifactId、version)。
然后去本地 Ma ven 仓库找对应的 jar。
而这个项目的 Ma ven settings 里,配置了一个项目级的本地仓库:
${user.dir}/.m2repo
${user.dir} 是执行 Ma ven 命令时的当前目录,也就是 backend/。
于是所有通过 Ma ven 坐标解析出来的 jar 都来自:
mindflow/backend/.m2repo/
mindflow-api 启动时,会去:
mindflow/backend/.m2repo/com/mindflow/mindflow-application/0.1.0-SNAPSHOT/mindflow-application-0.1.0-SNAPSHOT.jar
取 mindflow-application。
问题来了:这个 jar 是哪来的?
它来自上一次 mvn install 写进去的结果。
如果只跑了 mvn test 或 mvn compile,编译产物只会到各模块的 target/classes。
.m2repo 里的 SNAPSHOT jar 并不会更新。
继续用 ja vap 确认:
ja vap -classpath mindflowbackend.m2repocommindflowmindflow-application0.1.0-SNAPSHOTmindflow-application-0.1.0-SNAPSHOT.jar ` com.mindflow.application.knowledge.KnowledgeCrudUseCase
输出:
public abstract KnowledgePage list(int, int);
只有两参数。
而 target/classes 和源码里,已经是三参数了。
拼图完整了
现在两条加载路径都清楚了:
| 场景 | mindflow-application 从哪来 | 版本 |
|---|---|---|
| 跑测试 mvn -pl mindflow-api -am test | reactor 编译产出的 target/classes | 最新 |
| 启动服务 mvn -f mindflow-api/pom.xml spring-boot:run | .m2repo 里的 SNAPSHOT jar | 上一次 install 的 |
启动时,KnowledgeController 在 mindflow-api 自身,随启动模块一起编译,所以是三参数版本。
但 KnowledgeCrudUseCase 在 mindflow-application,从 .m2repo 加载,却还是两参数旧版本。
JVM 的类加载没有“重新检查”这一步。
Controller 调用 list(page, size, query) 时,字节码里已经写死了方法签名。
到了实际调用点,JVM 去 KnowledgeCrudUseCase 的 class 里找这个签名。
找不到,就抛出 NoSuchMethodError。
本质上是构建路径和启动路径共用了一个本地仓库,但两个路径对“最新版本”的定义不一样。
修掉它
先清掉还在跑旧版本的进程:
Stop-Process -Id-Force
然后从 backend 根目录跑一次 install。
把当前源码编译打包,并写入 .m2repo:
cd mindflowbackend mvn --settings .mvn/settings.xml install -DskipTests '-Dspring-boot.repackage.skip=true'
install 是 Ma ven 生命周期里比 compile 和 test 更靠后的阶段。
它会先编译、测试(这里跳过了),然后把 jar 复制到本地仓库。
-Dspring-boot.repackage.skip=true 会跳过 Spring Boot 的 repackage。
因为这里只需要普通 jar 让依赖解析通过,不需要可执行包。
再用 ja vap 验证 .m2repo 里的 jar:
public abstract KnowledgePage list(int, int, ja va.lang.String);
这次已经是三参数了。
重新启动:
mvn --settings .mvn/settings.xml -f mindflow-api/pom.xml spring-boot:run
再验证:
Invoke-WebRequest -Uri 'http://localhost:8080/api/v1/knowledgepage=0&size=1&q=Smoke' -UseBasicParsing
返回 200,问题消了。
一条快速排查线
以后遇到 NoSuchMethodError,按这个顺序排查,基本不会浪费时间:
- 看源码签名是否一致,排除手误。
ja vap看target/classes,排除编译问题。- 查端口上的进程,排除“请求打到了旧服务”。
- 如果是 Ma ven 多模块、项目级本地仓库,
ja vap看.m2repo里 SNAPSHOT jar 的签名。 - 确认 jar 是旧的后,执行
mvn install更新,再重启。
核心认知是:NoSuchMethodError 从来不怪源码。
它真正告诉你的是,运行时 classpath 上同名的类,有两个不同的版本在共存。
给这个项目定个习惯
以后只要改了 mindflow-application、mindflow-domain、mindflow-infrastructure 这类被 mindflow-api 依赖的模块,启动前先跑:
cd mindflowbackend mvn --settings .mvn/settings.xml install -DskipTests '-Dspring-boot.repackage.skip=true'
如果只是跑测试,仍然可以:
mvn --settings .mvn/settings.xml -pl mindflow-api -am test
这次两套路径的差异是 bug。
下次,它就该变成常识。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- Java异常处理实战:从体系架构到健壮性设计全解
- 时间:2026-08-27
-
- 基于Spring Security实现自动登录的完整指南
- 时间:2026-08-27
-
- VS Code Java开发高效配置清单(可直接复制使用)
- 时间:2026-08-27
-
- JavaSwing实现自定义TablePanel组件(附完整代码)
- 时间:2026-08-27
-
- Java基础攻略:输入输出、方法与数组实战指南
- 时间:2026-08-27
-
- Java反射、枚举、Lambda表达式及泛型通配符实例详解
- 时间:2026-08-27
-
- 30个经典算法题及Java解答(含实例代码)
- 时间:2026-08-27
-
- Spring Data JPA 查询实战:方法命名与 @Query 注解详解
- 时间:2026-08-27
精选合集
更多大家都在玩
大家都在看
更多-
- 糖尿病完全不能吃糖吗
- 时间:2026-09-15
-
- 蚂蚁庄园小课堂2026年9月16日最新题目答案
- 时间:2026-09-15
-
- 小鸡答题今天的答案是什么2026年9月16日
- 时间:2026-09-15
-
- 蚂蚁庄园每日答题答案2026年9月16日
- 时间:2026-09-15
-
- 以下哪种粮食是酿造绍兴黄酒的主要原料 蚂蚁庄园今日答案9月16日
- 时间:2026-09-15
-
- 劝学名句“及时当勉励,岁月不待人”出自哪位诗人 蚂蚁庄园今日答案9.16
- 时间:2026-09-15
-
- 蚂蚁庄园今天答题答案2026年9月16日
- 时间:2026-09-15
-
- 蚂蚁庄园答题今日答案2026年9月16日
- 时间:2026-09-15
