Spring 中的循环依赖处理
循环依赖是指两个或多个Bean之间相互依赖,形成了相互引用的关系。在Spring框架中,如果遇到循环依赖,即两个Bean互相持有对方的引用,Spring框架如何处理呢?
循环依赖的问题
当Spring容器必须同时实例化相互依赖的Beans时,由于无法同时调用它们的构造方法完成依赖注入,因此容器无法完成Bean的创建。这将导致死循环或抛出StackOverflowError
异常。
早期依赖注入
Spring解决循环依赖问题的核心在于“早期依赖注入”。这一策略涉及到三个关键概念:singletonObjects
、earlySingletonObjects
和singletonFactories
。
解决循环依赖的原理流程
- 依赖注入: 容器加载Bean时,对所有依赖项进行依赖注入。
- 早期引用: 如果Bean的构造器中有对其他Bean的引用,则使用一个"早期引用"暂时代替。
- 实例化其余Bean: 容器继续实例化和依赖注入其余Bean,并在处理完所有Bean后,开始解析早期引用。
- 查找Singleton缓存: 首先尝试从Singleton缓存中查找Bean,如果找到则返回。
- 创建新实例: 缓存中未找到时,尝试使用Bean定义的构造器创建新实例。
- 填充依赖项: 如果构造器中有依赖其他Bean,使用早期引用来填充这些依赖项。
- 异常处理: 如果无法满足依赖关系,Spring将抛出异常。
提前暴露Bean实例
SingletonObjects
: 表示已经完全初始化的单例Bean实例对象。EarlySingletonObjects
: 表示已创建但未完全初始化的单例Bean实例对象。singletonFactories
: 表示Bean工厂对象。
实例化过程
当Spring创建Bean对象时,会检查Bean是否已在缓存中且完整。如果正在创建的Bean处于创建中但已完成实例化与初始化,就返回该对象。
EarlySingletonObjects占位Bean对象
如果Bean有构造函数注入的依赖项,则先创建一个占位Bean对象并提前暴露,待依赖注入完成后再进行初始化。
singletonFactories的作用
在Bean实例化到一定程度后(通常是构造方法执行完成),提前暴露的占位Bean对象被添加到singletonFactories
中,从而避免循环依赖。
Spring的提前暴露Bean实例的代码实现
public Object getEarlyBeanReference(String beanName, RootBeanDefinition beanDefinition, Object bean) {
Object exposedObject = bean;
// 省略同步块和其他操作
return exposedObject;
}
当Bean有依赖其他Bean时,此方法将正在创建的Bean添加到earlySingletonObjects中,并返回半成品实例。
protected Object doResolveDependency(
DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames,
TypeConverter typeConverter, @Nullable Map<String, Object> cachedResult) throws BeansException {
// ... 省略代码 ...
if (earlySingletonExposure) {
Object earlySingletonReference = getEarlyBeanReference(beanName, bd, bean);
// ... 省略代码 ...
return earlySingletonReference;
}
// ... 省略代码 ...
}
这个方法在解决Bean依赖时,如果条件允许,则生成半成品Bean实例。否则,正常创建并注入依赖Bean。
通过上述机制,Spring框架能够在所有Bean都实例化完成后,正式开始依赖注入的过程,从而解决循环依赖问题。
注意:本文归作者所有,未经作者允许,不得转载