做开发的小伙伴应该都懂,最崩溃的不是写不出代码,而是项目启动就报错,还报得不明不白!今天就跟大家说说我近期踩的一个大坑------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日志,定位异常栈,找到依赖链路;断点调试是关键,能帮我们发现隐藏的依赖关系,避免盲目试错。
最后:你们平时开发中,遇到循环依赖都是怎么解决的?有没有比我更高效的方法?评论区分享一下,互相学习~
觉得有用的话,点赞,转发,推荐,支持一下吧!