Spring如何解决循环依赖

在关于Spring的面试中,我们经常会被问到一个问题:Spring是如何解决循环依赖的问题的。

一、问题的提出

spring循环依赖包括:构造器循环依赖、field属性注入循环依赖

1、构造器循环依赖

@Service
public class A {  
    public A(B b) {  }
}

@Service
public class B {  
    public B(C c) {  
    }
}

@Service
public class C {  
    public C(A a) {  }
}

结果:项目启动失败,发现了一个cycle

2、field属性注入循环依赖

@Service
public class A1 {  
    @Autowired  
    private B1 b1;
}

@Service
public class B1 {  
    @Autowired  
    public C1 c1;
}

@Service
public class C1 {  
    @Autowired  public A1 a1;
}

结果:项目启动成功

3、field属性注入循环依赖(prototype)

@Service
@Scope("prototype")
public class A1 {  
    @Autowired  
    private B1 b1;
}

@Service
@Scope("prototype")
public class B1 {  
    @Autowired  
    public C1 c1;
}

@Service
@Scope("prototype")
public class C1 {  
    @Autowired  public A1 a1;
}

结果:项目启动失败,发现了一个cycle

结论:

1、构造器注入和prototype类型的field注入发生循环依赖时都无法初始化
2、field注入单例的bean时,尽管有循环依赖,但bean仍然可以被成功初始化

二、Spring创建Bean的过程

Spring创建好了BeanDefinition之后呢,会开始实例化Bean,并且对Bean的依赖属性进行填充。Spring实例化bean是通过ApplicationContext.getBean()方法来进行的,实例化时底层使用了CGLIB或Java反射技术。

要理解两点:

1、Spring是通过递归的方式获取目标bean及其所依赖的bean的;
2、Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。

也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。

实例化过程中有一个重要的类DefaultListableBeanFactory,它有三个Map类型的属性,是解决问题的关键,这三个Map实际上是bean的缓存。对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存,就是DefaultListableBeanFactory类的这三个Map。

三、单例注入bean是如何解决循环依赖问题呢?

假设循环注入是A-B-A:A依赖B(A中autowire了B),B又依赖A(B中又autowire了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创建bean的流程,解决循环依赖的步骤大概是这样(假设容器首先初始化A对象):

1、首先Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于Spring容器缓存中还没有A对象实例,因而其会创建一个A对象,此时A并没有注入属性,也可以认为是半成品
2、A初始化后,Spring将在这个还没有设置属性的A放入缓存中
3、然后发现其依赖了B对象,因而会尝试递归的通过ApplicationContext.getBean()方法获取B对象的实例,但是Spring容器缓存中此时也没有B对象的实例,因而其还是会先创建一个B对象的实例。
4、此时A对象和B对象都已经创建了,并且保存在Spring容器中了,只不过A对象的属性b和B对象的属性a都还没有设置进去。
5、Spring创建B对象之后,Spring发现B对象依赖了属性A,因而还是会尝试递归的调用ApplicationContext.getBean()方法获取A对象的实例。
6、因为Spring中已经有一个A对象的实例,虽然只是半成品(其属性b还未初始化),但其也还是目标bean,因而会将该A对象的实例返回。
7、B对象的属性a就设置进去了,然后还是ApplicationContext.getBean()方法递归的返回,也就是将B对象的实例返回,此时就会将该实例设置到A对象的属性b中。
8、此时A对象的属性b和B对象的属性a都已经设置了目标对象的实例了

 

注意:前面在为对象B设置属性a的时候,这个A类型属性还是个半成品。但是这个A是一个引用,其本质上还是最开始就实例化的A对象,其引用地址是同一个。

四、为什么prototype类型的和构造器类型的Spring无法解决循环依赖呢?

答案也很简单,因为A中构造器注入了B,那么A在关键的方法addSingletonFactory()把A放入singletonFactories之前就去初始化了B,导致三级缓存中根本没有A,所以会发生死循环,Spring发现之后就抛出异常了。至于Spring是如何发现异常的呢,本质上是根据Bean的状态给Bean进行mark,如果递归调用时发现bean当时正在创建中,那么就抛出循环依赖的异常即可。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值