什么是三级缓存

按照目前我们实现的 Spring 框架,是可以满足一个基本需求的,但如果你配置了A、B两个Bean对象互相依赖,那么立马会抛出 java.lang.StackOverflowError,为什么呢?因为A创建时需要依赖B创建,而B的创建又依赖于A创建,就这样死循环了,就会不断地去实例化对象。

而这个循环依赖基本也可以说是 Spring 中非常经典的实现了,所要解决的场景主要有以下三种情况:

循环依赖分为三种,自身依赖于自身、互相循环依赖、多组循环依赖。但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。所以 Spring 提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。

Spring如何处理循环依赖

简介

Spring通过三级缓存和提前曝光机制来解决循环依赖问题,这三级缓存为:

  • singletonObject:一级缓存,该缓存key = beanName, value = bean;这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存。
  • earlySingletonObjects:二级缓存,该缓存key = beanName, value = bean;这里跟一级缓存的区别在于,该缓存所获取到的bean是提前曝光出来的,是还没创建完成的。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化肯定还没有做完,因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用。
  • singletonFactories:三级缓存,该缓存key = beanName, value = beanFactory;在bean实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换成beanFactory并加入到三级缓存。在需要引用提前曝光对象时再通过singletonFactory.getObject()获取。

核心源码刨析

 protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从一级缓存获取,key=beanName value=beanObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 从二级缓存获取,key=beanName value=beansingletonObject = this.earlySingletonObjects.get(beanName);// 是否允许循环引用if (singletonObject == null && allowEarlyReference) {/*** 三级缓存获取,key=beanName value=objectFactory,objectFactory中存储getObject()方法用于获取提前曝光的实例** 而为什么不直接将实例缓存到二级缓存,而要多此一举将实例先封装到objectFactory中?* 主要关键点在getObject()方法并不一定直接返回实例,而有可能对实例又使用* SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法对bean进行处理** 也就是说,spring中所有的单例bean在实例化后都会被进行提前曝光到三级缓存中,* 但是并不是所有的bean都存在循环依赖,也就是说三级缓存到二级缓存的步骤不一定都会被执行,有可能曝光后直接创建完成,没被提前引用过,* 就直接被加入到一级缓存中。*/ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {/*** 通过getObject()方法获取bean,通过此方法获取到的实例不单单是提前曝光出来的实例,* 它还经过了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法处理过(使用AOP时进行代理,否则不做任何操作)。* 这也正是三级缓存存在的意义,可以通过重写该后置处理器对提前曝光的实例,在被提前引用时进行一些操作*/singletonObject = singletonFactory.getObject();// 将三级缓存生产的bean放入二级缓存中this.earlySingletonObjects.put(beanName, singletonObject);// 删除三级缓存this.singletonFactories.remove(beanName);}}}}return singletonObject;}
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {// 1 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);final Object bean = instanceWrapper.getWrappedInstance();if (earlySingletonExposure) {// 2addSingletonFactory(beanName, new ObjectFactory() {@Overridepublic Object getObject() throws BeansException {return getEarlyBeanReference(beanName, mbd, bean);}});}// 3 Object exposedObject = bean;// 4populateBean(beanName, mbd, instanceWrapper);// 5if (exposedObject != null) {exposedObject = initializeBean(beanName, exposedObject, mbd);}if (earlySingletonExposure) {// 6Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {// 7// exposedObject跟bean一样,说明初始化操作没用应用Initialization后置处理器(指AOP操作)改变exposedObject// 主要是因为exposedObject如果提前代理过(存在AOP的情况下),就会跳过Spring AOP代理,所以exposedObject没被改变,也就等于bean了if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {// 8...}}}return exposedObject;}protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}//  AbstractAutoProxyCreator类public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);//  如果执行AOP操作则返回代理对象,否则返回原对象return wrapIfNecessary(bean, beanName, cacheKey);}

整体流程

上面流程中,做了部分删减。但基本创建一个bean,就这几步了。

1处,创建bean对象,此时,属性什么的全是null,可以理解为,只是new了,field还没设置

2处,添加到第三级缓存;加进去的,只是个factory,只有发生提前引用的时候,才会发挥作用

3处,把原始bean,存到exposedObject

4处,填充属性;循环依赖情况下,A/B循环依赖。假设当前为A,那么此时填充A的属性的时候,会去:

new B;

填充B的field,发现field里有一个是A类型,然后就去getBean(“A”),然后走到第三级缓存,拿到了A的ObjectFactory,然后调用ObjectFactory的getObject,然后调用AOP的后置处理器类的getEarlyBeanReference方法,拿到代理后的bean(假设此处切面满足,要创建代理),并且从三级缓存中删除A的ObjectFactory,然后将代理了不完整A的代理对象放到二级缓存中;

经过上面的步骤后,B里面,field已经填充ok,其中,且填充的field是代理后的A,这里命名为proxy A。

B 继续其他的后续处理,如果需要的话在初始化完成后,B会进行AOP动态代理,创建并返回代理类proxyB。

B处理完成后,被填充到当前的origin A(原始A)的field中(如果满足切面则填充的是proxyB),同时将B或proxyB放到一级缓存中并且删除二三级缓存。

5处,对A进行后置处理,此时调用aop后置处理器的,postProcessAfterInitialization;前面我们说了,此时不会再去调用wrapIfNecessary,所以这里直接返回原始A,即 origin A;

6处,去缓存里获取A,拿到的A是二级缓存中的proxy A;

7处,我们梳理下:

exposedObject:origin A

bean:原始A

earlySingletonReference: proxy A

此时,下面这个条件是满足的,所以,exposedObject,最终被替换为proxy A:

if (exposedObject == bean) {exposedObject = earlySingletonReference;
}

至此,循环依赖问题已经被解决掉了!

为什么是三级缓存

二级缓存的必要性

如果只有一级缓存的话,那么根据提前曝光机制需要将半成品对象也放入到缓存中,这样的话缓存里既缓存了完整的Bean也缓存了半成品Bean。如果这时候,有其他线程去这个缓存里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。所以必须要再加一个缓存,来将完整的Bean和半成品Bean分开。

三级缓存的必要性

如果没有AOP的话,只有二级缓存就够了!但是现在考虑这样一种情况:发生循环依赖的两个Bean都是满足切面的Bean,都需要进行动态代理,则再为A进行属性填充时会去创建B,在为B进行属性填充时又需要引用A的代理类proxyA,但是我们知道AOP动态代理是发生在初始化之后(此时A还停留在属性填充阶段),如果只有二级缓存,在B进行属性填充的时候就没办法等到A初始化完成后生成proxy然后被B的属性引用。所以就有了第三级缓存,用来存放一个工厂对象ObjectFactory,它的作用就是在满足切面的时候将AOP提前,也就是将生成proxyA的过程从初始化完A后提前至B的属性填充的时候,我们通过ObjectFactory#getObject就可以拿到代理对象,这样就解决了发生循环依赖时的AOP问题。

AOP时机:
①:如果没有循环依赖的话,在bean初始化完成后创建动态代理
②:如果有循环依赖,在bean实例化之后创建!

多例和构造器为什么无法解决循环依赖

多例

如果是原型bean,那么就意味着每次创建对象时都不会从缓存中获取,并且每一次都不会将之添加至缓存中,而是去创建新对象。这样的话,在为B进行属性填充时发现依赖A,但是并不会去缓存中找,而是继续去创建,这样依旧还是死循环。

构造器

因为构造器是在实例化时调用的,此时bean还没有实例化完成,如果此时出现了循环依赖,一二三级缓存并没有Bean实例的任何相关信息(在实例化之后才放入三级缓存中),因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。

手写简易版Spring系列处理循环依赖问题

循环依赖的核心功能实现主要包括 DefaultSingletonBeanRegistry 提供三级缓存:singletonObjects 、 earlySingletonObjects 、singletonFactories ,分别存放成品对象、半成品对象和工厂对象。同时包
装三个缓存提供方法: getSingleton 、 registerSingleton 、 addSingletonFactory ,这样使用方就可以分别在不同时间段存放和获取对应的对象了。

在 AbstractAutowireCapableBeanFactory 的 doCreateBean 方法中,提供了关于提前暴露对象的操作, addSingletonFactory(beanName, () --> getEarlyBeanReference(beanName, bea nDefinition, finalBean));finalBean));,以及后续获取对象和注册对象的操作 exposedObject = getSingleton(beanName); registerSingleton(beanName, exposedObject);exposedObject);,经过这样的处理就可以完成对复杂场景循环依赖的操作。

