Spring为什么需要使用三级缓存?
Spring为什么需要使用三级缓存?
有如下一个场景,X类中注入了Y类,Y类注入了X类,按照正常的类加载顺序,X类在注入Y的时候其实还没完全初始化完成,而注入的Y此时就开始实例化,发现也需要用到X,这是就去获取X,但是X并没有初始化完成,这个时候就会出现X在等Y初始化完成,而Y又在等X初始化完成,就进入了一个互相等待的情况。
Spring是如何避免这种情况的呢?那就是三级缓存。
先来说说Spring有哪三级缓存。
- 一级缓存 singletonObject : 单例对象集合;
- 二级缓存 earlySingletonObjects :早期单例对象集合,也就是对象还没有完全初始化;
- 三级缓存 singletonFactories :单例对象工厂集合,里面存储的是对象封装后的ObjectFactory。
Spring实现流程:
- X实例化完成;
- 检测到支持循环依赖,将X实例化的对象封装成ObjectFactory放入三级缓存中;
- populateBean渲染X对象,对属性Y进行注入;
- 获取Y对象,getSingleton返回null;
- 对Y进行实例化;
- 将Y实例化后的对象封装成ObjectFactory放入三级缓存;
- populateBean渲染Y对象,对属性X进行注入;
- 获取X对象,getSingleton返回之前三级缓存中存储的对象,并将对象放入二级缓存;
- 返回二级缓存中的X;
- Y初始化完成,返回Y;
- 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为什么需要使用三级缓存?相关推荐
- Spring循环依赖和三级缓存详解
Spring循环依赖和三级缓存详解 Spring在启动过程中,使用到了三个map,称为三级缓存 我们可以这样理解,假设,我们只有一个缓存容器,并且缓存是直接开放给用户可以调用的,如果将未完成赋值的Be ...
- Spring源码解析-三级缓存与循环依赖,nginx架构图
两个流程理论上是互不影响的 protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @N ...
- Spring是如何利用“三级缓存“巧妙解决Bean的循环依赖问题
前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对方,最终形成闭环.用一副经典的图示可以表示成这样(A.B.C都代表对象,虚线代表引用关系): 注意:其实可以N=1,也就 ...
- spring循环依赖和三级缓存
前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为nu ...
- spring 三级缓存_通过画图+视频把循环依赖、监听器等等spring源码讲明白了
大家在阅读源码的时候有没有这种感觉:每次要看源码的时候十分信誓旦旦逼迫自己努力看着源码,但是还没看多长时间就会感觉枯燥,无味没意思,所以我是十分不愿意去看源码,但是今天福利来了,有位大神通过画图+视频 ...
- 面试官:连Spring三级缓存都答不好,自己走还是我送你?
面试官:简历上写了精通Spring,那你回答一下Spring为什么用"三级缓存"去解决循环依赖? 我:.......应该有三个缓存的map结构 面试官:具体回答一下 我:平时没认真 ...
- Spring 的循环依赖:真的必须非要三级缓存吗?
作者:青石路 www.cnblogs.com/youzhibing/p/14337244.html 写作背景 做 Java 开发的,一般都绕不开 Spring,那么面试中肯定会被问到 Spring 的 ...
- Spring的三级缓存
Spring三级缓存 对象创建的过程 spring的三级缓存分别是 // 从上至下 分表代表这"三级缓存"private final Map<String, Object&g ...
- 别盲从了,spring 解决循环依赖真的一定需要三级缓存吗?demo结合源码讲解三级缓存的真正目的,一级缓存singletonFactories的真正作用,看到文章最后让面试官眼前一亮
背景 本篇是我上一篇<3分钟秒懂,最简单通俗易懂的spring bean 生命周期介绍与源码分析,附上demo完整源码>姊妹篇 spring 三级缓存问题是面试中的热点问题,大部分回答者会 ...
最新文章
- python下载的文件放在哪里的-python实现文件下载的方法总结
- 竹笋炒肉 I18N和L10N
- jsp内置对象【02】四种内置对象【02】session、application
- 一个简单的pygame接金币游戏
- linux iptables
- 【Deep Learning 一】课程一(Neural Networks and Deep Learning),第一周(Introduction to Deep Learning)答案
- Hyperledger Fabric 节点类型Commiter、Endorser、Leader、Anchor
- 小黑笔记:transe模型
- PHP计算字符串长度
- 计算机软考网络工程师中级多少分过,2019年计算机软考网络工程师中级及格分数...
- 关于jQuery通知插件toastr的使用
- ThreadLocal 源码之 expungeStaleEntry
- 5000元档投影仪挑选指南,当贝F3与极米H3两款旗舰级投影到底怎么选?
- NetCDF简介与格式入门
- Java实现神经网络方法
- 全国英语计算机等级考试报名费,通知 | 全国大学生英语竞赛计算机等级考试报名...
- 【Python】位运算(按位与 、按位或 |、左移位运算符 <<(相当于乘以2)、右移位运算符 >>(相当于除以2))
- BFE原生路由转发功能分析
- 穹顶灯打不出阴暗面_微服务的阴暗面,解释
- vscode中配置settting.json
热门文章
- c语言d1和f1的区别,讲点真话:当贝d1和f1区别有没有?测评哪个好?谁来分享使用心得...
- java-php-python-ssm记事网页计算机毕业设计
- 技能篇:开发必备linux命令大全-稳赚不亏
- unity可以用中文了?代码也可以中文?
- java 接受传感器的数据_java中调用第三方接口获取数据的方式
- QDateTime 转QString格式
- 模块化多电平变换器载波移相与电容电压平衡控制MATLAB仿真
- mysql 家谱树查询_族谱树算法
- 2021年Java工程师飞升成神之路
- 4、5线小县城的年轻人每天在玩什么?