网易首页 > 网易号 > 正文 申请入驻

Spring循环依赖三级缓存是否可以减少为二级缓存?

0
分享至

  - 前言 -

  提问:

  我们都知道Spring通过三级缓存来解决循环依赖的问题,那么是不是必须是三级缓存?二级缓存不能解决吗?

  要分析是否能够去掉其中一级缓存,我们需要先过一遍Spring是如何通过三级缓存来解决循环依赖的。

  - 循环依赖 -

  所谓的循环依赖,就是两个或者两个以上的bean互相依赖对方,最终形成闭环

  比如“A对象依赖B对象,而B对象也依赖A对象”,或者“A对象依赖B对象,B对象依赖C对象,C对象依赖A对象”;类似以下代码:

  public class A {
private B b;

  public class B {
private A a;
}

  常规情况下,会出现以下情况:

  1、通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。

  2、A对象需要注入B对象,发现对象池(缓存)里还没有B对象(对象在创建并且注入属性和初始化完成之后,会放入对象缓存里)。

  3、通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。

  4、B对象需要注入A对象,发现对象池里还没有A对象。

  5、创建A对象,循环以上步骤。

  - 三级缓存 -

  Spring解决循环依赖的核心思想在于提前曝光:

  1、通过构建函数创建A对象(A对象是半成品,还没注入属性和调用init方法)。

  2、A对象需要注入B对象,发现缓存里还没有B对象,将半成品对象A放入半成品缓存。

  3、通过构建函数创建B对象(B对象是半成品,还没注入属性和调用init方法)。

  4、B对象需要注入A对象,从半成品缓存里取到半成品对象A。

  5、B对象继续注入其他属性和初始化,之后将完成品B对象放入完成品缓存。

  6、A对象继续注入属性,从完成品缓存中取到完成品B对象并注入。

  7、A对象继续注入其他属性和初始化,之后将完成品A对象放入完成品缓存。

  其中缓存有三级:

  /** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new ConcurrentHashMap<>(256);

  /** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new HashMap<>(16);

  /** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletonFactories = new HashMap<>(16);

  左右滑动查看完整代码

  要了解原理,最好的方法就是阅读源码,从创建Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手。

  一. 在构造Bean对象之后,将对象提前曝光到缓存中,这时候曝光的对象仅仅是构造完成,还没注入属性和初始化。

  public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
……
// 是否提前曝光
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
……
}
}
左右滑动查看完整代码

  二. 提前曝光的对象被放入Map> singletonFactories缓存中,这里并不是直接将Bean放入缓存,而是包装成ObjectFactory对象再放入。

  public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 一级缓存
if (!this.singletonObjects.containsKey(beanName)) {
// 三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 二级缓存
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
}
public interface ObjectFactory {
T getObject() throws BeansException;
}
左右滑动查看完整代码

  三. 为什么要包装一层ObjectFactory对象?

  如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:

  1、不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。

  2、不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按照Spring设计原则的步骤来创建。

  Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?

  
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));:

  public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {

  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;
}
}

  左右滑动查看完整代码

  为了防止对象在后面的初始化(init)时重复代理,在创建代理时,earlyProxyReferences缓存会记录已代理的对象。

  public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
private final Map earlyProxyReferences = new ConcurrentHashMap<>(16);

  @Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
左右滑动查看完整代码

  四. 注入属性和初始化

  提前曝光之后:

  1、通过populateBean方法注入属性,在注入其他Bean对象时,会先去缓存里取,如果缓存没有,就创建该对象并注入。

  2、通过initializeBean方法初始化对象,包含创建代理。

  public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
……
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
……
}
}
// 获取要注入的对象
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 三级缓存
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}
左右滑动查看完整代码

  五. 放入已完成创建的单例缓存

  在经历了以下步骤之后,最终通过addSingleton方法将最终生成的可用的Bean放入到单例缓存里。

  1、

  AbstractBeanFactory.doGetBean ->

  2、

  DefaultSingletonBeanRegistry.getSingleton ->

  3、

  AbstractAutowireCapableBeanFactory.createBean ->

  4、

  AbstractAutowireCapableBeanFactory.doCreateBean ->

  5、

  DefaultSingletonBeanRegistry.addSingleton

  
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

  /** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new ConcurrentHashMap<>(256);

  /** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletonFactories = new HashMap<>(16);

  /** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new HashMap<>(16);

  protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

  左右滑动查看完整代码

  - 二级缓存 -

  上面第三步《为什么要包装一层ObjectFactory对象?》里讲到有两种选择:

  1、不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。

  2、不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按照Spring设计原则的步骤来创建。

  Sping选择了第二种,如果是第一种,就会有以下不同的处理逻辑:

  1、在提前曝光半成品时,直接执行getEarlyBeanReference创建到代理,并放入到缓存earlySingletonObjects中。

  2、有了上一步,那就不需要通过ObjectFactory来延迟执行getEarlyBeanReference,也就不需要singletonFactories这一级缓存。

  这种处理方式可行吗?

  
这里做个试验,对AbstractAutowireCapableBeanFactory做个小改造,在放入三级缓存之后立刻取出并放入二级缓存,这样三级缓存的作用就完全被忽略掉,就相当于只有二级缓存。

  public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 是否提前曝光
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 立刻从三级缓存取出放入二级缓存
getSingleton(beanName, true);
左右滑动查看完整代码

  测试结果是可以的,并且从源码上分析可以得出两种方式性能是一样的,并不会影响到Sping启动速度。

  那为什么Sping不选择二级缓存方式,而是要额外加一层缓存?

  
如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。

  Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。

  如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

  转自:思否segmentfault

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
霸凌式“教育专家”赵菊英账号被封

霸凌式“教育专家”赵菊英账号被封

新快报新闻
2024-06-17 23:52:09
桑尼:我爷爷的爷爷是中国人,所以我懂一点中文

桑尼:我爷爷的爷爷是中国人,所以我懂一点中文

直播吧
2024-06-17 22:10:27
湖北一学霸放弃清华的保送,说要享受高考过程,后来他考多少分?

湖北一学霸放弃清华的保送,说要享受高考过程,后来他考多少分?

悦悦侃历史
2024-06-17 15:06:08
今晚明晨!CCTV5直播2场欧洲杯,C罗+B费领衔,葡萄牙男足VS捷克

今晚明晨!CCTV5直播2场欧洲杯,C罗+B费领衔,葡萄牙男足VS捷克

晚池
2024-06-18 05:33:54
冷藏货车遇难者家属:母亲为补贴家用务工,并非为“省车费”搭车

冷藏货车遇难者家属:母亲为补贴家用务工,并非为“省车费”搭车

大众日报
2024-06-18 10:26:10
塔图姆谈杰伦-布朗FMVP:为他感到高兴 这是他应得的!

塔图姆谈杰伦-布朗FMVP:为他感到高兴 这是他应得的!

直播吧
2024-06-18 12:24:10
黄光裕现身国美超市,55岁大肚腩明显但意气风发,欲东山再起

黄光裕现身国美超市,55岁大肚腩明显但意气风发,欲东山再起

柠檬有娱乐
2024-06-18 09:37:56
时隔9个月,黄仁勋再次套现英伟达,逢高卖超2亿元;标普500创今年第30次收盘历史新高,纳指实现连续六个交易日续刷收盘新高;美联储哈克:今年降息一次是合适的

时隔9个月,黄仁勋再次套现英伟达,逢高卖超2亿元;标普500创今年第30次收盘历史新高,纳指实现连续六个交易日续刷收盘新高;美联储哈克:今年降息一次是合适的

每日经济新闻
2024-06-18 10:21:27
官宣退出,姆巴佩摊牌发声,官宣决定,法国队遗憾,齐达内祝福

官宣退出,姆巴佩摊牌发声,官宣决定,法国队遗憾,齐达内祝福

东球弟
2024-06-17 09:28:20
女儿中考亲妈后妈共同来接!网友:看了颜值后,丈夫才是人生赢家

女儿中考亲妈后妈共同来接!网友:看了颜值后,丈夫才是人生赢家

垛垛糖
2024-06-18 12:47:49
姆巴佩暂不接受手术,在线向网友征集面具想法,评论区被忍者神龟面具照刷屏

姆巴佩暂不接受手术,在线向网友征集面具想法,评论区被忍者神龟面具照刷屏

鲁中晨报
2024-06-18 10:23:05
姜萍手写证明过程被批错漏太多极不规范!网友:造假嫌疑很大!

姜萍手写证明过程被批错漏太多极不规范!网友:造假嫌疑很大!

远荐
2024-06-18 12:30:37
朱婷吴梦洁缺席,张常宁当队长,袁志顶替蔡斌,中国队替补战日本

朱婷吴梦洁缺席,张常宁当队长,袁志顶替蔡斌,中国队替补战日本

跑者排球视角
2024-06-18 14:55:37
法国足协主席确认:姆巴佩不会接受鼻子手术

法国足协主席确认:姆巴佩不会接受鼻子手术

直播吧
2024-06-18 07:36:14
0618晚评:主力或已布局!明天A股将大幅波动

0618晚评:主力或已布局!明天A股将大幅波动

孤烟财经
2024-06-18 16:34:49
玫瑰的故事:方协文被母亲戴绿帽子!黄亦玫离婚带走女儿大快人心

玫瑰的故事:方协文被母亲戴绿帽子!黄亦玫离婚带走女儿大快人心

露珠聊影视
2024-06-18 13:18:57
C罗:曾经最仰赖的超级身体正无情背叛他

C罗:曾经最仰赖的超级身体正无情背叛他

林子说事
2024-06-18 07:30:03
大帝把国防部最高层全换成搞经济的,你觉得他还要打吗?

大帝把国防部最高层全换成搞经济的,你觉得他还要打吗?

邵旭峰域
2024-06-18 14:20:02
19000吨钢筋“从天而降”,大桥剪彩前垮掉,75人当场身亡

19000吨钢筋“从天而降”,大桥剪彩前垮掉,75人当场身亡

星辰故事屋
2024-06-18 10:54:41
美双航母赶到菲律宾,中方3艘055离港消失,南海斗争准备摊牌了?

美双航母赶到菲律宾,中方3艘055离港消失,南海斗争准备摊牌了?

战域笔墨
2024-06-14 22:43:38
2024-06-18 17:18:44
Meta
Meta
关注java进阶架构师送架构
1044文章数 9858关注度
往期回顾 全部

科技要闻

6·18的穷途:电商已不知为何而战

头条要闻

工程师写"收获"日记:曾1年收红包339次 收钱有时令价

头条要闻

工程师写"收获"日记:曾1年收红包339次 收钱有时令价

体育要闻

对于凯尔特人来说 谁是MVP根本不重要

娱乐要闻

被曝新恋情,张碧晨王琳凯发声辟谣

财经要闻

深圳一学区房从每平米14万跌到4万

汽车要闻

全球最低价 现代IONIQ 5N预售价39.88万

态度原创

艺术
本地
房产
时尚
公开课

艺术要闻

穿越时空的艺术:《马可·波罗》AI沉浸影片探索人类文明

本地新闻

我和我的家乡|在鞍山的每一步都有新发现

房产要闻

净吸纳量连续两年TOP3!这座海口顶级写字楼,用实力上大分!

成熟女人的穿搭法则,高级感很重要,打造出精致感的LOOK

公开课

近视只是视力差?小心并发症

无障碍浏览 进入关怀版