文章阅读前推荐

推荐先去看看源码,源码很短,但是对于我们在脑子里构建一个完整思路很重要。看起来非常简单,只需要双击shift,全局查找文件:AbstractAutowireCapableBeanFactory,找到550行左右的doCreateBean方法,重点看一下580行到600行这20行代码就行,包含了三级缓存、属性注入、初始化,精华都在这20行,实在没条件的可以直接看文末附带的doCreateBean方法源码

Spring可以自动解决的循环依赖

public class AService {@Autowiredprivate BService bService;
}public class BService {@Autowiredprivate AService aService;
}

Spring无法自动解决构造器的循环依赖

public class DService {public DService(CService cService) {...}
}public class CService {public CService(DService dService) {...}
}

源码中关于三级缓存的定义

// 一级缓存
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);

所以说,其实三级缓存就是三个Map而已

三级缓存有什么区别?

一级缓存

一级缓存里存的是成品对象,实例化和初始化都完成了,我们的应用中使用的对象就是一级缓存中的

二级缓存

二级缓存中存的是半成品,没有完成属性注入和初始化,用来解决对象创建过程中的循环依赖问题
早期暴露出去的Bean,其实也就是解决循环依赖的Bean。早期的意思就是没有完完全全创建好,但是由于有循环依赖,就需要把这种Bean提前暴露出去。其实 早期暴露出去的Bean 跟 完完全全创建好的Bean 他们是同一个对象,只不过早期Bean里面的注解可能还没处理,完完全全的Bean已经处理了完了,但是他们指的还是同一个对象,只不过它们是在Bean创建过程中处于的不同状态

三级缓存

三级缓存中存的是 ObjectFactory<?> 类型的代理工厂对象,用于处理存在 AOP 时的循环依赖问题
存的是每个Bean对应的ObjectFactory对象,通过调用这个对象的getObject方法,就可以获取到早期暴露出去的Bean。
注意:这里有个很重要的细节就是三级缓存只会对单例的Bean生效,像多例的是无法利用到三级缓存的,通过三级缓存所在的类名DefaultSingletonBeanRegistry就可以看出,仅仅是对SingletonBean也就是单例Bean有效果。

发生循环依赖时的执行流程(精华必读)

正常不存在循环依赖的A、B对象是依次创建的,但是如果存在循环依赖的话,创建A的过程中,会顺便把B也创建了。注意,每次获取bean对象都会先去一级缓存看有没有值。
具体流程是:
1、遍历待创建的所有beanName,第一次遍历,开始获取A,此时缓存中没有,会开始正常创建流程
2、A初始创建完成,然后判断A是否是单例,且没有创建完毕,如果是,那么就会把A的beanFactory存入三级缓存
3、A开始处理@Autowired注解,开始注入B属性,于是尝试从缓存获取B,获取不到,则开始正常创建B的流程
4、B初始创建完成,同样判断B是否是单例,且没有创建完毕,如果是,那么就会把B的beanFactory存入三级缓存
5、B开始处理@Autowired注解,开始注入A属性,于是依次从一级缓存、二级缓存查找A属性,都没有就尝试从三级缓存获取A的beanFactory,通过beanFactory.getObject()方法获取A属性,接下来把A存入二级缓存,清除三级缓存。因为此时能获取到A,所以B的A属性能填充成功,B接着执行初始化,B处于实例化、初始化都完成的完全状态
6、B执行addSington(),把完全状态的B存入一级缓存,清空二三级缓存(实际只有三级有值)
7、A继续开始填充B属性,于是调用beanFactory.getBean()获取B,第六步已经把B存入一级缓存,此时直接返回,填充成功,继续执行初始化,得到一个完全状态的A
8、A执行addSington(),把完全状态的A存入一级缓存,清空二三级缓存(实际只有二级有值)
9、第二次遍历,开始获取B,此时一级缓存中有B,直接返回。
至此A、B全部实例化、初始化完成

疑惑解答

问题一:步骤5中,为什么要把B放入二级缓存?
答:主要是怕还有其他的循环依赖,如果还有的话,直接从二级缓存中就能拿到早期的AService对象

问题二:步骤6中,为什么要清空二三级缓存?
答:因为后续其他bean中也需要注入B时,会按顺序从一级缓存直到三级缓存查找,一级缓存有了,二三级缓存中的就不需要了,节省空间

问题三:文章开头提到,构造器造成的循环依赖三级缓存解决不了,为什么?
答:因为构造器循环依赖是发生在bean实例化阶段,此时连早期对象都还没创建出来,拿什么放到三级缓存。三级缓存只能是在bean实例化之后,才能起到作用

问题四:不用三级缓存,只用二级缓存能不能解决循环依赖?
答:不能,因为通过ObjectFactory获取的Bean可能是两种类型,第一种就是实例化阶段创建出来的对象,还是一种就是实例化阶段创建出来的对象的代理对象。至于是不是代理对象,取决于你的配置,如果添加了事务注解又或是自定义AOP切面,那就需要代理。假设舍弃第三级缓存,也就是没有ObjectFactory,那么就需要往第二缓存放入早期的Bean,那么是放没有代理的Bean还是被代理的Bean呢,这是在后面的属性注入阶段,处理注解的时候才能分辨的?
1)如果直接往二级缓存添加没有被代理的Bean,那么可能注入给其它对象的Bean跟最后最后完全生成的Bean是不一样的,因为最后生成的是代理对象,这肯定是不允许的;
2)那么如果直接往二级缓存添加一个代理Bean呢?
● 假设没有循环依赖,提前暴露了代理对象,那么如果跟最后创建好的不一样,那么项目启动就会报错,
● 假设没有循环依赖,使用了ObjectFactory,那么就不会提前暴露了代理对象,到最后生成的对象是什么就是什么,就不会报错,
● 如果有循环依赖,不论怎样都会提前暴露代理对象,那么如果跟最后创建好的不一样,那么项目启动就会报错
通过上面分析,如果没有循环依赖,使用ObjectFactory,就减少了提前暴露代理对象的可能性,从而减少报错的可能。

