Spring为什么需要使用三级缓存?

有如下一个场景,X类中注入了Y类,Y类注入了X类,按照正常的类加载顺序,X类在注入Y的时候其实还没完全初始化完成,而注入的Y此时就开始实例化,发现也需要用到X,这是就去获取X,但是X并没有初始化完成,这个时候就会出现X在等Y初始化完成,而Y又在等X初始化完成,就进入了一个互相等待的情况。

Spring是如何避免这种情况的呢?那就是三级缓存

先来说说Spring有哪三级缓存。

  • 一级缓存 singletonObject : 单例对象集合;
  • 二级缓存 earlySingletonObjects :早期单例对象集合,也就是对象还没有完全初始化;
  • 三级缓存 singletonFactories :单例对象工厂集合,里面存储的是对象封装后的ObjectFactory。

Spring实现流程:

  1. X实例化完成;
  2. 检测到支持循环依赖,将X实例化的对象封装成ObjectFactory放入三级缓存中;
  3. populateBean渲染X对象,对属性Y进行注入;
  4. 获取Y对象,getSingleton返回null;
  5. 对Y进行实例化;
  6. 将Y实例化后的对象封装成ObjectFactory放入三级缓存;
  7. populateBean渲染Y对象,对属性X进行注入;
  8. 获取X对象,getSingleton返回之前三级缓存中存储的对象,并将对象放入二级缓存;
  9. 返回二级缓存中的X;
  10. Y初始化完成,返回Y;
  11. X初始化完成。

为什么一定要使用三级缓存(singletonFactories)?

可以思考下,如果只有两级缓存,在进行对象注入的时候,给属性注入的就是原始对象。但是,在AOP场景下,对象可能是一个代理对象,这个时候注入就应该是一个代理对象而不是原始对象。

三级缓存也就是这个原因存储的是一个ObjectFactory,而不是Object;

当循环依赖过程中,对象第二次进入getBean流程时就需要用到三级缓存中对象,就会调用getObject方法返回一个封装后的对象,而对象既可能是原始对象,也可能是代理对象,再将这个对象返回给需要注入的类。

getObject会对对象进行一系列的包装然后返回。Spring中利用lambda表达式的形式声明,具体实现如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {// 默认最终公开的对象是bean,通过createBeanInstance创建出来的普通对象Object exposedObject = bean;// mbd的systhetic属性:设置此bean定义是否是"synthetic",一般是指只有AOP相关的pointCut配置或者Advice配置才会将 synthetic设置为true// 如果mdb不是synthetic且此工厂拥有InstantiationAwareBeanPostProcessorif (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {// 遍历工厂内的所有后处理器for (BeanPostProcessor bp : getBeanPostProcessors()) {// 如果bp是SmartInstantiationAwareBeanPostProcessor实例if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;// 让exposedObject经过每个SmartInstantiationAwareBeanPostProcessor的包装exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}// 返回最终经过层次包装后的对象return exposedObject;
}

Spring调用注册的BeanPostProcessor的getEarlyBeanReference方法来对对象进行包装,最后返回经过包装的对象。

AnnotationAwareAspectJAutoProxyCreator处理器是Spring用来进行AOP处理的,继承了SmartInstantiationAwareBeanPostProcessor类,getEarlyBeanReference方法如下:

public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 如果已经处理过,直接返回if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 这里advisedBeans缓存了已经进行了代理的bean,如果缓存中存在,则可以直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 这里isInfrastructureClass()用于判断当前bean是否为Spring系统自带的bean,自带的bean是// 不用进行代理的;shouldSkip()则用于判断当前bean是否应该被略过if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {// 对当前bean进行缓存this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 获取符合对应类的Advisor,如果没有类没有符合的aop就会返回空集合Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);// 对当前bean的代理状态进行缓存// 生成对应的代理对象if (specificInterceptors != DO_NOT_PROXY) {// 对当前bean的代理状态进行缓存this.advisedBeans.put(cacheKey, Boolean.TRUE);// 根据获取到的Advices和Advisors为当前bean生成代理对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 缓存生成的代理bean的类型,并且返回生成的代理beanthis.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}// 正常无需代理的对象直接返回beanthis.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

wrapIfNecessary方法和普通AOP在初始化方法中执行postProcessAfterInitialization是一样的,会先获取到类对应的Advisor,然后根据策略生成对应的代理工厂和代理类,当没有Advisor时,也就不会进行代理处理。

当有AOP场景下的类具有循环依赖的情况时,就会将代理对象的生成提前到初始化之前,对象注入的时候。

