Spring三级缓存解决循环依赖 源码解析

什么是循环依赖

什么是循环依赖

当我们代码中出现,形如TestA类中依赖注入TestB类,TestB类依赖注入A类时,在IOC过程中creaBean实例化A之后,发现并不能直接initbeanA对象,需要注入B对象,发现对象池里还没有B对象。通过构建函数创建B对象的实例化。又因B对象需要注入A对象,发现对象池里还没有A对象,就会套娃。

什么是三级缓存

三级缓存
三层缓存 singletonFactories 中的泛型是ObjectFactory<?>:是接口,源码上面包含@FunctionInterface,指为函数式接口,仅有一个方法,可以传入lambda表达式,可以是匿名内部类,通过调用getObject方法来执行具体的逻辑。ObjectFactory.getObject() 方法最终会调用getEarlyBeanReference()进行处理,返回创建bean实例化的lambda表达式。
二级缓存 earlySingletonObjects 存放bean,保存半成品bean实例,当对象需要被AOP切面代时,保存代理bean的实例beanProxy
一级缓存(单例池)singletonObjects 存放完整的bean实例

Spring的单例对象的初始化过程

首先我们得清楚Spring的单例对象的初始化,主要分为三步:
1、createBeanInstance: 实例化,也就是在内存中给对象开辟空间
2、populateBean:填充属性
3、initializeBean:调用spring xml中的init() 方法

循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖

开始以debug点入源码

beanFactory.preInstantiateSingletons();

beanFactory.preInstantiateSingletons();
这方法开始执行的话说明开始要完成bean的实例化了,将要进行初始化

在doCreateBean的createBeanInstance里进行了bean的实例化,但是还没有进行初始化

首先创建实例化A

Spring首先会创建实例化A,然后会开始尝试填充A中的b属性,首先会从一级缓存也就是单例池中查找,然后会从二级缓存中找,一样找不到,它就会将实例化A放入三级缓存中(此时的实例化A还只是实例化了并没有初始化),因为三级缓存泛型是ObjectFactory,所以能放入。
A新增到三级缓存
这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

将A对象放入三级缓存后会实例化B

实例化完B之后一样会从一级缓存中的顺序去找有没有a属性,最后会在三级缓存中找到并没有初始化的A并填充到a属性中。通过缓存的lambda表达式创建A实例对象,并放到二级缓存earlySingletonObjects中

此时B已经实例化并且初始化了

之后会将B对象存放到一级缓存中。但是当有AOP时,B对象还没有存入到一级缓存时,B对象初始化之后需要进行代理对象的创建,此时需要从三级缓存中获取bean实例对象,进行creatProxy创建代理类操作,这时会把proxy和B放入二级缓存中,才会把完整的B对象存入一级缓存中,返回给A对象

再次对A进行初始化

B对象已经完全实例化、初始化完成并存入一级缓存了,这时对象A继续从一级缓存中查询B对象,将会找到并填充到A.b属性中,这时候A里面的b属性有值了,并且是A对象,然后A对象里面继续会有b属性一直循环下去,填充完A.b属性之后会将A对象也存入一级缓存中。

这时候,A、B两对象都已经初始化完成并且都存入一级缓存了,也就创建A、B对象完成了

我的理解

三级缓存实现的过程:先实例化A,再初始化A在初始化A的过程中实例化B,初始化B。

底层会开始从一级缓存开始查,然后查二级缓存,再查三级缓存,在三级缓存中查到了k为a和value为参数为a的getEarlyBeanReference(beanName,mbd,bean)的方法,查到后将a存入二级缓存,然后以b对象开始再进行一次从一级缓存开始查找的操作,然后在二级缓存中找到了a 的半成品,将a 的半成品先当做成品作为b的属性a一起存入一级缓存,存入一级缓存的之后会进行删除b的三级缓存的操作,然后重新对a对象进行初始化,从一级对象中找到b的实例,此时b对象是已经初始化完成的,将b对象赋值为a.b属性,此时能在源码里面看到a里面的b属性是有值的并且是一直循环a和b对象的,然后初始化完成将a存入一级缓存删除所对应的三级缓存之后,a,b就都初始化完成了

三级缓存解决循环依赖问题的关键是什么?为什么通过提前暴露对象能解决?

实例化和初始化分开操作,在中间过程中给其他对象赋值的时候,并不是一个完整对象,而是把半成品对象赋值给了其他对象。

如果只使用一级缓存能不能解决问题?

不能。在整个处理过程中,缓存中放的是半成品和成品对象,如果只有一级对象,那么成品和半成品都会放到一级缓存中,有可能在获取过程中获取到半成品对象,此时半成品对象是无法使用的,不能直接进行相关的处理,因此要把半成品和成品的存放空间分割开来。
一级缓存是用来存放就绪状态的Bean的。保存在该缓存所实Aware子接口的方法已经回调完毕。初始化init()方法都已经完成了

只使用二级缓存行不行?为什么需要三级缓存?

三级和二级比,到底多了什么?

三级比二级多了getEarlyBeanReference(beanName, mbd, bean)方法

如果能保证所有的对象都不去调用getEarlyBeanReference方法,使用二级缓存可以嘛?

可以

为什么要加三级缓存,使用三级缓存的本质为了解决AOP的代理问题,就是说如果没有进行AOP的话,就能用二级缓存解决循环依赖的问题

如果创建对象时需要代理对象, 会不会创建普通的bean对象

会,代理过程有判断可能不会执行,不执行的话就会直接返回传入的bean对象,所以需要提前把对象创建准备好

创建代理对象方法

为什么使用三级缓存就能解决代理问题

当一个对象需要被代理的时候,在整个创建过程中是包含两个对象的。一个是普通对象,一个是代理生成的对象,bean默认都是单例的,那么在整个生命周期的处理环节中,一个beanName能对应两个对象嘛?不能,既然不能的话,保证我在使用的时候加一层判断,判断一下是否需要进行代理的处理。

因为不知道什么时候会调用,所以通过一个匿名内部类的方式,在使用的时候直接对普通对象进行覆盖操作,保证全局唯一对象!!

为什么三级缓存要有lambda表达式

因为要给B对象的a属性赋值,但当时A只是完成了实例化还没有完成初始化操作,给传的是a.getEarlyBeanReference,就会判断对象是否需要生成对应的代理。调用此方法才会生成对应的代理

所以
一级缓存存放成品对象

二级缓存存放半成品对象

三级缓存放lambda表达式,来完成代理对象的覆盖过程

就逻辑很清晰

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值