Spring如何解决循环依赖

什么是循环依赖

比如:在A类引入b对象,在B类引入A对象,创建a的时候需要依赖b,创建b的时候需要依赖a,而各自创建对象的时候,其互相依赖的对象还没有创建完成,就导致各自都无法成功创建对象。这就是循环依赖。

class A {
    private B b;
}

class B {
    private B a;
}

解决循环依赖的方法很简单,如下所示,先实例化A和B,此时都没初始化,即都没有对各自对象里面的属性进行赋值,接下来可以通过set方法,把实例a和实例b set到各自的对象属性中去,这样一来就解决循环依赖问题。

A a = new A();
B b = new B();
a.setB(b);
b.setA(a);

但是,如果A和B类都使用含参构造器的方法进行实例化,就没法解决循环依赖问题了,因为在使用含参构造器的方法,需要将属性值先实例化出来,A依赖属性b,就需要先将B实例化,可以此时B的实例化也依赖a,这样就没法实例化了。

A a = new A(b);
B b = new B(a);

所以,解决循环依赖问题的主要思想是:先实例化每个类,再把实例set到依赖它的实例中去,即先实例化,再初始化

Spring怎么解决循环依赖问题

既然普通的创建对象方法可以用上文描述的方法来解决,那么Spring怎么解决循环依赖的问题?
首先Spring有两大特性IOC和AOP:

  • IOC:对象的创建和依赖交给Spring容器去管理,以解决对象之间的耦合问题,开发的时候,我们只需要声明对象的引用即可,让我们便于开发;
  • AOP:将非业务代码抽离出去,比如权限验证、日志等逻辑是和业务无关的,将其抽离出去形成一个公共模块,也解决了耦合问题,符合单一职责原则。

我们再来看看Spring解决循环依赖的基本思想:先new,后set,即和上文描述的一样,也是先实例化,再初始化的方式。
那么Spring如何具体实现的呢?三级缓存
在Spring中,设置了三个缓存对象来缓存Bean:

  • 一级缓存:缓存已经初始化后的单例Bean;
  • 二级缓存:缓存已经实例化,未初始化的Bean;
  • 三级缓存:缓存可以生成这个Bean的工厂对象;
    Spring管理的Bean默认都是单例的,所以Spring最终把可以使用Bean都放入了一级缓存中,有要是用到哪个Bean就直接从一级缓存中获取就好了。

可以只使用一级缓存吗?

一级缓存存入的是初始化后的Bean,是供调用者直接拿来用的,如果只使用一级缓存,那么未初始化的Bean就会放进去,那么如果这个Bean的内部依赖被调用的话,就会造成空指针异常,因为这个Bean还没有初始化,其依赖还没有注入,所以单单使用一级缓存是不可以的。

可以使用二级缓存吗?

一级缓存是存放初始化后的Bean,单单使用一级缓存行不通,那么我们就再加一层缓存,二级缓存是存放实例化而未初始化的Bean的,那么使用二级缓存可以吗?
首先,假设要实例化A,看下面的代码

class A {
    private B b;
}

class B {
    private B a;
}

实例化流程如下:

  • 实例化A,把A加入二级缓存,此时A未初始化;
  • 然后初始化A,此时A需要依赖B,然后去一级缓存中找,发现没有,然后去二级缓存中找,发现也没有;
  • 此时实例化并初始化B,初始化B需要依赖A,因为A已经在二级缓存了,所以初始化B能在二级缓存找到A,所以实例化并初始化B成功,把B加入一级缓存;
  • 此时A就能一级缓存中找到B了,就可以初始化成功了 ,而B也初始化成功了,最后循环依赖的问题就解决了

我们发现二级缓存可以解决循环依赖的问题,那么为什么需要三级缓存呢?

为什么需要三级缓存?

首先,我们看看Spring加载Bean的过程:

  • Spring根据xml或者扫描注解,为每个类生成一个BeanDefintion,里面疯转了类信息,如全限定名、属性、是否单例等;
  • 根据BeanDefintion的信息,通过反射的方式去实例化Bean,此时还没有初始化;
  • 填充上述Bean的依赖对象;
  • 如果这个Bean被aop了,则需要生成代理类
  • 最后将这个Bean存入一级缓存中

问题出在AOP这里,如果没有AOP,使用二级缓存是可以解决循环依赖问题的,因为AOP后,是需要生成代理对象的。假设这个A类被AOP了,我们还是使用二级缓存的话,过程如下:

  • 实例化A,把A加入二级缓存;
  • 初始化A,填充B,缓存中没有B,那么开始实例化B,初始化B的时候从二级缓存中找到A,完成了B的初始化,放入到一级缓存;
  • 此时A能在一级缓存中找到B了,完成初始化;
  • 为A生成代理A。
    上面的结果就是B持有的是原始的A实例,而不是代理A实例,所以B的依赖是有问题的,B应该持有代理A实例。
    这个时候就需要三级缓存了,三级缓存存储的是Map结构,key是beanName,value是BeanFactory,即生成这个Bean的工厂对象,而这个Bean工厂做了一件事,生成并存储Bean的代理对象,如果A类存在AOP,那么在三级缓存里会存储A类的代理对象。
    比如:在初始化B的时候,需要A,此时B先去三级缓存中找A,此时找到了A的代理对象。
    所以使用三级缓存的过程如下:
  • 实例化A,把A加入三级缓存;
  • 初始化A,此时缓存中没有B,那么实例化B;
  • 初始化B,此时需要填充A,B去三级缓存中找A,因为A有AOP,所以B执行了三级缓存中生成代理对象A的方法获取代理对象A,为了防止每次使用都重复成代理对象A,所以把代理对象A再存入二级缓存,并删除在三级缓存中的代理对象A,这样以后就从二级缓存找就行了。
  • B现在从二级缓存中获取到代理对象A,完成初始化操作;
  • A填充B,完成初始化操作;

总结

  • Spring解决循环依赖的基本思想是先new再set,如果是使用构造方法实例化的对象,是没法解决循环依赖问题的;
  • 具体方式就是使用三级缓存,如果Bean没有被AOP使用二级缓存就行;
  • 一级缓存存放初始化后的单例Bean;
  • 二级缓存存放未初始化的Bean;
  • 三级缓存存放Bean工厂对象,用来生成Bean的代理对象;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值