另外在 DefaultAdvisorAutoProxyCreator 提供的切面服务中,也需要实现接口InstantiationAwareBeanPostProcessor 新增的 getEarlyBea nReference 方法,便于把依赖的切面对象也能存放到三级缓存中,处理对应的循环依赖。

设置三级缓存

public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {//  一级缓存,用来保存单例对象的实例(完整对象)private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//  二级缓存,用来缓存没有完成属性填充等操作的半成品对象private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);//  三级缓存private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//  保存实现了销毁方法的Bean实例private final Map<String, Object> disposableBeans = new LinkedHashMap<>();//  用来代表nullprotected static final Object NULL_OBJECT = new Object();@Overridepublic Object getSingleton(String beanName) {return getSingleton(beanName, true);}protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = singletonObjects.get(beanName);if (null == singletonObject) {singletonObject = earlySingletonObjects.get(beanName);// 判断二级缓存中是否有对象if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 把三级缓存中的代理对象中的真实对象获取出来,放入二级缓存中earlySingletonObjects.put(beanName, singletonObject);singletonFactories.remove(beanName);}}}return singletonObject;}@Overridepublic void registerSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {singletonObjects.put(beanName, singletonObject);earlySingletonObjects.remove(beanName);singletonFactories.remove(beanName);}}protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory){if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);}}public void registerDisposableBean(String beanName, DisposableBean bean) {disposableBeans.put(beanName, bean);}public void destroySingletons() {Set<String> keySet = this.disposableBeans.keySet();String[] disposableBeanNames = keySet.toArray(new String[0]);for (int i = disposableBeanNames.length - 1; i >= 0; i--) {String beanName = disposableBeanNames[i];DisposableBean disposableBean = (DisposableBean) disposableBeans.remove(beanName);try {disposableBean.destroy();} catch (Exception e) {throw new BeansException("Destroy method on com.qingyun.springframework.aop.test.bean with name '" + beanName + "' threw an exception", e);}}}
}

