Spring 循环依赖问题_____通俗易懂!!利用三级缓存解决循环依赖。

理解循环依赖的前提是先要去理解Bean的生命周期,循环依赖问题就是出现在bean进行赋值时,Bean生命周期的流程我将在后续进行总结,首先简单说说Bean的生命周期:

● bean对象首先要进行实例化,也就是在堆中申请空间,使用createBeanInstance方法通过反射创建对象。

● 然后要对bean进行填充,也就是对它的属性进行赋值。属性分为自定义的属性和容器属性,自定义属性的赋值交给populateBean方法进行set赋值,而容器属性就要通过实现Aware接口,在innvokeAwareMethods方法里面进行统一进行赋值。

这里也就是循环依赖可能会出现的地方。

● 初始化

● 使用

● 销毁


来看看循环依赖是一种什么情况:

 A对象中有B的属性b,而对象B有A的属性a,在A进行实例化后,就要进行赋值操作,但是A的赋值依赖于B,所以它就要去找内存中有没有B,发现没有B,所以就要创建一个B的实例,在B进行赋值时,发现需要A才能完成填充,而A又处于等待B的状态(是不是很像死锁),所以就会循环的互相依赖,这就叫循环依赖

这里做一个小补充:在Bean的生命周期中,我们将对象会分为两类,已经进行过实例化和初始化的叫做成品对象,也就是单例对象,放入单例池中。将只进行过实例化未进行初始化的叫做半成品对象,放在半成品池中。


相关方法参考:

● createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象。
● populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)。
● initializeBean:初始化,回到一些形如initMethod、InitializingBean等方法。

● getBean:先去单例池中寻找 A对象 是否存在,存在则直接返回,不存在则进入后续创建流程。

● createProxy:创建动态代理

● getEarlyBeanReference:提前处理,在对象 实例化完成 后在工厂池中创建一个工厂,这个工厂会通过调用提前引用来创建代理对象。

● postProcessAfterInitialization后置处理,在对象 初始化完成 后调用后置处理来创建代理对象

提前暴露:在实例化后,我们就可以将对象的引用放入缓存交给需要引用依赖的其他对象,这个过程就是提前暴露。


一级缓存

那么如何解决这个问题呢?

如果我们只持有一个对象的引用,能否在后续步骤中再对其进行赋值呢?

答案是可以的。

我们就在中间加上map结构一级缓存。

过程如下:

1. 当进行实例化时,先将实例化的半成品A加入到缓存中。

2. 在给A赋值时,查看缓存中是否有B对象。

3. 没有则创建B对象,给B对象进行实例化。

4. 实例化后将B加入到缓存中。

5. 然后在对B的a对象赋值时,查看缓存中有A对象的半成品,赋值给a,这时B就是个成品了,再将B的成品加入到缓存中。

6. 然后回到A的赋值阶段,将成品B赋值给b,再将成品的A加入缓存。

(如下图)

一级缓存通过提前暴露对象就解决了循环依赖问题,但是没有实现划分思想,拿到的也是bean的原始引用,如果我们需要的是bean的代理对象怎么办?Spring里充斥了大量的动态代理模式的架构,典型的AOP就是动态代理模式实现的。


二级缓存

这样确实是解决了问题,但是如果这样的话各种成品和半成品冗杂在一起很乱,效率很低,那么我们可不可以将半成品和成品分开来进行缓存?

所以当我们利用二级缓存来进行处理:

二级缓存,也就是增加了一个半成品池来存放半成品对象。

创建时:对象一旦实例化完成【createBeanInstance("a")】,会先将对象放入半成品池中

对象引用时:会先找单例池,如果不存在则会去半成品池中寻找

二级缓存执行过程如下:

1. A实例化完成后,将实例化后的A对象放入半成品池中。

2. 为A对象填充属性(需要B属性,所以去单例池和半成品池寻找B,没有则创建)

3. 对B进行实例化,然后进行赋值,需要A属性,去实例池找A,发现没有,然后去半成品池中找A,找到了,将A对象填充到B属性中。

4. B填充和初始化完成,放入单例池(成品池)。

 5. A对象填充, B属性存在【getBean单例池中找到了】

