前言

假设对象A、B 之间相互依赖,Spring IOC是如何解决A、B两个对象的实例化的?答案是三级缓存

三级缓存

SpringIOC 通过三级缓存来解决循环依赖问题,三级缓存指的是三个Map:

  • singletonObjects:一级缓存,key为BeanName,value为Bean,日常获取Bean的地方
  • earlySingletonObjects:二级缓存,key为BeanName,value为Bean,已经实例化但还没有进行属性注入的Bean,由三级缓存放入
  • singletonFactories:三级缓存,key为BeanName,value为对象工厂(ObjectFactory)

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

解决循环依赖

SpringIOC解决循环依赖的思路就是依靠缓存,同时还得引出个概念即早期暴露引用。我们知道在IOC容器里Bean的初始化的过程分为三个步骤:创建实例、属性注入实例、回调实例实现的接口方法

解决思路就在这:当我们创建实例与属性注入实例这俩个步骤之间的时候,我们引入缓存,将这些已经创建好但是并没有注入属性的实例放到缓存里,而这些放在缓存里但是没有被注入属性的实例对象,就是解决循环依赖的方法

打个比方:A对象的创建需要引用到B对象,而B对象的创建也需要A对象,而此时当B对象创建的时候直接从缓存里引用A对象(虽然不是完全体A对象,毕竟没有赋值处理),当B对象完成创建以后再被A对象引用进去,则A对象也完成了创建。

解决循环依赖具体过程

对Bean的创建最为核心三个方法解释如下:

  • createBeanInstance:实例化,通过反射调用对象构造方法实例化对象;
  • populateBean:填充属性,主要是对bean的依赖属性进行赋值;
  • initializeBean:初始化,可以回调InitializingBean、initMethod等方法。

实例化A的时候,先将A创建(早期对象)放入一个池子(singletonFactories)中。这个时候虽然属性没有赋值,但是容器已经能认识这个是A对象,只是属性全是null而已。在populateBean方法中对属性赋值的时候,发现A依赖了B,那么就先去创建B,又走一遍bean的创建过程(创建B)。同样也会把B的早期对象放入缓存(singletonFactories)中。当B又走到 populateBean方法(负责填充Bean实例属性的方法)的时候,发现依赖了A,我们又去创建A,但是这个时候去创建A,发现我们在缓存(singletonFactories)能找到A(早期对象),此时会通过A的ObjectFactory获取A,并把A从三级缓存移到二级缓存。然后就可以把B的A属性赋值了,这个时候B就初始化完成了,初始化完成后就会把B从三级缓存移到一级缓存。完成B实例化后,回到A调用的populateBean方法中。返回的就是B对象了,对A的B属性进行赋值就可以了。

流程如下:图片来源

IOC无法解决的两种循环依赖

一种是非单例对象,因为非单例对象不会放入缓存的。每次都是需要创建。

二是通过构造器注入,也无法解决。从上面的流程可以看出,调用构造器创建实例是在createBeanInstance方法,而解决循环依赖是在populateBean(负责属性注入的方法)这个方法中,执行顺序也决定了无法解决该种循环依赖。

为什么采用三级缓存?

一级缓存是单例缓存池(singletonObjects)

二级缓存是早期对象(earlySingletonObjects)

三级缓存是一个包裹对象ObjectFactory(registeredSingletons),通过getObject获取到早期对象。

从上面的流程来看,实际上二级缓存已经可以解决循环依赖了,那么为什么Spring还要包裹出来一个三级缓存呢?

三级缓存其实是为了解决代理对象之间(AOP)的循环依赖。如果没有三级缓存,在对象被AOP代理的情况下,存入二级缓存前都需要先去做AOP代理。二级缓存存在的必要就是为了性能,从三级缓存的工厂创建出对象,直接放入二级缓存,避免每次都从工厂中获取。.

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

三级缓存的value是ObjectFactory,ObjectFactory 的 getObject 如果包装的对象被AOP代理,则会返回相应的代理对象

具体源码分析可以参考:Spring ioc(4)—如何解决循环依赖

三级缓存的划分及作用

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

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

三级缓存 singletonFactories其职责就是包装一个bean,有回调逻辑,主要用于解决代理对象的循环依赖,所以它的作用非常清晰,并且只能处于第三层。

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

总结

  1. SpringIOC 通过三级缓存解决循环依赖
  2. 要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。
  3. Bean在实例化后(createBeanInstance)、属性注入前(populateBean),会先将属性为null的Bean包装成对象工厂(ObjectFactory)放入三级缓存中,在属性注入过程中会依次从一级到三级查询缓存查找依赖的Bean,不存在则先实例化依赖的Bean,完成属性注入。Bean初始化完成后,会被放入一级缓存
    4.三级缓存其实是为了解决代理对象之间(AOP)的循环依赖,通过第三级缓存我们可以拿到可能经过包装的对象,解决对象代理封装的问题。

