- 什么是 spring?
- 使用 Spring 框架的好处是什么?
- Spring框架的主要组成部分有哪些?
- 什么是Spring IoC(控制反转)和DI(依赖注入)?
- Spring框架中Bean的生命周期是怎样的?
- Spring AOP的理解及实现方式?
- 解释 Spring 支持的几种 bean 的作用域。
- Spring 框架中的单例 bean 是线程安全的吗?
- Spring事务管理有哪些方式?
- Spring循环依赖
**Spring框架中的循环依赖** 是指在一个或者多个Java Bean之间形成了环状依赖关系,即Bean A依赖于Bean B,同时Bean B又直接或间接依赖于Bean A。在Spring容器管理Bean的生命周期过程中,若出现循环依赖且没有得到妥善处理,可能导致容器无法确定初始化Bean的顺序,进而影响Bean的正确实例化。
Spring框架对于循环依赖的处理机制:
1. **构造器注入的循环依赖:**
- Spring不能解决构造器注入造成的循环依赖。因为在创建Bean的过程中,构造器注入要求所有的依赖都必须在构造器调用时准备好。一旦形成循环依赖,Spring容器无法打破这个循环,最终会导致`BeanCurrentlyInCreationException`异常。
2. **setter注入的循环依赖:**
- Spring可以通过其内部的三级缓存机制解决setter注入的循环依赖问题:
- 第一级缓存(singletonObjects)存放已经完成初始化的单例Bean。
- 第二级缓存(earlySingletonObjects)存放提前曝光的单例Bean,即已经实例化但尚未完成属性注入和初始化的Bean。
- 第三级缓存(singletonFactories)存放Bean工厂,这些工厂能够在需要的时候生产Bean实例。
- 在处理setter注入时,Spring允许Bean在实例化之后、属性注入之前被“提前曝光”,也就是放入到第二级缓存中。这样,当后续的Bean需要依赖它时,可以从第二级缓存中获取这个半成品Bean,从而有效地打破了循环依赖。
总结起来,对于setter注入的循环依赖,Spring容器能够通过三级缓存机制确保即使在依赖关系环形引用的情况下也能顺利完成Bean的初始化。而对于构造器注入的循环依赖,开发者需要避免这样的设计,因为它会导致Spring无法自动解决此类问题。
通过一个简单的例子来形象地理解Spring是如何通过三级缓存解决setter注入的循环依赖:
假设我们有两个互相依赖的Bean:`BeanA`和`BeanB`,其中`BeanA`的setter方法需要注入`BeanB`,而`BeanB`的setter方法也需要注入`BeanA`。
```java
public class BeanA {
private BeanB beanB;
// Setter方法注入BeanB
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
private BeanA beanA;
// Setter方法注入BeanA
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
在Spring容器初始化这两个Bean的过程中:
- 首先,Spring尝试初始化
BeanA
,发现它依赖于BeanB
,于是开始创建BeanB
。 - 在创建
BeanB
时,Spring发现它依赖于BeanA
,但由于BeanA
正在创建过程中,此时Spring不会抛出异常,而是将刚刚实例化但未完成注入的BeanA
对象暂存到第二级缓存(earlySingletonObjects)中。 - 然后,Spring从第二级缓存中取出
BeanA
的半成品对象注入到BeanB
的setter方法中,完成了BeanB
的实例化。 - 接下来,Spring返回去继续完成
BeanA
的初始化,从第二级缓存中取得已经注入了BeanA
的BeanB
对象,注入到BeanA
的setter方法中。 - 最终,两个Bean都完成了各自的初始化和依赖注入。
通过这种三级缓存机制,Spring巧妙地打破了原本看似无法解决的循环依赖问题,使得即使在setter注入的循环引用情况下,也能确保两个Bean都能正常初始化和完成依赖注入。