Spring Boot 启动报错:OpenFeign 隐性循环依赖,排查了整整一下午

做开发的小伙伴应该都懂,最崩溃的不是写不出代码,而是项目启动就报错,还报得不明不白!今天就跟大家说说我近期踩的一个大坑------OpenFeign引发的循环依赖问题,全程真实排查过程,新手也能看懂,老手也能避坑,赶紧码住~

一、项目启动直接"罢工",循环依赖找上门

事情是这样的:我在项目中集成了OpenFeign,用于服务间的接口调用,代码写完后启动项目,结果直接报错炸屏------循环依赖异常(Circular Dependency)

当时我整个人都懵了:用过OpenFeign的都知道,Feign Client本质就是一个接口,只做接口定义,不涉及具体实现,怎么会跟WebMvc扯上关系?更别提形成循环依赖了,这操作我是真没见过!

下面是演示代码(涉及隐私,未用真实代码)

Java 复制代码
//feign client 接口类
@FeignClient(path = "test", name = "test")
public interface FeignClientTest  {}

//webmvc配置类
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    
    //依赖了Interceptor
    public MvcConfig(@Qualifier("interceptor") HandlerInterceptor interceptor) {
        super();
        this.interceptor= interceptor;
    }
}

//拦截器类
@Component
public class Interceptor implements AsyncHandlerInterceptor {
    //依赖了FeignClientTest类  
    public Interceptor (FeignClientTest  feignClientTest) {
        this.feignClientTest= feignClientTest;
    }
}

相信多数开发者遇到此类"不合逻辑"的报错时,都会陷入困惑,下文将逐步拆解排查流程,精准定位并解决问题根源。

二、刨根问底,一步步定位问题核心

报错不可怕,可怕的是找不到报错原因。为了搞清楚到底是哪里出了问题,我开启了"debug模式",一步步拆解排查~

步骤1:开启Debug日志,捕捉关键报错信息

默认的报错日志太简略,根本看不出具体是哪个bean引发的循环依赖。于是我先去修改了logback的配置文件,手动打开Spring的Debug日志,让日志更详细:

xml 复制代码
<logger name="org.springframework" level="debug" />

重启项目后,控制台输出了大量详细日志,这一步很关键------详细日志是定位循环依赖的核心前提

步骤2:断点调试,锁定异常根源

有了详细日志,很快找到了具体的异常位置。

紧接着,我在报错的地方打上断点,启动调试模式,查看异常栈信息。

通过分析异常栈,我终于发现了关键问题------原来OpenFeign会为每个服务创建一个独立的Spring Context,并且会把我们应用本身的Context作为父Context,然后执行Context的刷新流程。

重点来了:在Context刷新时,会执行ContextRefreshedEvent的事件监听程序,而且不仅会处理当前Context的监听器,还会处理父Context的监听器

我们的父Context(也就是应用本身的Context)里,有一个监听器叫mvcResourceUrlProvider,这个监听器是WebMvcAutoConfiguration$EnableWebMvcConfiguration类(在其父类WebMvcConfigurationSupport 中)通过@Bean方法定义的。如下

Java 复制代码
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    @Bean
    public ResourceUrlProvider mvcResourceUrlProvider() {
        ResourceUrlProvider urlProvider = new ResourceUrlProvider();
        urlProvider.setUrlPathHelper(getPathMatchConfigurer().getUrlPathHelperOrDefault());
        urlProvider.setPathMatcher(getPathMatchConfigurer().getPathMatcherOrDefault());
        return urlProvider;
    }
}

要调用这个监听器,就必须获取对应的WebMvcAutoConfiguration$EnableWebMvcConfiguration这个Bean,而这个Bean又依赖于WebMvcConfigurer,源码如下

java 复制代码
//DelegatingWebMvcConfiguration为EnableWebMvcConfiguration的父类
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

    //依赖WebMvcConfigurer
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
}

刚好我们应用中的WebMvc配置类继承了这个接口,这样一来,就形成了**"OpenFeign Context → 父Context监听器 → mvcResourceUrlProvider → WebMvcAutoConfiguration$EnableWebMvcConfiguration → WebMvcConfigurer → 应用Bean → OpenFeign Client"**的循环依赖,完美闭环,不报错才怪!

看到这里,你们是不是瞬间恍然大悟?评论区说说你们有没有想到这个隐藏的依赖关系🤔

三、试错2次,终于搞定循环依赖

找到问题根源后,就开始针对性解决。我先后试了2种方法,前一种失败了,后一种成功了,全程真实试错过程,你们可以直接参考,避免走弯路!

尝试1:配置OpenFeign懒加载,失败!

既然是OpenFeign创建的Context引发的依赖问题,那我首先想到的就是"懒加载"------让OpenFeign Client延迟实例化,是不是就能避开循环依赖?

于是我添加了OpenFeign懒加载的配置,通过以下配置实现:

yaml 复制代码
spring.cloud.openfeign.lazy-attributes-resolution=true

满怀期待重启项目,结果......还是报循环依赖!只不过这一次,报错信息里没有显示Feign Client的相关信息

此时我又懵了:难道懒加载怎么没起作用?有没有小伙伴遇到过这种"配置了懒加载,却还是报错"的情况?

尝试2:注入时添加@Lazy注解,成功!

既然配置懒加载没用,我就继续断点调试,发现了一个关键知识点(敲黑板,重点记!):

如果一个懒加载的Bean,被非懒加载的Bean依赖,那么这个懒加载的Bean仍然会被提前实例化,相当于懒加载配置白写了!

而我们的Feign Client虽然配置了全局懒加载,但它被其他非懒加载的Bean(Interceptor 类)依赖了,所以还是会提前实例化,进而引发循环依赖。

找到问题所在后,我立刻修改代码------在Feign Client注入的地方,手动添加@Lazy注解,让Feign Client真正实现懒加载,避免提前实例化。

Java 复制代码
//拦截器类
@Component
public class Interceptor implements AsyncHandlerInterceptor {
    //@Lazy懒依赖FeignClientTest类  
    public Interceptor (@Lazy FeignClientTest  feignClientTest) {
        this.feignClientTest= feignClientTest;
    }
}

修改完成后,再次重启项目......竟然成功启动了!没有报错,接口调用也正常😭

四、总结经验,避免再踩坑

这次踩坑,总结3个核心经验,帮大家避开类似坑:

  • OpenFeign的底层机制:OpenFeign会为每个服务创建独立的Spring Context,且会关联父Context(应用Context),父Context的监听器、Bean都会被关联,容易引发隐藏依赖。

  • 懒加载的正确用法:仅配置全局懒加载不够,如果懒加载Bean被非懒加载Bean依赖,仍会提前实例化,需在注入时添加@Lazy注解,确保真正懒加载。

  • 排查循环依赖的技巧:遇到循环依赖,先开启Spring Debug日志,定位异常栈,找到依赖链路;断点调试是关键,能帮我们发现隐藏的依赖关系,避免盲目试错。

最后:你们平时开发中,遇到循环依赖都是怎么解决的?有没有比我更高效的方法?评论区分享一下,互相学习~

觉得有用的话,点赞,转发,推荐,支持一下吧!

相关推荐
咩咩啃树皮9 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
鱟鲥鳚9 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
大模型码小白11 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司11 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地12 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun31415913 小时前
TCP超时重传机制是为了解决什么问题?
java
莫逸风15 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
隔窗听雨眠15 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
z1234567898616 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin52112316 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python