线上问题:FeignClient循环依赖及源码分析

1 场景复现

依赖关系:虚线左侧为实现的类,虚线右侧为Spring的原生接口类或注解。
正常看,没有循环依赖,但是,实际运行时,异常信息:Is there an unresolvable circular reference?
明确告知,可能存在循环依赖,需要进一步从异常日志排查。
在这里插入图片描述

1.1 FeignClient

通过Feign调用服务。
在这里插入图片描述

1.2 HandlerInterceptor

新建Token拦截器。
在这里插入图片描述

1.3 WebMvcConfigurer

添加Token拦截器。
在这里插入图片描述

1.4 Controller

接口依赖FeignClient。
在这里插入图片描述

2 方案

在TokenInterceptor的FeignClient添加@Lazy,启动SpringBoot时不加载TokenInterceptor的FeignClient,当服务启动完成后,有需要时再加载,这样就不用再启动时重复验证单例FeignClient,保证服务正常启动。
在这里插入图片描述

3 分析

SpringBoot启动时堆栈信息如下:从内往外抛出异常,
异常信息较多,分段看。

  • 第一段
    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘rpcApi’: Unsatisfied dependency expressed through field ‘feignTemplateService’; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration’: Unsatisfied dependency expressed through method ‘setConfigurers’ parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘webMvcHandler’: Unsatisfied dependency expressed through field ‘tokenInterceptor’; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘tokenInterceptor’: Unsatisfied dependency expressed through field ‘templateService’; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘com.monkey.tutorial.common.rpc.IFeignTemplateService’: Requested bean is currently in creation: Is there an unresolvable circular reference?

  • 第二段
    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration’: Unsatisfied dependency expressed through method ‘setConfigurers’ parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘webMvcHandler’: Unsatisfied dependency expressed through field ‘tokenInterceptor’; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘tokenInterceptor’: Unsatisfied dependency expressed through field ‘templateService’; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘com.monkey.tutorial.common.rpc.IFeignTemplateService’: Requested bean is currently in creation: Is there an unresolvable circular reference?

  • 第三段
    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘webMvcHandler’: Unsatisfied dependency expressed through field ‘tokenInterceptor’; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘tokenInterceptor’: Unsatisfied dependency expressed through field ‘templateService’; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘com.monkey.tutorial.common.rpc.IFeignTemplateService’: Requested bean is currently in creation: Is there an unresolvable circular reference?

  • 第四段
    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘tokenInterceptor’: Unsatisfied dependency expressed through field ‘templateService’; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘com.monkey.tutorial.common.rpc.IFeignTemplateService’: Requested bean is currently in creation: Is there an unresolvable circular reference?

  • 第五段
    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘com.monkey.tutorial.common.rpc.IFeignTemplateService’: Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355) ~[spring-beans-5.3.6.jar:5.3.6]

3.1 定位异常

第五段异常信息可以定位到抛出异常的位置:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation,
这个方法是新建单例对象前的校验,源码如下图所示:
在这里插入图片描述
由源码可知,新建单例对象前都会通过beforeSingletonCreation校验需要创建的单例对象是否在当前集合中,根据源码的判定逻辑,如果需要创建的单例对象名称(beanName)在singletonsCurrentlyInCreation中,则抛出异常。

3.2 调试方案

通过异常定位以及对抛出异常的逻辑分析,
可在beforeSingletonCreation处打断点调试,下面是几个核心位置的调试信息:rpcApi、WebMvcHandler、TokenInterceptor和IFeignTemplateService。

3.2.1 rpcApi

rpcApi校验创建单例对象调试信息如下图所示,
图中右侧,this.singletonsCurrentlyInCreation集合为空,因此,可以通过校验。
在这里插入图片描述

3.2.2 IFeignTemplateService

进入IFeignTemplateService校验,由于rpcApi依赖IFeignTemplateService,
因此,rpcApi校验结束后,会进入IFeignTemplateService校验,
调试结果如下图所示,由图可知,IFeignTemplateService的this.singletonsCurrentlyInCreation已经存在一个元素rpcApi,
即rpcApi依赖IFeignTemplateService,而IFeignTemplateService不在集合singletonsCurrentlyInCreation中,因此校验通过。

在这里插入图片描述

3.2.3 mvcResourceProvider

接下来校验的脚步会被mvcResourceUrlProvider截胡,
先进行mvcResourceUrlProvider校验,
调试结果如下图所示,由图可知,this.singletonsCurrentlyInCreation中已存在rpcApi和IFeignTemplateService,
说明rpcApi和IFeignTemplateService是依赖mvcResourceUrlProvder的,此时,校验通过,
这里的核心是:IFeignTemplateService已经添加到this.singletonsCurrentlyInCreation,是后续抛出异常的引子
在这里插入图片描述

3.2.4 webMvcHandler

mvcResourceUrlProvider依赖webMvcHandler,
mvcResourceUrlProvider校验通过后,会进入webMvcHandler校验,
调试结果如下图所示,由图可知,webMvcHandler校验通过,无异常。
在这里插入图片描述

3.2.5 tokenInterceptor

webMvcHandler依赖tokenInterceptor,校验webMvcHandler后,会进入tokenInterceptori校验,
tokenInterceptor校验过程如下图所示,校验通过。
在这里插入图片描述

3.2.6 IFeignTemplateService

由于tokenInterceptor也依赖IFeignTemplateService,
所以,tokenInterceptor校验通过后,会接着校验IFeignTemplateService,
调试过程如下图所示,综合上面的分析,this.singletonsCurrentlyInCreation中已经存在IFeignTemplateService,
(由于IFeignTemplateService依赖mvcResourceUrlProvider;mvcResourceUrlProvider依赖webMvcHandler)
校验失败,抛出异常。
在这里插入图片描述
抛出异常
在这里插入图片描述

3.3 循环依赖

经过分析,形成的循环依赖如下图所示。

在这里插入图片描述

3.4 解决

为了保证SpringBoot服务,需要打破循环依赖,
所以,在TokenInterceptor中,在服务启动时不加载FeignClieit,
在FeignClient上面添加@Lazy注解,延迟加载,服务启动后,有调用时再加载。
在这里插入图片描述

4 小结

(1)循环依赖有显式依赖:如A->B->C->A,以及隐式依赖,需要逐步调试;
(2)循环依赖的解决方案:断开环,可调整代码设计、延迟加载等方式。

猜你喜欢

转载自blog.csdn.net/Xin_101/article/details/130034641