位置:首页 > Java > Java运行排错:新代码旧Jar包问题原因与解决方法

Java运行排错:新代码旧Jar包问题原因与解决方法

时间:2026-08-18  |  作者:游戏探长  |  阅读:0

一个知识库列表接口挂了。 前端照常请求:

ja va运行排错,新码旧jar问题及解决

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 testmvn 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 testreactor 编译产出的 target/classes最新
启动服务 mvn -f mindflow-api/pom.xml spring-boot:run.m2repo 里的 SNAPSHOT jar上一次 install 的

启动时,KnowledgeControllermindflow-api 自身,随启动模块一起编译,所以是三参数版本。

KnowledgeCrudUseCasemindflow-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 生命周期里比 compiletest 更靠后的阶段。

它会先编译、测试(这里跳过了),然后把 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,按这个顺序排查,基本不会浪费时间:

  1. 看源码签名是否一致,排除手误。
  2. ja vaptarget/classes,排除编译问题。
  3. 查端口上的进程,排除“请求打到了旧服务”。
  4. 如果是 Ma ven 多模块、项目级本地仓库,ja vap.m2repo 里 SNAPSHOT jar 的签名。
  5. 确认 jar 是旧的后,执行 mvn install 更新,再重启。

核心认知是:NoSuchMethodError 从来不怪源码。

它真正告诉你的是,运行时 classpath 上同名的类,有两个不同的版本在共存。

给这个项目定个习惯

以后只要改了 mindflow-applicationmindflow-domainmindflow-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。

下次,它就该变成常识。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多