一.Spring中bean的循环依赖怎么解决
- 什么是循环依赖
- 1 其实就是在进行getBean的时候,A对象中去依赖B对象,而B对象又依赖C对象,但是对象C又去依赖A对象,结果就造成A、B、C三个对象都不能完成实例化,出现了循环依赖。就会出现死循环,最终导致内存溢出的错误。
- 如何去解决Spring的循环依赖:
实例:
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
可以看到,这里A和B中各自都以对方为自己的全局属性。这里首先需要说明的一点,Spring实例化bean是通过ApplicationContext.getBean()方法来进行的。
如果要获取的对象依赖了另一个对象,那么其首先会创建当前对象,然后通过递归的调用ApplicationContext.getBean()方法来获取所依赖的对象,最后将获取到的对象注入到当前对象中。
这里我们以上面的首先初始化A对象实例为例进行讲解。
首先Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于Spring容器中还没有A对象实例,因而其会创建一个A对象
然后发现其依赖了B对象,因而会尝试递归的通过ApplicationContext.getBean()方法获取B对象的实例
但是Spring容器中此时也没有B对象的实例,因而其还是会先创建一个B对象的实例。
读者需要注意这个时间点,此时A对象和B对象都已经创建了,并且保存在Spring容器中了,只不过A对象的属性b和B对象的属性a都还没有设置进去。
在前面Spring创建B对象之后,Spring发现B对象依赖了属性A,因而还是会尝试递归的调用ApplicationContext.getBean()方法获取A对象的实例
因为Spring中已经有一个A对象的实例,虽然只是半成品(其属性b还未初始化),但其也还是目标bean,因而会将该A对象的实例返回。
此时,B对象的属性a就设置进去了,然后还是ApplicationContext.getBean()方法递归的返回,也就是将B对象的实例返回,此时就会将该实例设置到A对象的属性b中。
这个时候,注意A对象的属性b和B对象的属性a都已经设置了目标对象的实例了
读者朋友可能会比较疑惑的是,前面在为对象B设置属性a的时候,这个A类型属性还是个半成品。但是需要注意的是,这个A是一个引用,其本质上还是最开始就实例化的A对象。
而在上面这个递归过程的最后,Spring将获取到的B对象实例设置到了A对象的属性b中了
这里的A对象其实和前面设置到实例B中的半成品A对象是同一个对象,其引用地址是同一个,这里为A对象的b属性设置了值,其实也就是为那个半成品的a属性设置了值。
如图详解:
3. 具体使用spring的三级缓存:
- 首先第一步是在Spring中会先去调用getSingleton(String beanName, boolean allowEarlyReference)来获取想要的单例对象。
- 然后第一步会先进行通过singletonObjects这个一级缓存的集合中去获取对象,如果没有获取成功的话并且使用isSingletonCurrentlyInCreation(beanName)去判断对应的单例对象是否正在创建中(也就是说当单例对象没有被初始化完全,走到初始化的第一步或者第二的时候),如果是正在创建中的话,会继续走到下一步
- 然后会去从earlySingletonObjects中继续获取这个对象,如果又没有获取到这个单例对象的话,并且通过参数传进来的allowEarlyReference标志,看是不是允许singletonFactories(三级缓存集合)去拿到该实例对象,如果allowEarlyReference为Ture的话,那么继续下一步
- 此时上一步中并没有从earlySingletonObjects二级缓存集合中拿到想要的实例对象,最后只能从三级缓存singletonFactories (单例工厂集合中)去获取实例对象,
- 然后把获取的对象通过Put(beanName, singletonObject)放到earlySingletonObjects(二级缓存中),然后在再从singletonFactories(三级缓存)对象中的集合中把该对象给remove(beanName)出去。