某大厂面试:Spring为什么用三级缓存解决循环依赖?

Spring 在 Bean 创建过程中是如何解决循环依赖的?

其实循环依赖笔者在Spring源码的章节就进行了详细的介绍,这里单独提出来是为了方便觉得源码章节过于复杂又没有时间看的小伙伴们如果想要了解循环依赖的设计,毕竟循环依赖也是面试的常见题型。

面试官:你了解循环依赖吗?

我:循环依赖就是在Spring容器中,两个对象相互依赖造成死循环的问题,举个例子,假如我们有两个对象

一个是User1,一个是User2,User1类里面有一个属性来自于User2,User2类有一个属性来自于User1,这就是A B A的问题,我们在用一张图介绍一下循环依赖

面试官:那你知道Spring是如何解决的吗?

按照我们上面 写的User1,User2类作为实例,然后我们进行debug

这里先对User1属性注入,详细的代码过程请自行到Spring实例化章节,我们这里就简单过一遍循环依赖的关键代码,将代码停在这里

看方法名能够大概猜到这里就是属性注入的关键,我们断点

然后我们直接跳到AutowiredAnnotationBeanPostProcessor,进入这个方法PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);首先获取我们的InjectionMetadata,注入需要的元数据

然后->metadata.inject(bean, beanName, pvs);->element.inject(target, beanName, pvs);
然后我们先看最后一行代码
field.set(bean, value);懂反射的小伙伴们都知道,我们可以通过Field类对一个类的对象属性赋值,所以Spring默认也采用的这种方式,那么我们现在有了类,缺少value,而这个value在元数据中已经显示User2类,所以我们需要拿到User2的对象才行,回到IDEA上面的代码,其中有一行

然后我们跟踪下resolveDependency->doResolveDependency->descriptor.resolveCandidate

最终又回到了getBean,也就是说Spring会从工厂里拿,而这个时候我们的工厂并没有创建它,所以User2创建对象后依旧会进行进入属性注入的方法

这个时候我们跳到了User2的属性注入方法,然后一样User2里面有User1这个类,所以又会去获取User1的对象,但是我们的User1并没有创建完成,属于创建中的状态,那么Spring是如何解决的呢?我们继续跟踪代码

这里的代码是我们先创建了User1然后属性注入user2,这时工厂没有User2对象,于是创建User2,User2创建完后开始对内部的属性user1注入,这时会跳到上图,然后我们再跟踪进去

singletonObject == null 目前是为空的,因为我们的user1还在创建中,然而这一行代码我们运行isSingletonCurrentlyInCreation(beanName)

返回的是true,还记我们创建对象时会将对象标识为正在创建吗?这里就起到了效果,那么if会成立,这个时候就会从我们的ObjectFactory获取,在上文提到过ObjectFactory是间接存取,
随后User2拿到了User1的对象,然后User1也会拿到User2的对象因此解除循环依赖,创建对象成功。

面试官:Spring为什么采用三级缓存的方式解决循环依赖?用二级缓存不可以吗?

我:如果只是在IOC这种常规的方式,使用二级缓存也是可以的,但是Spring如果用上了Spring AOP、事物等类增强的方式,那么二级缓存就会有些问题。下面笔者将仔细和大家探讨Spring为何采用三级缓存的。

首先看到这个图,里面有三个属性用来存储实例对象,这也就是我们所说的三级缓存。

然后我们看到这个代码逻辑,其实很简单就是判断缓存是否存在,通过dubug发现,我们通常是在ObjectFactory<?> singletonFactory这里获取,这个singletonFactory。我们再对照下面的代码,可以看到这个值是在依赖注入之前就存起来了,

简单介绍了Spring的三级缓存后,我们再来思考下如果我们采用二级缓存能实现user1互相user2注入吗?那么如果我们这里将代码改成

//修改源码 将Spring 三级缓存改成二级缓存
    //earlySingletonObjects.put("xxx");

这样就不存在singletonFactory.getObject();而是通过earlySingletonObjects直接获取,

这里的get也是可以通过,似乎二级缓存我们也可以实现依赖注入,但是Spring为什么采用三级这么多余的操作呢?我们回想一下前面我们的二级缓存是在常用的情况下,也就是说我们的对象没有被增强的情况,如果我们的对象通过AOP或者事物的方式进行了增强,这会是怎样的一个问题呢?现在我们来举例:假如我们的user1、user2都是被AOP增强的对象,(这里请大家注意一个细节,熟悉SpringAOP源码的朋友应该知道,AOP的操作周期是在依赖注入完成之后)好了我们现在user1进行实例化,并且user1对user2赋值,注意这里的user1是原生的对象,此时user2开始创建并对user1赋值,此时按照我们二级缓存的方式,user1会脱离循环执行earlySingletonObjects.get方法,然后user2注入的对象是原声的user1对象,user2注入完后由于被AOP增强,所以User2成为了代理对象,所以此时的user1注入的是user2的代理对象,此时User1注入完毕了,user1按照AOP增强也成为了代理对象,这里就会有很大的问题,我们的user1是代理对象,而user2注入的user1确实原生对象,那么Spring之如何解决的呢?

三级缓存的精妙

我们发现Spring的三级缓存无非就是多了一个singletonObject = singletonFactory.getObject();这行代码,也就是ObjectFactory,注意这里ObjectFactory这是函数表编写

我们来看看singletonObject = singletonFactory.getObject()到底干了什么

这里是将我们的后置处理器再次循环,然后找出SmartInstantiationAware后置处理器,也就是说如果我们的有实现了它的类就会去执行getEarlyBeanReference,这里我们看到AOP的核心类

在AbstractAutoProxyCreator中就实现了它,因此这里会去调用AOP的getEarlyBeanReference,也就是下面的方法

这里就是增强,也就是说如果这个类是需要被增强的类,那么这个类会被提前增强,而且这里为什么会有AOP的缓存?道理也很简单,因此如果SpringAOP提前对user进行了增强,那么在依赖注入后的增强就会通过这个缓存判断是否已经被增强,这样就可以实现增强代码只实现一次,不重复增强user类,那么这就实现了注入的对象和原来的类是同一个。

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值