在用于提供单例对象注册的操作的 DefaultSingletonBeanRegistry 类中,共有三个缓存对象的属性; singletonObjects 、 earlySingletonObjects 、 singletonFactories如它们的名字一样,用于存放不同类型的对象(单例对象、早期的半成品单例对象、单例工厂对象)。

紧接着在这三个缓存对象下提供了获取、添加和注册不同对象的方法,包括:getSingleton 、 registerSingleton、addSingletonFactory,其实后面这两个方法都比较简单,主要是getSingleton 的操作,它是在一层层处理不同时期的单例对象,直至拿到有效的对象。

提前暴露对象

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) {Object bean = null;try {//  调用构造方法实例化Beanbean = createBeanInstance(beanDefinition, beanName, args);// 处理循环依赖,将实例化后的Bean对象提前放入缓存中暴露出来if (beanDefinition.isSingleton()) {Object finalBean = bean;addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));}// 实例化后判断boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean);if (!continueWithPropertyPopulation) {return bean;}// 在设置 Bean 属性之前,允许 BeanPostProcessor 修改属性值applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);// 给 Bean 填充属性if (beanDefinition.getPropertyValues() != null &&beanDefinition.getPropertyValues().getPropertyValues().length != 0) {applyPropertyValues(beanName, bean, beanDefinition);}// 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法bean = initializeBean(beanName, bean, beanDefinition);} catch (Exception e) {throw new BeansException("Instantiation of com.qingyun.springframework.aop.test.bean failed", e);}// 注册实现了 DisposableBean 接口的 Bean 对象registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);// 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPEObject exposedObject = bean;if (beanDefinition.isSingleton()) {// 获取代理对象exposedObject = getSingleton(beanName, false);if (exposedObject == null) {exposedObject = bean;}registerSingleton(beanName, exposedObject);}return exposedObject;}//  此处有可能会提前进行动态代理完成AOP增强,但是也有可能返回原始对象protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) {Object exposedObject = bean;for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {exposedObject = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).getEarlyBeanReference(exposedObject, beanName);if (null == exposedObject) {return exposedObject;}}}return exposedObject;}
}

在 AbstractAutowireCapableBeanFactory#doCreateBean 的方法中主要是扩展了对象的提前暴露 addSingletonFactory 了,和单例对象的获取getSingleton 以及注册操作 registerSingleton 。

这里提到一点 getEarlyBeanReference 就是定义在如 AOP 切面中这样的代理对象,可以参考源码中接口InstantiationAwareBeanPostProcessor#getEarlyBeanReference 方法的实现。

项目代码Github地址:https://github.com/Zhang-Qing-Yun/mini-spring,本节代码对应的commit标识为7f8b6eb

欢迎标星

解鞍卸甲——手写简易版Spring框架(终):使用三级缓存解决循环依赖问题相关推荐

  1. 手写简易版Spring框架(七):定义标记类型接口Aware,实现感知容器相关的信息

    文末有惊喜 目标 目前已实现的 Spring 框架,在 Bean 操作上能提供出的能力,包括:Bean 对象的定义和注册,以及在操作 Bean 对象过程中执行的,BeanFactoryPostProc ...

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

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

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

    三级缓存解决循环依赖详解 一.什么是三级缓存 二.三级缓存详解 Bean实例化前 属性赋值/注入前 初始化后 总结 三.怎么解决的循环依赖 四.不用三级缓存不行吗 五.总结 一.什么是三级缓存 就是在 ...

  4. 手写Spring-第十六章-旋转吧雪月花!用三级缓存解决循环依赖

    前言 循环依赖,一直是一个令人头疼的问题.虽然我们一般情况下会尽量避免这种情况的发生,但很多时候它会在无意识的情况下出现.比如隔了好几个bean之后,发现循环起来了.那么什么是循环依赖呢?其实就是A依 ...

  5. 手写简易版web框架

    Web框架 Web应用框架(Web application framework)是一种开发框架,用来支持动态网站.网络应用程序及网络服务的开发.Web应用框架有助于减轻网页开发时共通性活动的工作负荷, ...

  6. 手写简易版链表及原理分析

    好多人都觉得为什么要自己写这样的数据结构,变成里面不是有吗?为什么要去写,有这个疑问,其实这个疑问这我的脑海中也存在了很长一段时间,本人是学习java编程的,直接看java的集合框架不行吗?这个时候如 ...

  7. 5 拦截器拦截请求路由_手写简易版axios拦截器,实现微信小程序wx.request的封装与拦截...

    前言: axios是一个功能强大的网络请求库,其中拦截器又是axios的精髓.在小程序的开发或者需要手动实现ajax的时候,没有实现对请求的拦截,开发的时候非常不方便,因此手写一个简易版的axios拦 ...

  8. 手写简易版 React 来彻底搞懂 fiber 架构

    React 16 之前和之后最大的区别就是 16 引入了 fiber,又基于 fiber 实现了 hooks.整天都提 fiber,那 fiber 到底是啥?它和 vdom 是什么关系? 与其看各种解 ...

  9. 手写简易版Vue源码之数据响应化的实现

    当前,Vue和React已成为两大炙手可热的前端框架,这两个框架都算是业内一些最佳实践的集合体.其中,Vue最大的亮点和特色就是数据响应化,而React的特点则是单向数据流与jsx. 笔者近期正在研究 ...

最新文章

  1. 中国年度AI省市格局:北广上稳居前三,江苏四川力压浙江,山西转型“挖数据”增速迅猛...
  2. 浅析Objective-C字面量
  3. 怎没用计算机算e的,小E教你们如何用计算机算虚数
  4. properties配置文件的加密
  5. 离线安装 Android 4.0 SDK
  6. win11还原点如何设置 windows11还原点的设置方法
  7. react系列之isMounted is an Antipattern
  8. 你是否需要购买网站重构?
  9. Linux kernel中网络设备的管理
  10. 【性能测试】性能测试中问题反思和心得
  11. 明御:APT攻击预警平台
  12. 迅雷下载Android Studio最新版本(Android Studio 2.1.2.0)
  13. 为什么WiFi自动信道选到的信道多数在1/6/11
  14. 2021年高处安装、维护、拆除新版试题及高处安装、维护、拆除考试试卷
  15. unity中Loding.UpdatePreloading占用CPU过高如何解决?
  16. Mybatis阶段常用单词
  17. matlab保存nii_Matlab实现NIfTI(ANALYZE)核磁共振图像读写
  18. 使用dockpanel动态添加picturebox并绑定图片
  19. 微笑识别(HOG+SVM+opencv+python)
  20. 计算机科学大师唐纳德,现代计算机科学的鼻祖

热门文章

  1. linux 之top命令详解
  2. 谷歌浏览器打不开12306
  3. Gitlab与Jaeger集成,实现Tracing链路追踪
  4. 手游大话藏宝阁找不到服务器,大话西游手游藏宝阁指定交易在哪里 藏宝阁怎么没有指定我...
  5. 特征工程-使用随机森林进行缺失值填补
  6. 【C++】STL容器之string使用(赋值、拼接、查找、替换、比较、截取、插入、删除、子串)
  7. java 代码审查_代码审查(Code Review)清单
  8. Impala入门学习与使用详解
  9. 中国交通运输发展白皮书
  10. 丢手帕问题 java_丢手帕问题java 实现