参考

  • 实例创建流程_从Spring Bean创建流程中看三级缓存解决循环依赖
  • 好文:Spring Bean 循环依赖为什么需要三级缓存

Spring IOC 如何解决循环依赖?相关推荐

  1. Spring是如何解决循环依赖的

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的.这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够 ...

  2. 万字长文带你吃透Spring是怎样解决循环依赖的

    在Spring框架中,处理循环依赖一直是一个备受关注的话题.这是因为Spring源代码中为了解决循环依赖问题,进行了大量的处理和优化.同时,循环依赖也是Spring高级面试中的必考问题,回答得好可以成 ...

  3. Spring三级缓存解决循环依赖问题详解

    spring三级缓存解决循环依赖问题详解 前言 这段时间阅读了spring IOC部分的源码.在学习过程中,自己有遇到过很多很问题,在上网查阅资料的时候,发现很难找到一份比较全面的解答.现在自己刚学习 ...

  4. Spring的getBean解决循环依赖

    Spring是如何解决循环依赖的? 通过三级缓存提前暴露对象解决的. 三级缓存存放了哪些对象信息? 一级缓存存放的是完整对象. 二级缓存存放的是那些属性还没赋值的对象. 三级缓存存放的是ObjectF ...

  5. Spring 是如何解决循环依赖的?

    1.由同事抛的一个问题开始 最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到.平时自认为对spring循环依赖问题还是比较了解的,直到遇到这个和后面的 ...

  6. 互相引用 spring_听说你还不知道Spring是如何解决循环依赖问题的?

    作者:Vt 前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入 ...

  7. Spring是如何解决循环依赖的?

    1.案发情况 @Service public class TestService1 {@Autowiredprivate TestService2 testService2;@Asyncpublic ...

  8. 框架源码专题:Spring是如何解决循环依赖的?

    文章目录 1.什么是循环依赖? 2.解决循环依赖思路 3. 使用了三级缓存还有什么问题?怎么解决的? 4. 手写伪代码解决缓存依赖 5. 二级缓存能否解决循环依赖,三级缓存存在的意义 6. Sprin ...

  9. 【171期】面试官:小伙汁,Spring是怎么解决循环依赖的呢?

    程序员的成长之路 互联网/程序员/技术/资料共享 关注 阅读本文大概需要 17 分钟. 来自:blog.csdn.net/Baisitao_/article/details/107349302 前言 ...

最新文章

  1. spi时序图怎么分析,怎么看懂spi时序图
  2. 廖雪峰Java1-2Java程序基础-3整数运算
  3. log4j2动态修改日志级别及拓展性使用
  4. python抖音github_GitHub - eternal-flame-AD/Douyin-Bot: Python 抖音机器人,论如何在抖音上找到漂亮小姐姐?...
  5. 用字符串表达式访问JSON数据(java,fastjson)
  6. 如何使用Tasklist命令
  7. PHP中date()函数里的参数
  8. wifi p2p连接 linux,Wi-Fi p2p ap 共存
  9. DEL: Chrome Browser Shortcuts
  10. AOP(execution表达式)
  11. Carhart四因子模型实用攻略
  12. “流动书库”藏书汇总,欢迎借阅
  13. 缓存:浏览器缓存、DNS缓存和CDN缓存
  14. html爱心表白代码(最全)
  15. 某程序员自述:我,三十多岁,逃离北上广,通过技术移民到加拿大!
  16. iOS:iOS 的 APP 如何适应 iPhone 5s/6/6Plus 三种屏幕的尺寸?
  17. CSS 字体新玩法之彩色字体
  18. steam安装维护服务器,安装steam无法连接服务器 | 手游网游页游攻略大全
  19. 蓝桥杯——蓝肽子序列
  20. win10的局域网如何设为专用网络

热门文章

  1. 2019 ICPC World Finals Problem J. Miniature Golf
  2. 【学习笔记】我命由天不由我之随机化庇佑 —— 爬山法 和 模拟退火法
  3. 【无码专区12】子集和(背包dp)
  4. P6177-Count on a tree II/[模板]树分块
  5. P6046-纯粹容器【数学期望,组合数】
  6. P3112-[USACO14DEC]后卫马克Guard Mark【贪心】
  7. ssl提高组周三备考赛【2018.10.17】
  8. 2021牛客暑期多校训练营1 J-Journey among Railway Stations(线段树+思维转化)
  9. 2019.01.24【NOIP普及组】模拟赛C组
  10. SpringCloud Zuul(六)之PRE Filter