PS : AOP处理器postProcessAfterInitialization实现

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {// 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型,// 前面还会添加&符号,如果beanName为空,则以当前bean对应的class为keyObject cacheKey = getCacheKey(bean.getClass(), beanName);// 判断当前bean是否正在被代理,如果正在被代理则不进行封装if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 如果它需要被代理,则需要封装指定的beanreturn wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

Spring为什么需要使用三级缓存?相关推荐

  1. Spring循环依赖和三级缓存详解

    Spring循环依赖和三级缓存详解 Spring在启动过程中,使用到了三个map,称为三级缓存 我们可以这样理解,假设,我们只有一个缓存容器,并且缓存是直接开放给用户可以调用的,如果将未完成赋值的Be ...

  2. Spring源码解析-三级缓存与循环依赖,nginx架构图

    两个流程理论上是互不影响的 protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @N ...

  3. Spring是如何利用“三级缓存“巧妙解决Bean的循环依赖问题

    前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对方,最终形成闭环.用一副经典的图示可以表示成这样(A.B.C都代表对象,虚线代表引用关系): 注意:其实可以N=1,也就 ...

  4. spring循环依赖和三级缓存

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

  5. spring 三级缓存_通过画图+视频把循环依赖、监听器等等spring源码讲明白了

    大家在阅读源码的时候有没有这种感觉:每次要看源码的时候十分信誓旦旦逼迫自己努力看着源码,但是还没看多长时间就会感觉枯燥,无味没意思,所以我是十分不愿意去看源码,但是今天福利来了,有位大神通过画图+视频 ...

  6. 面试官:连Spring三级缓存都答不好,自己走还是我送你?

    面试官:简历上写了精通Spring,那你回答一下Spring为什么用"三级缓存"去解决循环依赖? 我:.......应该有三个缓存的map结构 面试官:具体回答一下 我:平时没认真 ...

  7. Spring 的循环依赖:真的必须非要三级缓存吗?

    作者:青石路 www.cnblogs.com/youzhibing/p/14337244.html 写作背景 做 Java 开发的,一般都绕不开 Spring,那么面试中肯定会被问到 Spring 的 ...

  8. Spring的三级缓存

    Spring三级缓存 对象创建的过程 spring的三级缓存分别是 // 从上至下 分表代表这"三级缓存"private final Map<String, Object&g ...

  9. 别盲从了,spring 解决循环依赖真的一定需要三级缓存吗?demo结合源码讲解三级缓存的真正目的,一级缓存singletonFactories的真正作用,看到文章最后让面试官眼前一亮

    背景 本篇是我上一篇<3分钟秒懂,最简单通俗易懂的spring bean 生命周期介绍与源码分析,附上demo完整源码>姊妹篇 spring 三级缓存问题是面试中的热点问题,大部分回答者会 ...

最新文章

  1. python下载的文件放在哪里的-python实现文件下载的方法总结
  2. 竹笋炒肉 I18N和L10N
  3. jsp内置对象【02】四种内置对象【02】session、application
  4. 一个简单的pygame接金币游戏
  5. linux iptables
  6. 【Deep Learning 一】课程一(Neural Networks and Deep Learning),第一周(Introduction to Deep Learning)答案
  7. Hyperledger Fabric 节点类型Commiter、Endorser、Leader、Anchor
  8. 小黑笔记:transe模型
  9. PHP计算字符串长度
  10. 计算机软考网络工程师中级多少分过,2019年计算机软考网络工程师中级及格分数...
  11. 关于jQuery通知插件toastr的使用
  12. ThreadLocal 源码之 expungeStaleEntry
  13. 5000元档投影仪挑选指南,当贝F3与极米H3两款旗舰级投影到底怎么选?
  14. NetCDF简介与格式入门
  15. Java实现神经网络方法
  16. 全国英语计算机等级考试报名费,通知 | 全国大学生英语竞赛计算机等级考试报名...
  17. 【Python】位运算(按位与 、按位或 |、左移位运算符 <<(相当于乘以2)、右移位运算符 >>(相当于除以2))
  18. BFE原生路由转发功能分析
  19. 穹顶灯打不出阴暗面_微服务的阴暗面,解释
  20. vscode中配置settting.json

热门文章

  1. c语言d1和f1的区别,讲点真话:当贝d1和f1区别有没有?测评哪个好?谁来分享使用心得...
  2. java-php-python-ssm记事网页计算机毕业设计
  3. 技能篇:开发必备linux命令大全-稳赚不亏
  4. unity可以用中文了?代码也可以中文?
  5. java 接受传感器的数据_java中调用第三方接口获取数据的方式
  6. QDateTime 转QString格式
  7. 模块化多电平变换器载波移相与电容电压平衡控制MATLAB仿真
  8. mysql 家谱树查询_族谱树算法
  9. 2021年Java工程师飞升成神之路
  10. 4、5线小县城的年轻人每天在玩什么?