什么叫循环依赖:简单来说,在spring中对象的创建管理都是IOC容器帮我们做的,以默认单例的方式帮我们创建bean,但是会出现一个问题:对象A里面有个属性是B对象,B对象里面又有一个A对象。如下
class A {
private B b;
}
class B{
private A a;
}
不管先创建哪个对象,在属性赋值时都要需要另一个对象。假设创建A对象,需要属性赋值的时候,发现需要B对象,去创建B对象的时候,发现需要A对象,此时A对象还未创建完,这样就形成了一个简单的循环依赖问题。
流程图:
文章图片
此时容器中当然没有A和B对象,可以发现此时已经形成了一个闭环,也就是循环依赖。解决的关键在于:下图的最后一步没找到
文章图片
此时若能解决此处的问题,闭环将被打破。关键点在于此时A对象已经实例化了,但还未完成初始化(半成品对象)。
文章图片
这时候,请思考一个问题,如果我们持有一个对象的引用(C中的指针),能否在后续的步骤中给这个对象进行赋值操作?
当然是可以的,在这个问题的基础上,我们可以引入一个东西,来解决循环依赖,就是大家所熟悉的缓存。稍稍加点改动,只需要我们实例化后将对象放入缓存,从容器中查找时,也查找缓存就可以解决循环依赖。此时B已经是成品对象了并且在缓存中,然后A对象就可以完成初始化了。
文章图片
当然成品对象和半成品对象放在一起怎么看都不合适,所以spring中解决循环依赖是:三级缓存和提前暴露。其实最核心的是实例化和初始化分开进行。
spring三级缓存源码:
//一级缓存,保存beanName和创建Bean直接实例的关系(保存成熟对象)
private final Map singletonObjects = new ConcurrentHashMap(256);
//三级缓存,保存beanName和创建Bean的工厂之间的关系(第二个是一个函数式接口的类型)
private final Map> singletonFactories = new HashMap(16);
//二级缓存,保存保存beanName和创建Bean直接实例的关系(半成品对象)
private final Map earlySingletonObjects = new ConcurrentHashMap(16);
简述下过程:
A首先完成了初始化的第一步,而且将本身提早曝光到singletonFactories中,此时进行初始化的第二步,发现本身依赖对象B,此时就尝试去get(B),发现B尚未被create,因此走create流程,B在初始化第一步的时候发现本身依赖了对象A,因而尝试get(A),尝试一级缓存singletonObjects(确定没有,由于A还没初始化彻底),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,因为A经过ObjectFactory将本身提早曝光了,因此B可以经过ObjectFactory.getObject拿到A对象(半成品),B拿到A对象后顺利完成了初始化阶段一、二、三,彻底初始化以后将本身放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成本身的初始化阶段二、三,最终A也完成了初始化,进去了一级缓存singletonObjects中,因为B拿到了A的对象引用,因此B里面A对象完成了初始化。
注意事项:
1.Spring只是解决了单例模式下属性依赖的循环问题;Spring为了解决单例的循环依赖问题,使用了三级缓存。
2.多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。
【spring|Spring是如何解决循环依赖的()】
推荐阅读
- java|Spring如何解决循环依赖(从基础到源码讲解)
- redis|小米面试题(讲一下Redis分布式锁)
- java|Bean 生命周期详解
- mybatis|百度智能业务部java实习一面
- java|Spring boot——Actuator 详解
- spring|Spring如何解决循环依赖问题
- java|JAVA Stream的collect用法与原理(详解)
- spring|在IntelliJ IDEA里创建Spring Boot项目
- Spring|Spring Cloud OpenFeign/Hystrix 超时配置