Spring中如何解决循环依赖

什么是循环依赖:

                                        类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。

比如下图中A类依赖了B类,B类依赖了C类,而最后C类又依赖了A类,这样就形成了循环依赖问题

                               

演示代码

public class A{
    private B b;
    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
}
class B{
    private A a;
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
}

在Spring IoC的使用场景中有两类循环依赖是无解的:

  • 构造器的循环依赖:构造器要调用构造函数new 一个对象出来,而参数又依赖于另一个对象。创建类A依赖于类B,new 的时候去创建类B发现类B不存在就会出错拋出 BeanCurrentlyInCreationException 异常。

  • prototype 原型bean循环依赖:原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过set方法产生的循环依赖也会抛出异常。

然而针对 singleton bean的循环依赖的场景可以通过三级缓存的方式解决

spring的缓存:一级缓存(singletonObjects):存放bean对象
                        二级缓存(earlySingletonObjects):提前暴露bean对象
                        三级缓存(singletonFactories):工厂对象

Spring IoC处理循环依赖的思路:

在整理Spring IoC处理singleton bean循环依赖的思路之前先来复习一下bean的生命周期,其包括的三个步骤:

  • 实例化:执行了bean的构造方法,bean中依赖的对象还未赋值

  • 设置属性:给bean中依赖的对象赋值,若被依赖的对象尚未初始化,则先进行该对象的生命周期(递归)。

  • 初始化:执行bean的初始化方法,回调方法等。

解决循环依赖的思路就藏在这三个步骤中,在实例化与设置属性两个步骤之间引入缓存机制,将已经创建好实例但是并没有设置属性的bean放到缓存里,缓存中是没有属性设置的实例对象。假设A对象和B对象互相依赖,A对象的创建需要引用到B对象,而B对象的创建也需要A对象。在创建A对象的时候可以将其放入到缓存中,当B对象创建的时候直接从缓存里引用A对象(此时的A对象只完成了实例化,没有进行设置属性的操作,因此不是完成的A对象,我们称之为半成品A对象),当B对象利用这个半成品的A对象完成实例创建以后(三个步骤都完成),再被A对象引用进去,则A对象也完成了创建。

解决循环依赖的整个过程是:

先从一级缓存里取bean实例,如果没有对应的bean实例,二级缓存里取,如果二级缓存中也没有bean实例,singletonFactories三级缓存里获取。由于三级缓存存放着产生bean实例的工厂类,因此可以通过该工厂类产生bean实例。

这里可以调用工厂类暴露的getObject方法返回早期暴露对象引用,也是我们所说的半成品bean,也可以成为earlySingletonObject。并且将这个半成品bean放到二级缓存里,在三级缓存里删除该bean。什么时候这个半成品填充了属性以后,就被移动到一级缓存中,也就是被作为可以使用的已经完成初始化的实例bean了,处理循环依赖的过程宣告完毕。下面通过一个例子让大家更好理解这个思路。

 循环依赖执行顺序(A、B)
1、创建A对象,将A对应的singletonFactory对象放入三级缓存
2、A属性装填时,需要B对象,但是B对象不存在,创建B对象。
3、创建B对象,同时把B的工厂对象,放入三级缓存。
4、B进行属性填装,依赖A对象
5、从三级缓存获取A对象,创建A对象
6、将创建出的A放入到二级缓存,同时将A的工厂对象从三级缓存中移除
7、返回A对象之后,B对象的属性就装填完毕
8、把B对象放入到一级缓存,同时将B的工厂从三级缓存移除
9、B返回之后,A对象的属性就装填完毕
10、把A对象放入到一级缓存,同时把A从二级和三级缓存中删除

4、处理循环依赖举例

根据上面的思路,这里假设A 和 B 互相依赖,如图2所示,在A 创建实例的时候使用了getBean方法,通过createBeanInstatnce方法对A进行实例化。此时的A只是被实例化出来了,并没有进行填充属性的操作,然后通过addSingletonFactory的方法将创建A的工厂类添加到三级缓存中。上面的思路中提到了这个放到三级缓存中的工厂类是用来生成bean实例用的。

接着往下,当通过populateBean填充实例A属性的时候发现,A依赖B。此时开始通过getBean方法创建B的实例,依旧通过createBeanInstatnce方法对B进行实例化,也把创建B实例的工厂类通过addSingletonFactory方法添加到三级缓存中。在使用populateBean方法填充B的属性时,发现B依赖A,此时通过getBean方法对A进行实例化。

这个时候就出现循环依赖的情况了,getBean方法先从一级缓存中获取 A 的实例,发现没有,再去二级缓存中找,还是找不到,没有办法只有找三级缓存中的A 实例创建工厂去创建A的实例。在前面的步骤中A 已经将工厂类通过addSingletonFactory方法存放到了三级缓存中,于是调用A的工厂类创造A的实例,并且将其放到二级缓存中返回给B 用来填充B的属性,当B完成属性填充以后产生了B的实例,返回给populateBean(A)使用,此时A获取了B的实例(完成属性填充的B实例)。

所以,A 也可以完成属性填充从而产生A 的初始化以后的实例并且将其放到一级缓存中。由于B之前使用的是A的实例是没有做属性填充的,也就是半成品的A实例,因此此时从一级缓存中获取成品的A实例完成B对象的初始化


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值