人人都能看懂的Spring源码解析,Spring如何解决循环依赖
人人都能看懂的Spring源码解析,Spring如何解决循环依赖
- 原理解析
- 什么是循环依赖
- 循环依赖会有什么问题?
- 如何解决循环依赖
- 问题的根本原因
- 如何解决
- 为什么需要三级缓存?
- Spring的三级缓存
- 源码走读
- Spring的三级缓存
- 提前暴露
- getSingleton方法
- 总结
往期内容:
- 人人都能看懂的Spring底层原理,看完绝对不会懵逼
- 简单易懂的Spring扩展点详细解析,看不懂你来打我
- 人人都能看懂的Spring源码解析,配置解析与BeanDefinition加载注册
- 简单易懂又非常牛逼的Spring源码解析,ConfigurationClassPostProcessor的具体逻辑
- 简单易懂值得收藏的Spring源码解析,依赖注入和bean的初始化
原理解析
什么是循环依赖
两个Bean,BeanA和BeanB,它们都有一个引用指向对方,这就是最简单的循环依赖。还有更复杂的循环依赖,涉及到多个bean,只要其中形成环,就是循环依赖。
循环依赖会有什么问题?
如果Spring不对这种情况做处理,那么在进行依赖注入的时候就会出现死循环。
如何解决循环依赖
问题的根本原因
要解决循环依赖的问题,首先就要分析导致这个问题的根本原因。
因为上面是假设Spring只有一个缓存,那就是单例缓存池,而且该缓存池是用于存放已初始化完成的bean,而没有完成依赖注入的bean不算是初始化完成的bean,所以不会放入单例缓存池中。
所以beanA依赖beanB时,只能实例化一个beanB,然后对其进行依赖注入,而beanB又依赖了beanA,所以又只能实例化一个beanA,对其进行依赖注入,这样就没完没了了。
如何解决
解决办法就是多增加一个缓存(二级缓存),用于存放已经实例化但是未完成初始化的bean。因为增加了一个二级缓存,在bean被实例化的之后就预先放入到该二级缓存中,那么在后续其他bean进行依赖注入的时候,发现依赖了该bean,然后检查到二级缓存中有,就不需要往下进行实例化。
比如上图,在第二次getBean(beanA)的时候,二级缓存中已经存放了beanA,因此不会往下再次进行beanA的实例化,而是直接从缓存中取,死循环因此就解决了。
为什么需要三级缓存?
那看起来搞两级缓存就能解决问题,但是Spring实际上是有三级缓存的,那为什么Spring需要搞三级缓存呢?
那是因为Spring要处理AOP,我们可以想一想,如果只有两级缓存,要怎么同时处理循环依赖和AOP呢?
此时就要在每个bean实例化后,放入二级缓存之前,都要判断一下这个bean是否需要进行AOP处理:如果需要,就要先通过动态代理生成代理对象,放入二级缓存的就是代理对象;如果不需要进行AOP处理,那么就把原始对象放入二级缓存。
这个显然不符合Spring的设计。Spring的设计是把AOP放到依赖注入完成以后,在初始化阶段触发的,而现在却要把AOP的处理提前到实例化之后。
那如果还是要把AOP放到初始化阶段触发,只有涉及到循环依赖的bean才提前进行AOP呢?那就要在每次从二级缓存获取bean的时候,都要判断一下是否需要进行AOP处理,这样就太繁琐了。
因此最好的解决办法就是再加一级缓存,三级缓存。先从二级缓存中取,如果有,直接返回,如果二级缓存没有,再从三级缓存中拿,放入二级缓存,并从三级缓存删除,并且只有从三级缓存中取的时候,才判断是否需要进行AOP处理。
Spring的三级缓存
- singletonObjects:一级缓存,Map<String, Object>类型,key是beanName,value是已初始化完成的bean。
- earlySingletonObjects:二级缓存,Map<String, Object>类型,key是beanName,value是已实例化完成但是未进行初始化的bean。
- singletonFactories:三级缓存,Map<String, ObjectFactory<?>>,key是beanName,value是ObjectFactory<?>类型。ObjectFactory是一个函数式接口,可以视为一个回调函数,里面的逻辑就是调用bean后置处理判断是否需要进行AOP处理,如果需要则进行AOP处理,返回一个代理对象,如果不需要进行AOP处理,则返回原始对象。
源码走读
Spring的三级缓存
上面描述的三级缓存,就是DefaultSingletonBeanRegistry的成员变量。
而DefaultSingletonBeanRegistry又是DefaultListableBeanFactory的祖先类,所以三级缓存就在DefaultListableBeanFactory里面。
提前暴露
AbstractAutowireCapableBeanFactory#doCreateBean方法,首先调用createBeanInstance方法进行bean的实例化。
实例化以后,就是判断是否需要提前暴露(earlySingletonExposure为true),如果需要,则调用addSingletonFactory方法进行提前暴露。
然后下面的populateBean方法里面就是依赖注入的处理,initializeBean方法里面就是bean初始化的处理,这两个方法在前面的文章已经讲过。
需要提前暴露的条件:
- mbd.isSingleton():是否单例。
- this.allowCircularReferences:是否允许循环依赖,默认为true(但是高版本改成默认为false了,Spring希望我们把代码写的更好,而不是指望Spring来给我们处理循环依赖的问题)。
- isSingletonCurrentlyInCreation(beanName):该bean是否正在创建中。
三个条件都满足,就调用addSingletonFactory方法,提前暴露。
addSingletonFactory方法里面,就是把beanName和ObjectFactory类型的对象singletonFactory,放入三级缓存singletonFactories。这里的singletonFactory的就是外面的lambda表达式 () -> getEarlyBeanReference(beanName, mbd, bean)。ObjectFactory是一个函数式接口,里面有一个getObject方法,而singletonFactory的getObject方法就是调用getEarlyBeanReference(beanName, mbd, bean)。
getSingleton方法
一个bean在依赖注入阶段,要处理对别的bean的依赖,会调用beanFactory的getBean方法,尝试从容器中取,getBean方法会先调用getSingleton方法,尝试从三级缓存中取。
- 首先从第一级缓存取this.singletonObjects.get(beanName),不是null就返回
- 如果第一级缓存取到的是null,则从第二级缓存取this.earlySingletonObjects.get(beanName),不是null就返回
- 如果第二级缓存取到的还是null,则从第三级缓存取this.singletonFactories.get(beanName)
- 如果第三级缓存取到的不是null,则调用getObject方法,获取getObject方法返回的对象,放入二级缓存,从三级缓存中删除
- 如果第三级缓存取到的还是null,就要走创建bean的逻辑
这里的singletonFactory的getObject实现方法,就是addSingletonFactory方法的第二个参数的lambda表达式 () -> getEarlyBeanReference(beanName, mbd, bean),也就是会调用 getEarlyBeanReference(beanName, mbd, bean) 方法获取返回的对象。
可以看到遍历所有的bean后置处理器,判断如果是SmartInstantiationAwareBeanPostProcessor类型,就回调getEarlyBeanReference方法,这里会进入到AbstractAutoProxyCreator#getEarlyBeanReference方法。
wrapIfNecessary方法就是判断如果当前bean需要进行AOP处理,则进行动态代理,返回代理对象;如果不需要进行AOP处理,会返回原始对象。
总结
以上就是关于“Spring如何处理循环依赖”的讲解的全部内容。下面做一个简单总结:
- Spring是通过三级缓存解决循环依赖的。一级缓存是singletonObjects;二级缓存是earlySingletonObjects;三级缓存是singletonFactories。
- 一个bean在实例化完成后,在进行依赖注入之前,如果判断满足三个条件(当前bean是一个单例bean,并且允许循环依赖,并且当前bean正在创建中),就会进行提前暴露,放入到第三级缓存中。
- 在依赖注入阶段,会调用beanFactory的getBean方法获取依赖的bean,getBean方法会先调用getSingleton方法尝试从三级缓存中取。
- getSingleton方法先尝试从第一级缓存取,如果没有再从第二级缓存取,如果再没有就从第三级缓存取,如果第三级缓存中有,会调用getObject方法获取返回对象,再放入第二级缓存,从第三级缓存中删除。
- singletonFactory的getObject方法,会调用getEarlyBeanReference方法,里面会调用到AbstractAutoProxyCreator的getEarlyBeanReference方法,判断是否要进行AOP处理,如果需要会进行动态代理返回代理对象,如果不需要,就返回原始对象。
人人都能看懂的Spring源码解析,Spring如何解决循环依赖相关推荐
- 人人都能读懂的react源码解析(大厂高薪必备)
人人都能读懂的react源码解析(大厂高薪必备) 1.开篇(听说你还在艰难的啃react源码) 本教程目标是打造一门严谨(严格遵循react17核心思想).通俗易懂(提供大量流程图解,结合demo ...
- spring源码解析(五) 循环依赖
1.什么是循环依赖? Bean A → Bean B → Bean A 在A对象生命周期中注入B,进入B生命周期找A,但A还不存在,继续进入A对象生命周期找B,这就是循环依赖. 2.循环依赖造成的结果 ...
- Spring源码解析-三级缓存与循环依赖,nginx架构图
两个流程理论上是互不影响的 protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @N ...
- Spring 源码总结、IOC、循环依赖、AOP分析
Spring 源码 本文基于 jdk 11 核心类 interface BeanFactory 该接口是访问 Spring bean 容器的根接口,是 bean 容器的基本客户端视图: 其他接口如Li ...
- spring源码阅读笔记09:循环依赖
前面的文章一直在研究Spring创建Bean的整个过程,创建一个bean是一个非常复杂的过程,而其中最难以理解的就是对循环依赖的处理,本文就来研究一下spring是如何处理循环依赖的. 1. 什么是循 ...
- 一篇从零开始、步骤完整的网站搭建教程(全篇7000字、102张截图说明,力求每一个人都能看懂,附源码)
从今年八月开始到现在自己也是从0开始做了有两个网站: 这中间也经常有不了解的地方需要去查.其实网上的资料也不少 但可能相对比较零散,需要反复的查来查去,费时又累心 那这次有时间就想着说写一篇从零开始. ...
- 不再害怕面试问ArrayMap一文完全看懂Android ArrayMap源码解析
作者:VIjolie 前言 ArrayMap是谷歌推出的在安卓等设备上用于替代HashMap的数据结构,和HashMap相比,具有更高的内存使用率,因此适合在Android等内存较为紧张的移动设备,下 ...
- Spring-bean的循环依赖以及解决方式___Spring源码初探--Bean的初始化-循环依赖的解决
本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可以应用在我们实际开发项目中. 什么是循环依赖? 怎么检测循环依赖 Spring怎么解决循环依赖 S ...
- 人人都能看懂的Spring底层原理,看完绝对不会懵逼
人人都能看懂的Spring原理,绝对不会懵逼 为什么要使用Spring? Spring的核心组件 Spring是如何实现IOC和DI的? 定义了BeanDefinition 扫描加载BeanDefin ...
最新文章
- Solidworks2017安装与破解
- oracle 更改ip
- 线性八叉树_octree八叉树数据结构原理与实现
- 字体怎么安装到电脑上_文章还在使用电脑上的固定字体?这款字体软件超好用...
- PCB常用度量衡单位
- 20道有代表性的HTML基础题,测测你能入前端的门吗
- Linux基础命令---cp
- 第二十一天 认识一维数组part3
- 【异常处理】Word2016 出现“此功能看似已中断 并需要修复
- 图层php,ps图层怎么用
- 苹果AppStore审核,技术支持网址不通过被拒绝
- vue实现结算淘宝购物车效果
- android系统裁剪优化
- python中文姓名排序_Python实现针对中文排序的方法
- Linux项目实训一
- 计算一个数二进制中1的个数超全解法(C语言)
- 7个H5网页制作工具全面介绍
- 【Unity】基础游戏单位GameObject中常用的属性和API
- 单细胞转录组文章复现系列(一)——seurat
- OJ 1202 镂空三角形