欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

解决循环依赖看看spring是怎么做的

程序员文章站 2022-06-20 10:57:13
...

循环依赖的定义:

假设有两个对象A,B;对象A依赖B,对象B依赖A。

@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;
  }
}

spring中如何解决循环依赖:

通过递归,递归的终止条件是能在map中找到被所需要的bean。

分析:

首先理解创建一个对象分两步

首先new出来一个对象,然后给对象里的变量赋值。

解析:

这里我们以上面的首先初始化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属性设置了值。

☎️https://www.cnblogs.com/asker009/p/14376955.html

  • 三级缓存解决了循环依赖的问题

一级缓存:存放完整的bean

二级缓存:存放加载bean的没有被实例化的对象,是不完整的bean

三级缓存:存放代理的bean,第三级缓存里实际存入的是ObjectFactory接口签名的回调实现,把原始的包返回。

那么你知道三级缓存吗?三级缓存是解决什么问题的?

  • 三级缓存是解决循环依赖问题的,一级缓存存放的是完整的Bean,二级缓存存放的是半成品的Bean,三级缓存存放的是ObjectFactory。在创建Bean时会把半成品的Bean包装成为ObjectFactory放入到三级缓存中,在进行该Bean的注入时就需要从缓存中拿到半成品的对象,如果没有三级缓存的存在,那么因为注入的Bean有可能需要完成动态代理,但是你注入的Bean并没有完成动态代理,就导致了最终注入的Bean是个半成品。二级缓存实际上是可以不需要存在的,每次都直接从三级缓存中拿其实也是可以的,只不过三级缓存中有一堆for循环,每次就导致了性能的降低,因此在完成动态代理之后会将对象从三级缓存中移除,并且加入到二级缓存中,下次就只需要从二级缓存中拿就能拿到完整的对象了。