feign-core 就在依赖树里,运行时却找不到类:一次 provided scope 引发的启动失败排查

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

基于「基础企业档案」真实项目代码,复盘一次换 IDEA 版本引发的离奇启动失败

关键词:NoClassDefFoundError · provided scope · dependencyManagement · effective-pom

👋 欢迎阅读

🏠个人主页: 知守观

📘我的专栏: 老炮踩坑录

💻当前内容:Maven依赖范围

前言

今天这篇是番外篇,翻出的是另一个项目------基础企业档案,一个Spring Cloud微服务项目。

离职后整理旧硬盘,翻出以前做的另一个 项目,它是一个 Spring Cloud 微服务项目。想跑起来看一眼当年写的代码,顺手用机器上装着的两个 IDEA 版本各打开了一次。

诡异的事出现了。同一个仓库、同一份代码、同一个 JDK,一个版本启动正常,另一个版本直接报错:

text 复制代码
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 加上:

xml 复制代码
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
</dependency>

Reload,重启,报错一个字没变。

依赖写了,运行时却没有生效。到这里我意识到,问题不在"有没有声明依赖"。

本地仓库路径先搞错了

我习惯性去默认仓库找 openfeign 的目录:

powershell 复制代码
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。

powershell 复制代码
mvn dependency:tree "-Dincludes=io.github.openfeign:feign-core"

PowerShell 里这个参数不加引号会被解析坏,顺手提一下。输出:

text 复制代码
[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 都能在这里一起指定:

xml 复制代码
<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:

xml 复制代码
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <scope>compile</scope>
</dependency>

再次执行 dependency:tree,确认 scope 已变更:

text 复制代码
修改前: io.github.openfeign:feign-core:jar:11.8:provided
修改后: io.github.openfeign:feign-core:jar:11.8:compile

Reload Maven,这次 FeignClientFactoryBean 初始化没有再报错。

第二个报错:feign-slf4j

重启后应用继续启动,又报:

text 复制代码
java.lang.NoClassDefFoundError: feign/slf4j/Slf4jLogger

feign-slf4j,一模一样的 provided。它和 feign-core 同属一个 dependencyManagement 条目,一批依赖都是这个 scope。

xml 复制代码
<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老炮踩坑录」,看真实案例,少踩坑。

相关推荐
她的男孩1 小时前
企业接口照样拦得住:独立 Flyway、@RequiresFeature 与离线许可证
java·spring boot·后端
十年Java程序媛1 小时前
Lambda 与函数式接口|别只会复制 ()->{},底层规则和坑一次性讲清
java·spring boot·后端
Nozokime1 小时前
ThreadLocal 在流式 Agent 里静默失效:LangChain4j 轨迹埋点的零侵入方案
java
小宋10211 小时前
OpenTelemetry GenAI可观测性实战:串起模型、工具、Token与错误
java·人工智能·算法·贪心算法
m0_587383001 小时前
工业场景设备维修维护实战技巧 全流程标准化落地与常见问题排查指南
java·spring·小程序·架构·需求分析
郑州光合科技余经理2 小时前
本地生活平台搭建:跨业态用户标识怎么贯通
java·开发语言·前端·后端·uni-app·php·ai编程
木头科技2 小时前
【AI 工程化第八篇】Spring AI 多模型路由实战:DeepSeek、通义、Kimi、OpenAI 如何统一接入和自动切换
java·人工智能·spring
深盾科技_Virbox2 小时前
云端与本地并行交付,软件许可管理如何实现统一运营?
java·大数据·开发语言·安全
thefool1122662 小时前
多线程 3
java