老炮踩坑录 · F12 · 翻车现场系列· 番外篇
基于「基础企业档案」真实项目代码,复盘一次换 IDEA 版本引发的离奇启动失败
关键词:NoClassDefFoundError · provided scope · dependencyManagement · effective-pom
👋 欢迎阅读
🏠个人主页:知守观
📘我的专栏:老炮踩坑录
💻当前内容:Maven范围依赖
文章目录
- 前言
- 一开始判断错了方向
- 本地仓库路径先搞错了
- dependency:tree 定位到 scope
- 很多人没踩过的点:BOM 连 scope 一起管
- 为什么偏偏和 IDEA 版本有关
- 修复
- 第二个报错:feign-slf4j
- 为什么容易被误导
- 自查清单
- 老炮点评
前言
今天这篇是番外篇,翻出的是另一个项目——基础企业档案,一个Spring Cloud微服务项目。
离职后整理旧硬盘,翻出以前做的另一个 项目,它是一个 Spring Cloud 微服务项目。想跑起来看一眼当年写的代码,顺手用机器上装着的两个 IDEA 版本各打开了一次。
诡异的事出现了。同一个仓库、同一份代码、同一个 JDK,一个版本启动正常,另一个版本直接报错:
java.lang.NoClassDefFoundError: feign/Request$Options at org.springframework.cloud.openfeign.FeignClientFactoryBean.<init>(FeignClientFactoryBean.java:106) ... Caused by: java.lang.ClassNotFoundException: feign.Request$Options报错出现在 Spring 注册@FeignClient的阶段。FeignClientFactoryBean初始化时要 new 一个Request.Options,类却没有找到。
代码一个字没动,换个 IDE 就起不来。排查这种问题没有明确线索,一时不知道从哪里下手。
一开始判断错了方向
看到堆栈里的spring-cloud-openfeign-core-3.1.1,我第一反应是版本对不上。项目里手写的 openfeign 版本是2.2.3.RELEASE,运行时加载的却是 3.1.1,版本显然被上层依赖管理覆盖了。具体是父 POM 里哪一层 BOM 生效,当时没往上翻,先没有继续追查。
缺类就补类。我在子模块的 pom 里直接把 feign-core 加上:
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-core</artifactId></dependency>Reload,重启,报错一个字没变。
依赖写了,运行时却没有生效。到这里我意识到,问题不在"有没有声明依赖"。
本地仓库路径先搞错了
我习惯性去默认仓库找 openfeign 的目录:
dir"$env:USERPROFILE\.m2\repository\io\github\openfeign"整个路径不存在。
可堆栈里明明有spring-cloud-openfeign-core-3.1.1.jar,JAR 不可能凭空出现。直到我跑了一次 mvn,日志里一行信息说明了原因——本地仓库路径早就被 settings.xml 改到了D:\Maven\repertory,~/.m2下并没有所需的依赖。
我一直在错误的仓库路径里查找。
dependency:tree 定位到 scope
换到正确的仓库路径,feign-core 的 JAR 确实存在。JAR 在、声明在、运行时就是没有,剩下的可能性只有 scope。
mvn dependency:tree"-Dincludes=io.github.openfeign:feign-core"PowerShell 里这个参数不加引号会被解析坏,顺手提一下。输出:
[INFO] cn.linkkids:base-guidang-web:jar:0.0.1-SNAPSHOT [INFO] \- io.github.openfeign:feign-core:jar:11.8:provided末尾两个字:provided。
很多人没踩过的点:BOM 连 scope 一起管
provided 的效果不用多讲,编译期在、运行期不在。问题是我从来没写过 provided,它从哪来的?
答案在 dependencyManagement 里。不少人对它的印象停留在"统一管版本号",实际上 version、scope、exclusions 都能在这里一起指定:
<dependencyManagement><dependencies><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-core</artifactId><version>11.8</version><scope>provided</scope></dependency></dependencies></dependencyManagement>上面是示意,父 POM 的原文我离职后已经拉不到了。
子模块声明依赖时不写 scope,就继承管理条目里的 provided;自己显式写 scope,才能覆盖继承值。我后来查 effective-pom 验证,feign-core 的最终 scope 确实是 provided。
为什么这么设计,我只能推测:公司内部部署体系里,feign 相关 JAR 可能由统一的基础环境提供,业务包刻意不带。翻提交记录或许有答案,旧仓库权限已经没了,这部分无法确认。
为什么偏偏和 IDEA 版本有关
这一步的判断我没有直接证据,只能根据 IDEA 的配置项推断。
IDEA 的运行配置里有个开关:Add dependencies with "provided" scope to classpath。两个 IDEA 版本下,这个开关的默认状态、或者老配置迁移后的状态很可能不一样——一边把 provided 依赖带进了运行时 classpath,一边没有。旧环境已经不在我机器上,没法回头点开那个配置截图验证,所以这段只能给到"最可能的解释"。
能确认的事实只有一条:feign-core 被解析成 provided,这是 Maven 的解析结果,不随 IDE 变。
修复
在子模块显式声明 compile,覆盖继承的 provided:
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-core</artifactId><scope>compile</scope></dependency>再次执行 dependency:tree,确认 scope 已变更:
修改前: io.github.openfeign:feign-core:jar:11.8:provided 修改后: io.github.openfeign:feign-core:jar:11.8:compileReload Maven,这次FeignClientFactoryBean初始化没有再报错。
第二个报错:feign-slf4j
重启后应用继续启动,又报:
java.lang.NoClassDefFoundError: feign/slf4j/Slf4jLoggerfeign-slf4j,一模一样的 provided。它和 feign-core 同属一个 dependencyManagement 条目,一批依赖都是这个 scope。
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-slf4j</artifactId><scope>compile</scope></dependency>修改完 feign-slf4j,我全量跑了一次 dependency:tree,把所有 provided 依赖过一遍,确认没有遗漏再重启。这次起来了。
为什么容易被误导
编译全程不报错,provided 本来就参与编译。IDEA 里跳转、补全、代码检查一切正常,只有跑起来的那一刻类没了。
更迷惑人的是它和环境绑定:本地 IDE 里启动失败,生产可能运行正常,两边对"谁来提供这些 JAR"的假设完全不同。反复检查业务代码也查不到原因,问题不在业务代码这一层。
排查这类问题,我现在固定用两个命令:
mvn dependency:tree看实际解析结果,-Dincludes=groupId:artifactId过滤,多模块工程里很有用;mvn help:effective-pom看最终合并生效的 POM,父 POM、import 进来的 BOM 叠了多少层一目了然。怀疑配置被继承关系修改时,先看它。
自查清单
| 检查项 | 怎么查 | 危险信号 |
|---|---|---|
| 能编译、运行时 NoClassDefFoundError | dependency:tree 看该 JAR 的 scope | provided 或 scope 与预期不符 |
| 怀疑版本/scope 被父 POM 改了 | help:effective-pom 里搜 artifactId | 生效值和你手写的不一致 |
| 本地仓库与预期不一致 | mvn -v确认 local repo | IDEA 和命令行指向不同仓库 |
| 同一框架多个依赖接连报错 | 全量 dependency:tree 扫 provided | 同组依赖成批出现,修一个不够 |
老炮点评
你写的 pom.xml,从来不是你一个人的 pom.xml。父 POM 往上还有父 POM,BOM 里还能 import BOM,Maven 真正执行的是所有层合并后的那一份。你在自己这层看到的配置,和 effective-pom 里的实际内容可能完全不同。
对隐式继承的配置保持警惕,比背熟多少条 scope 规则都管用。
下期预告:《多模块工程没 install,跑起来的代码是上周的》
改了 common 包的代码,web 模块跑起来行为没有任何变化,排查很久才想起来自己根本没 install。Maven reactor、SNAPSHOT 缓存、IDEA 的工作区解析,三套规则混在一起,又踩了一次坑。
下期聊聊怎么确认自己运行的到底是哪个版本的代码。
如果本文对你有帮助,欢迎:
👍 点赞 | ⭐ 收藏 | 👤 关注 | 💬 留言
我是老炮,Java 老兵,仍在一线。关注「Java老炮踩坑录」,看真实案例,少踩坑。