Spring如何通过三级缓存解决循环依赖
什么是循环依赖
什么是循环依赖?比如如下一个例子
public class A{
private B b;
}
public class B{
private A a;
}
这个例子存在的问题:理论上spring创建A的时候依赖了B,然后spring就会去加载B,但是这个时候B又依赖了A,spring又去加载A,就会陷入一个死循环,但我们在实际使用spring的时候并没有出现这样的循环,这是因为spring设计之初就考虑了这个问题,那么spring是如何解决的呢?我们先要明确的是spring对循环依赖的处理有三种情况,分别为
- 构造器的循环依赖:这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常。
- 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
- 非单例循环依赖:无法处理。Spring如何通过三级缓存解决循环依赖Spring中有三级缓存,分别如下
- singletonObjects:完成初始化的单例对象的cache(一级缓存)
- earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)
- singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)
Spring获取一个Bean的流程就说从一级到三级依次去寻找这个Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
//isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
//allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//从singletonFactories中移除,并放入earlySingletonObjects中。
//其实也就是从三级缓存移动到了二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
如果一二三级均没有这个Bean,就会走新建方法,Spring创建一个Bean的时候也会有3个步骤,如下
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
- initializeBean:调用spring xml中的init 方法。
我们再来说上面的循环依赖的例子:A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行第二步填充属性,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象,B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。
为什么构造器循环依赖和多例循环依赖Spring无法解决
构造器循环依赖
this .singletonsCurrentlylnCreation.add(beanName)将当前正要创建的bean 记录在缓存中 Spring 容器将每一个正在创建的bean 标识符放在一个“当前创建bean 池”中, bean 标识 柏:在创建过程中将一直保持在这个池中,因此如果在创建bean 过程中发现自己已经在“当前 创建bean 池” 里时,将抛出BeanCurrentlylnCreationException 异常表示循环依赖;而对于创建 完毕的bean 将从“ 当前创建bean 池”中清除掉。
多例循环依赖
对于“prototype”作用域bean, Spring 容器无法完成依赖注入,因为Spring 容器不进行缓 存“prototype”作用域的bean ,因此无法提前暴露一个创建中的bean 。
为什么不能只用一二级缓存来解决循环依赖?
这里面涉及到的问题很复杂,可以看这篇文章,我怕文章被删了,所以留了下图。
总结起来就是几点:一级缓存是放完全初始化好的对象的,如果只需要IOC功能其实一级缓存就能解决。但涉及到循环依赖,我们就需要暴露出一个没有初始化好的对象,那么我们不能把初始化好的和没好的都放到一级缓存里面吧?那不是乱套了,所以这个时候就需要引入二级缓存,把初始化好的放到一级缓存里面去,没好的放到二级缓存里面去。看起来可以完美解决问题了,但如果有代理对象的话,实际流程就会变成下面这样。我们假设上诉例子中A为代理对象
- A初始化的时候把能够生成A代理对象的一个lambda表达式放到三级缓存中
- A发现自己依赖B对象就去生成B对象,B对象发现自己需要A对象,就会去三级缓存中把这个lambda表达式取出来并且生成了A对象的代理对象,然后把代理对象放入到二级缓存,同时赋值B对象中的A,到此B对象构建完毕并放入一级缓存中。
- A对象使用构建好的B对象构建自己,A对象构建完毕。
你可能会有点疑问,为什么要把一个构建A代理对象的工厂放入到三级缓存中?为什么不能直接把已经生成好的代理对象放到二级缓存呢?这其实是出于性能的考虑,因为如果没有用到循环依赖的话,如果我们一开始就把生成好的代理对象放到二级缓存里面去,是不是有点影响性能呢?
详细的看下面
- Ovs+Dpdk简单实践
- Spark入门,概述,部署,以及学习(Spark是一种快速、通用、可扩展的大数据分析引擎)
- 创建基于MailKit和MimeKit的.NET基础邮件服务
- 把一个矩阵行优先展成一个向量,numpy.ravel() vs numpy.flatten()区别
- dataframe插入数据报错SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a
- python 对矩阵进行复制操作 np.repeat 与 np.tile区别
- python标准异常:中英文对比
- 激活windows10转到电脑设置的水印消失3种方法总结
- Android 运行时权限及APP适配
- python如何保存矩阵,保存matrix,保存numpy.ndarray
- SDP(12): MongoDB-Engine - Streaming
- .NET Core开源API网关 – Ocelot中文文档
- Selenium的使用方法简介
- 爬虫代理哪家强?十大付费代理详细对比评测出炉!
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释