Spring如何解决循环依赖

Spring循环依赖包括:

1.构造器循环依赖,

//构造器循环依赖A,B方法互相调用
@Service
public class A {  
    public A(B b) {  }
}
 
@Service
public class B {  
    public B(A a) {  
    }
}
//结果:项目启动失败,发现了一个cycle,AB直接调用死循环,不能结束

2.field属性注入循环依赖

//注入循环依赖
@Service
public class A1 {  
    @Autowired  
    private B1 b1;
}
 
@Service
public class B1 {  
    @Autowired  
    public A1 a1;
}
//结果:项目启动成功

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

//field属性注入循环依赖(prototype)
@Service
@Scope("prototype")
public class A1 {  
    @Autowired  
    private B1 b1;
}
 
@Service
@Scope("prototype")
public class B1 {  
    @Autowired  
    public A1 a1;
}
//结果:项目启动失败,发现了一个cycle

结论

构造器注入和prototype类型的field注入发生循环依赖时都无法出书画

field注入单例的bean时,尽管有循环依赖,但bean仍然可以被成功初始化

Spring创建bean的过程

Spring 创建好BeanDefinition之后,会开始实例化bean。并且对bean的依赖项进行填充

Spring实例化bena是通过ApplicationContetxt.getBean()方法进行的,实例化底层用了CGLIB或java反射技术

要理解两点

Spring是通过递归的方式获取目标bean及其所依赖的bean的

2.Spring实例化一个bean的时候,是分两部进行的,首先实例化目标bean,然后为其注入属性

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

比如说spring在实例化A对象是,会先实例化A的依赖项B,C。此时发现B又依赖D,那么就会先实例化Bean对象D,再实例化B,C再实例化A。

实例化过程中有一个重要的类DefaultListableBeanFactory(默认列表Bean工厂)

他又三个map类型的属性,是解决问题的关键,是IOC的三级缓存的表现,对于单例来说,在Spring在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到,这个对象应该存在Cache中,Spring 为了解决单例的循环依赖问题,使用了三级缓存,就是DefaultListableBeanFactory(默认列表Bean工厂)类的三个Map。

 三级缓存(单例工厂)singletonFactories存放ObjectFactory传入的时匿名内部类ObjectFactory.getObject() 方法最终会调用getEarlyBeanReference()进行处理,返回创建bean实例化的lambda表达式。

二级缓存(早期的单例对象)earlySingletonObjects存放bean,保存半成品bean实例,当对象需要被AOP代理是,保存bean实例的bean代理

一级缓存(单利池)singletonObjects存放完整的bean实例

/**单例对象的缓存:从bean名称到bean实例。   */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**早期单例对象的缓存:从bean名称到bean实例。 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**单例工厂的缓存:对象工厂的bean名称。   */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

 单例注入bean如何解决循环依赖?

假设循环注入是A-B-A: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创建bean的流程中的三级缓存解决了依赖循环的问题

  1. Spring通过Application.getBean()获取A对象的实例,由于Spring容器缓存中还没有A对象实例,因此会创建A对象,
  2. A对象初始化之后,Spring 将这个还没有设置属性的A放入缓存中
  3. 然后发现了其依赖B对象,因而会先创建B对象的实例
  4. 此时A和B都已经被创建了,只不过A和B的对象属性都还没有设置进去
  5. Spring创建B对象之后,优化发现B依赖了A,因此会再尝试调用A,通过Application.getBean()获取A对象的实例,但此时Spring中已经存在了一个A对象,会将这个对象返回
  6. 此时A对象的属性b和B对象的属性A都已经被设置目标对象的实例了

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

 使用二级缓存能不能解决循环依赖?

答案是不可以:

缺少singletonObjects时,将无法产生对象

缺少singletonFactories时,无法产生代理对象

缺少earlySingletonObjects时,无法保证始终只有一个代理对象

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

因为A中注入了B,那么A在关键的方法addSingletonFactory()把A放入singletonFactories之前就去初始化B,导致三级缓存根本没有A,所以会发生死循环,Spirng发现之后就抛出异常了。

Spring是如何发现异常的呢,本质上是根据一Bean状态给Bean进行mark如果递归调用时发现Bean正在创建中,那么抛出循环依赖的异常即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值