likes
comments
collection
share

spring如何解决循环依赖

作者站长头像
站长
· 阅读数 13

1.什么时候产生循环依赖

我们知道用了spring之后,对象的创建、管理、和装配由spring管理。被管理的对象就称为bean,bean的生命生命周期大致分为如下4个步骤:

1.实例化bean --> 2.属性填充 --> 3.初始化bean --> 4.使用---> 5.销毁bean

  • 实例化bean:相当于调用空参构造new了一个A对象[A a = new A()],里面的成员变量都是默认值,如int->0,引用->null。

  • 属性填充:相当于调用了set方法,对a的属性设置值

  • 初始化bean:这里就会调用自己指定的init-method()

  • 销毁bean:回收bean对象

ps:bean的生命周期还是比较复杂的,这里重点讲清循环依赖,只要知道上述大概的bean生命周期就行。

在创建对象时,如果A对象依赖B对象,而B对象又依赖A,这个时候就产生了循环依赖。下面进行模拟演示

class A{
    private B b;
}
class B{
    private A a;
}
new A() ---> 注入B属性时,一级缓存还没有B实例 ---> new B() --->注入A属性时,一级缓存中还没有A实例-->出现循环依赖

2. 二级缓存

上面我们看到,单靠一级缓存,无法解决循环依赖。这个时候引入二级缓存,情况就变成下面这样了:

  1. A a = new A()
  2. 放入二级缓存(属性都未赋值,a是个半成品)
  3. 属性填充(发现需要B)----下面进入实例化B的生命周期
  4. B b = new B()
  5. 属性填充(发现需要A)
  6. 去二级缓存找,找到了A(半成品a),b.a = a;(循环依赖解决)
  7. 初始化b-----(b实例化完成放入一级缓存,且是个完整的b,记住这时b.a 还是个半成品 )
  8. 回到A的属性填充阶段,此时一级缓存有b了,a.b = b
  9. 初始化a-----(这时a也是个完整的a了)

至此,有小伙伴可能会疑问,b.a还是个半成品,后续没有给b.a赋值一个完整a的步骤吗?其实b.a 这里就是个引用,因此当a初始化完成,b.a也就是个完整的a了。

听说spring解决循环依赖需要三级缓存,这里怎么二级就够了?

因为上述情况我们没有考虑spring很重要的一个特性----AOP。

3.三级缓存

3.1 二级缓存存在的问题

打个不太恰当的比喻:a是房主,他要卖房,但是没有家具,a把房子给中介去卖,中介觉得添置点家具会更容易卖,就给房子加了家具。此时b来买房,想要家具齐全的房子,那么b自然应该去找中介,而不是直接找a。这里中介就相当于代理。

1.实例化bean --> 2.属性填充 --> 3.初始化bean --> 4.使用---> 5.销毁bean

  1. A a = new A()
  2. 放入二级缓存(属性都未赋值,a是个半成品)
  3. 属性填充(发现需要B)----下面进入实例化B的生命周期
  4. B b = new B()
  5. 属性填充(发现需要A)
  6. 去二级缓存找,找到了A(半成品a),b.a = a;(循环依赖解决)
  7. 初始化b-----(b实例化完成放入一级缓存,且是个完整的b,记住这时b.a 还是个半成品 )
  8. 回到A的属性填充阶段,此时一级缓存有b了,a.b = b
  9. 初始化a-----(这时a也是个完整的a了) 不存在循环依赖的情况下,aop是在初始化这个阶段完成的,然后把代理对象放入一级缓存。当aop遇上循环依赖,二级缓存就显得乏力了。我们按照上面二级缓存解决循环依赖的逻辑继续分析:在B b = new B()的时候,在二级缓存找到a的半成品赋值给b。这时候的半成品是没有被代理的,在第9步的时候才创建a的代理对象。这个时候就出问题了,A是要被代理的,b.a 应该是代理对象。但是这个时候,b.a是被代理前的对象。也就是说b.a 是房主,a的代理在第9步,初始化a的时候才创建。

3.2 解决问题

这个时候就要提前aop了,提前把A的代理创建出来,再赋值给B。如何判断是否需要提前aop呢?那就要看当前创建的对象是否存在循环依赖。在哪个阶段判断是否存在循环依赖呢?在B属性注入这个阶段。这个阶段如果发现a还在创建中,就可以断定A出现了循环依赖。创建一个集合CreatingSet<>,专门存放正在创建中的bean name,在实例化A的时候,就把A的名字放进去,A初始化完后移出这个集合。后面在B属性注入A的时候,看看集合中是否有A就可以判断了。

这时候创建完整A的步骤就变成了:

  1. A a = new A();
  2. 属性填充(发现需要B)--进入B的生命周期 2.1 B b = new B() 2.2 属性填充A-->去一级缓存找A,没有-->isCreating?,true-->去二级缓存找,没有-->提前aop,创建A的代理对象-->放入二级缓存 2.3 初始化b 2.4 b放入一级缓存
  3. 初始化a
  4. a放入一级缓存

经过如上步骤,貌似二级缓存也成功解决了aop遇上循环依赖的问题

但是aop是生成了个代理对象,而代理对象存在的前提是它要知道自己代理谁,即要有个原始对象。那么这个元素对象放在哪呢?对,就是三级缓存里,在实例化A之后,就把A的原始对象放进三级缓存中了。因此完整的流程如下:

  1. A a = new A();a放入三级缓存
  2. 属性填充(发现需要B)--进入B的生命周期 2.1 B b = new B() 2.2 属性填充A-->去一级缓存找A,没有-->isCreating?,true-->去二级缓存找,没有-->去三级缓存找-->提前aop,创建A的代理对象-->放入二级缓存 2.3 初始化b 2.4 b放入一级缓存
  3. 初始化a
  4. a放入一级缓存
转载自:https://juejin.cn/post/7115308606622269471
评论
请登录