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日志,定位异常栈,找到依赖链路;断点调试是关键,能帮我们发现隐藏的依赖关系,避免盲目试错。

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

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

相关推荐
恼书:-(空寄1 小时前
事务绑定事件监听器的使用
java
星辰_mya1 小时前
@SpringBootApplication 与 SPI 机制的终极解密
java·spring boot·spring
xdl25991 小时前
【异常解决】Unable to start embedded Tomcat Nacos 启动报错
java·tomcat
是2的10次方啊1 小时前
串行与并行:高并发系统里的优雅接口设计
java
qiuyuyiyang1 小时前
SpringBoot中如何手动开启事务
java·spring boot·spring
sheji34162 小时前
【开题答辩全过程】以 摩托车及配件售后管系统为例,包含答辩的问题和答案
java
aisifang002 小时前
SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
spring boot·后端·maven
我是苏苏2 小时前
消息中间件RabbitMQ04:路由模式+死信队列的应用实践模板
java·开发语言
花无缺0002 小时前
Java开发踩坑:一次线上性能优化案例
java·开发语言·人工智能·面试