问题五:如果把二级缓存去掉,只留下一级、三级缓存呢?
答:假设舍弃第二级缓存,也就是没有存放早期的Bean的缓存,其实肯定也不行。上面说过,ObjectFactory其实获取的对象可能是代理的对象,那么如果每次都通过ObjectFactory获取代理对象,那么每次都重新创建一个代理对象,这肯定也是不允许的。

doCreateBean()方法源码(带注释)

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}//1、通过BeanDefinition实例化对象if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}final Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// Allow post-processors to modify the merged bean definition.synchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Post-processing of merged bean definition failed", ex);}mbd.postProcessed = true;}}// Eagerly cache singletons to be able to resolve circular references// even when triggered by lifecycle interfaces like BeanFactoryAware.//对象是否单例、是否未创建完成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));}// Initialize the bean instance.Object exposedObject = bean;try {//属性注入(在这里解析@Autowired注解时,触发循环依赖)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);}}if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" +StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +"] in its raw version as part of a circular reference, but has eventually been " +"wrapped. This means that said other beans do not use the final version of the " +"bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);}catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);}return exposedObject;}

从缓存中获取Bean的源码

protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从一级缓存中获取Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 如果一级缓存里没有 且 bean正在创建中synchronized (this.singletonObjects) {// 从二级缓存里获取singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {// 二级缓存没有 从三级缓存获取一个工厂ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 能获取到工厂 则创建beansingletonObject = singletonFactory.getObject();// 把实例存入二级缓存this.earlySingletonObjects.put(beanName, singletonObject);// 把工厂从三级缓存移除this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

–我是“三七有脾气”,一个在互联网"苟且偷生"的Java程序员
“如果感觉博客对你有用,麻烦给个点赞、评论、收藏,谢谢”

Spring使用三级缓存解决循环依赖?终于完全弄明白了相关推荐

  1. 为什么Spring需要三级缓存解决循环依赖,而不是二级缓存?

    来源:https://www.cnblogs.com/semi-sub/p/13548479.html 在使用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring已经帮我们去解决 ...

  2. Spring的三级缓存解决循环依赖

    一.什么是Spring三级缓存 第一级缓存:也叫单例池,存放已经经历了完整生命周期的Bean对象. 第二级缓存:存放早期暴露出来的Bean对象,实例化以后,就把对象放到这个Map中.(Bean可能只经 ...

  3. 京东一面:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?我懵了。。...

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:cnblogs.com/semi-sub/p/13548479.html 前言 bean生命周期 三级缓存解决循环依赖 总结 ...

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

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

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

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

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

    什么是三级缓存 按照目前我们实现的 Spring 框架,是可以满足一个基本需求的,但如果你配置了A.B两个Bean对象互相依赖,那么立马会抛出 java.lang.StackOverflowError ...

  7. Spring三级缓存解决循环依赖

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

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

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

  9. spring无法用三级缓存解决循环依赖的问题分析

    spring无法解决构造器的循环依赖,对上述例子稍微进行改动: @Component("b") public class B {private A a;public B(A a) ...

最新文章

  1. SSL加密包解析的几个概念梳理
  2. 玩转MaxCompute studio SQL编辑器
  3. C语言中函数可变参数解析
  4. linux文件目录的管理,Linux文件目录管理
  5. LeetCode 面试题 03 数组中重复的数字
  6. Qt中标绘功能的实现方法对比
  7. 海信集团:通过数据来驱动企业的管理,让数据真正成为生产力
  8. 微信小程序单选框radio使用实例
  9. latex中文模板_都8012年了还不用LaTex编辑论文就out了!!
  10. 【Java程序设计】运算符与优先级
  11. 16.6 假新闻识别 Fake News Detection on Social Media A Data Mining Perspective
  12. Mac键盘突然停止响应如何处理
  13. 罗振宇2021跨年演讲6:山村小学的豆腐课到底在玩啥?
  14. 2013年计算机运算速度慢,win7电脑运行速度很慢怎么提速|三个win7提速的技巧
  15. html thead作用,html元素thead标签的使用方法及作用
  16. ztree在vue中的使用 使用封装好的vue-giant-tree
  17. 手机中的计算摄影-超广角畸变校正
  18. 《非常网管:网络管理从入门到精通(修订版)》——第1章 网络基础知识回顾1.1 计算机网络基础...
  19. 近况记录丨脑子错乱!
  20. 一个表情包引发的悬案!

热门文章

  1. 计算机系高考激励的句子,高考激励的话
  2. 一代神机华为MateRS专为保时捷跑车70周年量身定制
  3. 对象创建等2019/2/26
  4. 音乐欣赏课程笔记(二)
  5. 如何用python 获取xiaohongshu的图片、文章和视频
  6. 小红书图片笔记去水印源码
  7. 招财宝+保本基金的稳健高杠杆玩法
  8. Visio保存为透明的图片
  9. 《调色师手册:电影和视频调色专业技法(第2版)》——挑选监视器
  10. 《青山翠影》壹 艰难的抉择 | 福兮祸兮