阿里面试常问题:Spring 是如何解决循环依赖的?

371 篇文章 12 订阅
243 篇文章 10 订阅

前言

相信很多小伙伴在工作中都会遇到循环依赖,不过大多数它是这样显示的:

还会提示这么一句:

Requested bean is currently in creation: Is there an unresolvable circular reference?

老铁!这就是发生循环依赖了!

当然这里是一个异常情况。

下面就一起看一看,什么是循环依赖,以及 Spring 是如何解决循环依赖的?

什么是循环依赖

Dependency Resolution Process

Spring IoC 容器会在运行时检测到 构造函数注入 循环引用,并抛出 BeanCurrentlyInCreationException。

所以要避免构造函数注入,可以使用 setter 注入替代。

根据官方文档说明,Spring 会自动解决基于 setter 注入的循环依赖。

当然在咱们工作中现在都使用 @Autowired 注解来注入属性。

PS: @Autowired 是通过反射进行赋值。

这里从我们最经常使用的场景切入,看 Spring 是如何解决循环依赖的?

代码

@Service
public class CircularServiceA {

    @Autowired
    private CircularServiceB circularServiceB;
}

@Service
public class CircularServiceB {

    @Autowired
    private CircularServiceC circularServiceC;
}

@Service
public class CircularServiceC {

    @Autowired
    private CircularServiceA circularServiceA;

}

这里有 A、B、C 三个类,可以看到发生了循环依赖:

但是即使发生了循环依赖,我们依然可以启动 OK,使用并没有任何影响。

Spring 是如何解决循环依赖的

在 Spring 单例 Bean 的创建 中介绍介绍了使用三级缓存。

singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。
earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。
singletonFactories: 三级缓存,存储 singletonFactory。

当然,这里看着比较长,可以简化一下:

通过 Debug 来说明生成过程

从 preInstantiateSingletons 方法开始:

添加断点 beanName.equals("circularServiceA")

启动Debug:

会从缓存中获取单例 Bean

这里很显然获取不到,继续执行,创建单例实例

发现是单例再次获取

这里还会从一级缓存获取一次 circularServiceA , 没有获取到,将 circularServiceA 添加到在创建的池子里面 (singletonsCurrentlyInCreation 是一个 set 集合)。

然后会调用工厂方法 createBean(beanName, mbd, args) 创建对象。

在 createBean 中去实例化 Bean 。

判断是否是循环引用,是的话需要添加到三级缓存中。

circularServiceA 不在一级缓存中,则将 circularServiceA 的 singletonFactory 添加到 三级缓存 (singletonFactories) 中,同时从二级缓存中移除。

到这一步为止,circularServiceA 已经在三级缓存中了。

开始对 Bean 的属性进行赋值。

在 populateBean 方法中执行到

PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);

就会对属性进行赋值

在 injet 方法中,回去解决相关依赖。

继续 Debug ,发现解决依赖,最后发现其实又调用回 beanFactory.getBean(beanName);

不过这次创建的是 circularServiceB 。

下面是调用链:

circularServiceB 的过程和 circularServiceA 的一样,也是创建了三级缓存,然后去创建 circularServiceC

这时候三级缓存里面有它们三个的 singletonFactory 。

circularServiceC 也调用到 doGetBean 方法去获取 circularServiceA

不过这次 调用到 Object sharedInstance = getSingleton(beanName); 的时候, circularServiceA 已经存在了。

这次调用虽然没有从一级缓存 (singletonObjects) 中获取到 circularServiceA,但是 circularServiceA 在 创建中 ,所以进入判断

在这里执行完之后, circularServiceA 从三级缓存升级到二级缓存

使用反射对 circularServiceC 中的 circularServiceA 进行赋值, 此时 circularServiceA 是在 二级缓存中。

那就比较好奇了,这时候 circularServiceC 里面的 circularServiceA 已经通过反射赋值,这个赋值给的是什么值?

查看代码:

这块是从三级缓存(singletonFactories)中获取的 singletonObject,然后调用

singletonObject = singletonFactory.getObject();

获取的一个对象

这里获取到的是 circularServiceA 的引用,注意 circularServiceA 这时候还没创建完成,只是引用。所以这里赋值的是 circularServiceA 的引用。

到这里 circularServiceC 就创建完了。

然后会将 C 添加到一级缓存和已注册列表中,同时从二级三级缓存中删除 C。

继续执行 B 和 A 的属性赋值以及后续的初始化流程。

至此,循环依赖解决完毕。

总结

Spring 使用三级缓存来解决循环依赖的问题,三级缓存分别是:

  • singletonObjects:一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

  • earlySingletonObjects:二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

  • singletonFactories:三级缓存,存储 singletonFactory。

本文也通过 Debug 来验证了使用三级缓存解决依赖的过程。

不过还有一些问题没有说明:

  1. 循环依赖和代理之间的关系是什么?比如 @Transactional 和 @Async 注解会对循环依赖产生什么影响?
  2. 为什么要用三级缓存?二级缓存不可以么?

最新2021整理收集的一些高频面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法以及最新的简历模板等学习资料,也有详细的学习规划图,面试题整理等,需要获取这些内容的朋友扫描下方二维码免费获取:暗号:CSDN

å¨è¿éæå¥å¾çæè¿°

看完三件事❤️

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  1. 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

  2. 关注公众号 『 java烂猪皮 』,不定期分享原创知识。

  3. 同时可以期待后续文章ing🚀

  4. .关注后回复【666】扫码即可获取学习资料包

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
源码深度解析是一种深入研究源代码的方法,通过仔细阅读和理解源代码中的细节和逻辑,以获得对代码的深刻理解和洞察。这样的分析可以帮助开发者更好地理解代码的实现方式,从而更好地理解并使用该代码库。 关于spring如何解决循环依赖问题,我们可以从源码的角度来分析。Spring采用了三级缓存来解决循环依赖问题。 第一级缓存是singletonFactories缓存,用于存储正在创建的Bean的工厂对象。当容器正在创建一个Bean时,会将这个Bean的工厂对象存储在singletonFactories缓存中。 第二级缓存是earlySingletonObjects缓存,用于存储已经完成了属性填充但尚未初始化完成的Bean。当容器创建一个Bean时,会将正在创建的Bean存储在earlySingletonObjects缓存中。 第三级缓存是singletonObjects缓存,用于存储已经完成初始化的Bean。当一个Bean初始化完成后,会将其存储在singletonObjects缓存中。 Spring在创建Bean的过程中,会先查找一级缓存,如果找到了对应的工厂对象,则直接返回该对象,避免了创建过程中的循环依赖。如果一级缓存中没有找到对应的工厂对象,则通过递归的方式创建依赖的Bean。 在创建Bean的递归过程中,如果发现正在创建的Bean已经在二级缓存中,说明发生了循环依赖。此时,Spring会从二级缓存中获取正在创建的Bean的代理对象,以解决循环依赖。 当一个Bean创建完成后,会将其放入三级缓存中,并从一级缓存和二级缓存中移除。 总结来说,Spring通过三级缓存的方式解决循环依赖问题,保证了Bean的创建过程中不会陷入无限递归的循环。这种机制的实现使得Spring解决循环依赖问题上具有较好的性能和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值