spring循环依赖
Spring中的循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环,导致Spring容器无法正确初始化这些Bean。
循环依赖通常发生在以下场景:
- 构造函数循环依赖:两个Bean在构造函数中相互依赖。
- 字段注入循环依赖:两个Bean通过@Autowired或@Resource相互注入。
- 方法注入循环依赖:两个Bean通过@Bean方法相互依赖。
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
这里,A依赖B,B又依赖A,形成了循环依赖。
spring循环依赖的解决方式
Spring通过三级缓存机制部分解决了循环依赖问题,但仅限于字段注入和Setter注入,构造函数注入无法解决。
Spring三级缓存具体的实现类是:DefaultListableBeanFactory
Spring的三级缓存机制
- 一级缓存(Singleton Objects):存放完全初始化好的Bean。
- 二级缓存(Early Singleton Objects):存放提前暴露的Bean(尚未完成属性注入)。
- 三级缓存(Singleton Factories):存放Bean的工厂对象,用于生成提前暴露的Bean。
数据结构的源码如下:
// 一级缓存:存放完全初始化好的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放早期暴露的Bean(未填充属性)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放Bean工厂(用于生成早期引用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
解决过程示例:
- 当Spring初始化A时,会先将A的工厂对象放入三级缓存。
- 在A的属性注入阶段,发现需要注入B,于是开始初始化B。
- 在初始化B时,发现需要注入A,此时Spring会从三级缓存中获取A的工厂对象,生成一个提前暴露的A对象(未完成属性注入),并将其放入二级缓存。
- B完成初始化后,A继续完成属性注入,最终A和B都初始化完成。
sequenceDiagram
participant Container
participant CacheA
participant CacheB
Container->>CacheA: 开始创建A
Container->>CacheA: 实例化A(未设属性)
Container->>CacheA: 将A的ObjectFactory放入三级缓存
Container->>CacheA: 开始注入A的属性B
Container->>CacheB: 开始创建B
Container->>CacheB: 实例化B(未设属性)
Container->>CacheB: 将B的ObjectFactory放入三级缓存
Container->>CacheB: 开始注入B的属性A
Container->>CacheA: 发现一级缓存里没有,二级缓存里也没有,就取三级缓存里的ObjectFactory,获取早期引用,并放入二级缓存里并返回
Container->>CacheB: 将A的早期引用注入B
Container->>CacheB: 完成B的初始化
Container->>CacheA: 将初始化好的B注入A
Container->>CacheA: 完成A的初始化
关键的源码解析
获取Bean的核心入口:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 检查一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 2. 检查二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 检查三级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject(); // 触发工厂创建早期引用
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
暴露早期引用:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化(此时对象还未填充属性)
Object beanInstance = instantiateBean(beanName, mbd);
// 2. 将ObjectFactory加入三级缓存(关键步骤!)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
// 3. 属性填充(可能触发循环依赖)
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
以下场景的循环依赖无法解决:
- 构造器循环依赖:Spring无法通过三级缓存解决构造函数循环依赖,因为在构造函数中需要完整的Bean实例,而提前暴露的Bean尚未完成初始化。
- Prototype作用域的循环依赖:Spring不缓存prototype Bean,每次请求都新建实例。
- @Async代理的循环依赖:代理对象需要在初始化后生成,与早期引用冲突。
我们需要避免循环依赖
为什么需要三级缓存?二级不行吗?
三级缓存的核心价值在于处理AOP代理。如果只有二级缓存:
- 普通Bean可以直接存半成品到二级缓存
- 但AOP代理需要在初始化后生成,必须通过ObjectFactory延迟判断是否需要代理
Spring如何检测循环依赖?
通过isSingletonCurrentlyInCreation(beanName)检查当前Bean是否正在创建中:
// DefaultSingletonBeanRegistry
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.get().contains(beanName);
}