6. A对象填充和初始化完成,放入单例池。

7. 将半成品池中的已实例完成的对象的半成品清除。

二级缓存确实是划分了不同的区块来执行相应的职责,据单一职责原理,符合了JAVA编程思想,但是还是没有解决AOP代理问题,拿到的还是原始引用。


三级缓存

所以我们使用加上了工厂池三级缓存来解决解决循环依赖创建+AOP代理问题

所谓的三级缓存就是在 单例池、半成品池 的基础上,增加了一个工厂池,里面存储的都是每个对象绑定的ObjectFactory(),此时的 单例池、半成品池 中 存储的 已经 不是对象本身,而是代理对象。

先来看看三级缓存:

名称作用
singletonObjects一级缓存:存放完整的Bean
earlySingletonObjects二级缓存:存放提前暴露的Bean,Bean不是完整的,为完成属性注入和执行初始化方法
singletonFactories三级缓存:存放的是Bean工厂,主要生产Bean,存放到二级缓存中

 注意一些细节,一级缓存的大小为256,且类型为ConcurrentHashMap,三级缓存的val的泛型类型为ObjectFactory。

ObjectFactory()作用特点:

● 为了调用提前处理【getEarlyBeanReference】来执行创建动态代理【createProxy】
● 对象 实例化完成【createBeanInstance】,就会在工厂池中创建一个factory()
factory()不一定会被执行,只有该对象在创建过程中,又被其他对象所引用才会被调用,从而执行createProxy

 

 这里引用一下别的博客的图,博客链接会在下面放出。

AOP处理器实现了Bean处理器接口

AOP处理器创建代理对象会通过两个入口来调用创建动态代理(createProxy):

postProcessAfterInitialization:【后置处理】在对象 初始化完成 后调用后置处理来创建代理对象
getEarlyBeanReference:【提前处理】在对象 实例化完成 后在工厂池中创建一个工厂,这个工厂会通过调用提前引用来创建代理对象。

知道这些后我们来看看三级缓存下的执行过程:

1. A对象实例化完成后,先会在工厂池中放入一个factory(a)。

2. 对A进行填充,需要从单例池或者半成品池中获取对象B。

3. 没有找到B,执行B的创建流程。

4. B对象实例化,加入工厂池factory(b)。

5. 为B填充属性,要用到A对象。

6. 首先判断单例池和半成品池,没有发现A对象。

7. 从工厂池中调用factory(a),AOP切入,调用执行提前引用,创建A动态代理

8. 将A对象的代理放入半成品池,可以被获取到并进行填充。

9. B获取并且填充、初始化,AOP切入,调用执行后置处理,创建B动态代理

10 .将B对象的代理放入单例池,可以被获取到并进行填充。

 11. A对象获取填充B属性(getBean单例池中找到了B代理对象)。

12. A属性填充和初始化完成,AOP切入,调用执行后置处理。

13. 这时半成品池中已经有了A的代理,所以不需要执行createProxy来创建代理,会将半成品池中的A代理对象移入单例池(注意是移入,不是复制)。

14. 一旦对象进入单例池,则意味着创建流程已经全部完成,此时会将工厂池的相应factory()进行清理。

通过第三级缓存我们可以拿到可能经过包装的对象,解决对象代理封装的问题。

在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。


最后聊聊各级缓存:

一级缓存singletonObjects是完整的bean,它可以被外界任意使用,并且不会有歧义。

二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。

三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。

 

问:为什么不在创建实例化后直接调用提前引用,而是要通过工厂方法调用?

答:所谓的提前引用指的是在创建的过程当中被引用。A在创建过程中,如果有人要去引用这个A,那么才会执行这个提前引用流程,否则这个factory(a)是不会被执行的,重点取决于是否被引用决定。

如有问题,欢迎指出。

参考文章:

(25条消息) Spring 三级缓存解决循环依赖原理分析_Fox_bert的博客-CSDN博客_spring三级缓存如何解决循环依赖

Spring Bean 循环依赖为什么需要三级缓存 - 我是属车的 - 博客园 (cnblogs.com) 

参考视频:

某站马士兵教育spring源码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值