一、前言
文章目录:Spring源码深度解析:文章目录
这篇文章是接着 Spring源码深度解析:七、bean的加载① - doGetBean 的继续分析过程。
二、什么是循环依赖
循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:
循环依赖,其实就是一个死循环的过程,在初始化 A 的时候发现引用了 B,这时就会去初始化 B,然后又发现 B 引用 C,跑去初始化 C,初始化 C 的时候发现引用了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件。
三种循环依赖的情况:
- 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出
BeanCurrentlylnCreationException
异常。 - 单例模式下的
setter
循环依赖:通过“三级缓存”处理循环依赖,能处理。 - 非单例循环依赖:无法处理。原型(
Prototype
)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory
类中下面的判断,抛出异常。
下面详解每一种情况。
1 构造器循环依赖
表示通过构造器注入构成的循环依赖,这种依赖是无法解决的只能抛出 BeanCurrentlyInCreationException
异常表示循环依赖。
如在创建A类时,构造器需要创建B类,则回去创建B类,在创建B类过程中又发现要去创造C类,又去创建C类,而 创建C类有需要A类。则会形成一个闭环,无法解决。
因此Spring 将每一个正在创建的bean标识符放在了一个“当前创建bean池”(singletonsCurrentlyInCreation 集合)
。当bean标识符在创建过程中将一直保持在这个池中,,因此如果创建bean过程中发现自己已经在“当前创建bean池”中,则会抛出 BeanCurrentlyInCreationException
异常,并将创建完毕的bean从“当前创建bean池”中清除。
2 setter 循环依赖
表示通过setter 注入方式构成的循环依赖。对于setter 注入造成的依赖是通过Spring容器提前暴露刚完构造器注入但并未完成其他步骤(如setter注入,即仅仅自己完成了创建,但是对里面引用的属性还未创建完成) 的bean来完成的。而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。
如下代码所示:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
AbstractAutowireCapableBeanFactory#getEarlyBeanReference()
/**
* 给调用者一次机会,主要就是调用了SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()方法。将getEarlyBeanReference方法的返回值作为提前暴露的对象。
* 我们可以通过实现getEarlyBeanReference()方法来替代Spring提前暴露的对象
* Aop就是在这里将Advice动态织入bean中,若没有bean则直接返回bean,不做任何处理
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 当前类并非合成类 && 在hasInstantiationAwareBeanPostProcessors中
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
具体步骤如下:
- Spring 容器创建单例 “testA” bean, 首先根据无参构造器创建bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的bean,并将“testA” 标识符放到“当前创建bean池”,然后通过setter 注入 “testB”;
- Spring 容器创建单例“testB” bean,首先根据无参构造器创建bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的bean,并将“testB” 标识符放到“当前创建bean池”,然后通过setter 注入 “testC”;
- Spring 容器创建单例“testC” bean,首先根据无参构造器创建bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的bean,并将“testC” 标识符放到“当前创建bean池”,然后通过setter 注入 “testA”。进行诸如“testA”时由于提前暴露了“ObjectFactory” 工厂,从而使它返回提前暴露一个创建中的bean
- 最后再依赖注入“testB” 和“testA”,完成setter 注入。
3. prototype范围的依赖处理
对于 prototype 作用域的bean,Spring容器无法完成依赖注入,因为Spring容器中无法缓存prototype 作用域的bean,因此无法暴露一个创建中的bean。
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ACircular {
private BCircular bCircular;
@Autowired
public void setbCircular(BCircular bCircular) {
this.bCircular = bCircular;
}
}
...
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BCircular {
private ACircular aCircular;
@Autowired
public void setaCircular(ACircular aCircular) {
this.aCircular = aCircular;
}
}
调用时会抛出异常。注意这里是调用时,并非是服务启动时,因为原型模式的特性并不需要启动时就创建好bean。在调用的时候才会尝试创建,所以这里在使用bean 的时候才会抛出异常。
三、思路分析
要明白Spring中的循环依赖,得先明白Spring中Bean的生命周期。
Bean 的生成步骤
被 Spring 管理的对象叫做 Bean 。Bean的生成步骤如下:
- Spring 扫描 class 得到
BeanDefinition
; - 根据得到的
BeanDefinition
去生成 bean; - 首先根据 class 推断构造方法;
- 根据推断出来的构造方法,反射,得到一个对象==(暂时叫做原始对象)==;
- 填充原始对象中的属性(依赖注入);
- 如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象;
- 把最终生成的代理对象放入单例池==(源码中叫做 singletonObjects)==中,下次 getBean 时就直接从单例池拿即可;
对于 Spring 中的 Bean 的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很多很多,这里不详细说了。
我们可以发现,在得到一个原始对象后,Spring 需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?
比如,A 类中存在一个 B 类的 b 属性,所以,当 A 类生成了一个原始对象之后,就会去给 b 属性去赋值,此时就会根据 b 属性的类型和属性名去BeanFactory
中去获取 B 类所对应的单例bean。
- 如果此时
BeanFactory
中存在 B 对应的 Bean,那么直接拿来赋值给 b 属性; - 如果此时
BeanFactory
中不存在 B 对应的 Bean,则需要生成一个 B 对应的 Bean,然后赋值给 b属性。
问题就出现在「第二种」情况,如果此时 B 类在 BeanFactory
中还没有生成对应的 Bean,那么就需要去生成,就会经过 B 的 Bean 的生命周期。
那么在创建 B 类的 Bean 的过程中,如果 B 类中存在一个 A 类的 a 属性,那么在创建 B 的 Bean 的过程中就需要 A 类对应的 Bean,但是,触发 B 类 Bean 的创建的条件是 A 类 Bean 在创建过程中的依赖注入,所以这里就出现了循环依赖:
A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)
从而导致 A Bean 创建不出来,B Bean 也创建不出来。
上文分析得到,之所以产生循环依赖的问题
主要是:A创建时—>需要B---->B去创建—>需要A,从而产生了循环。
那么如何打破这个循环,加个缓存就可以了
A 的 Bean 在创建过程中,在进行依赖注入之前,先把 A 的原始 Bean 放入缓存(提早暴露,只要放到缓存了,其他 Bean 需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时 A 的Bean 依赖了 B 的 Bean 。
如果 B 的 Bean 不存在,则需要创建 B 的 Bean,而创建 B 的 Bean 的过程和 A 一样,也是先创建一个 B 的原始对象,然后把 B 的原始对象提早暴露出来放入缓存中, 然后在对 B 的原始对象进行依赖注入 A,此时能从缓存中拿到 A 的原始对象(虽然是 A 的原始对象,还不是最终的 Bean),B 的原始对象依赖注入完了之后,B 的生命周期结束,那么 A 的生命周期也能结束。
因为整个过程中,都只有一个 A 原始对象,所以对于 B 而言,就算在属性注入时,注入的是 A 原始对
象,也没有关系,因为A 原始对象在后续的生命周期中在堆中没有发生变化。
四、解决方案
在Spring中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是「三级缓存」
- 一级缓存为:
singletonObjects;
它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。 - 二级缓存为:
earlySingletonObjects;
映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance. - 三级缓存为:
singletonFactories;
映射创建Bean的原始工厂
// 一级缓存:用于保存BeanName和创建bean实例之间的关系,即缓存bean。 beanname -> instance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:也是保存BeanName和创建bean实例之间的关系,与singletonObjects 不同的是,如果一个单例bean被保存在此,则当bean还在创建过程中(比如 A类中有B类属性,当创建A类时发现需要先创建B类,这时候Spring又跑去创建B类,A类就会添加到该集合中,表示正在创建),就可以通过getBean方法获取到了,其目的是用来检测循环引用。
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:用于保存BeanName和常见bean的工厂之间的关系。beanname-> ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。那么Spring 是如何通过上面介绍的三级缓存来解决循环依赖的呢?这里只用 A,B 形成的循环依赖来举例:
1. 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
2. 为 A 创建一个 Bean工厂,并放入到 singletonFactories 中。
3. 发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
4. 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
5. 为 B 创建一个 Bean工厂,并放入到 singletonFactories 中。
6. 发现 B 需要注入 A 对象,此时在一级、二级未发现对象A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A
还是一个半成品,并没有完成属性填充和执行初始化方法)
7. 将对象 A 注入到对象 B 中。
8. 对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
9. 对象 A 得到对象B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
10. 对象 A完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A。
我们从源码的角度来看一下这个过程:
创建 Bean 的方法在AbstractAutowireCapableBeanFactory::doCreateBean()
// 创建Bean的核心方法
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 实例化Bean
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// 有可能在本Bean创建之前,就有其他Bean把当前Bean给创建出来(比如依赖注入过程中)
// 单例情况下清除缓存。这里保存的是 FactoryBean 和 BeanWrapper 的映射关系。
// factoryBeanInstanceCache是在创建其他bean的时候缓存了一下FactoryBean 。
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
// 如果没有缓存,则重新创建
if (instanceWrapper == null) {
// 1. 创建Bean实例:根据指定的bean使用对应的策略创建新的实例。如:工厂方法、构造函数自动注入,简单初始化
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 获取bean实例
Object bean = instanceWrapper.getWrappedInstance();
// 获取bean类型
Class<?> beanType = instanceWrapper.getWrappedClass();
// 将目标类型替换成实际生成的类型.纠正了上面说到类型错误(如果存在)
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 2. 调用 MergedBeanDefinitionPostProcessor 后处理器,后置处理合并后的BeanDefinition
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
// 调用MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition()后处理器的方法。
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// 3. 判断是否需要提早曝光:单例 & 允许循环依赖 & 当前bean已经正在创建中
// 由于当前bean已经在创建中,本次创建必然是循环引用造成的,所以这里判断是否可以需要提前曝光
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 4. 为避免后期循环依赖,在bean初始化完成前将创建实例的ObjectFactory加入工程 -- 解决循环依赖:添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 5.对bean进行属性填充,将各个属性值注入,其中如果存在依赖于其他bean的属性,则会递归初始依赖bean
populateBean(beanName, mbd, instanceWrapper);
// 调用初始化方法,比如Aware接口的实现,init-method、InitializingBean属性等。
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
// 6. 进行循环依赖检查
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
// earlySingletonReference只有在检测到有循环依赖的情况下才会不为空
if (earlySingletonReference != null) {
// 如果exposedObject没有在初始化方法中被改变,也就是没有被增强
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
// 检测依赖
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 因为bean创建后其所依赖的bean一定是已经创建了的。actualDependentBeans不为空说明当前bean创建后其依赖的bean却没有全部创建完,也就说说存在循环依赖。
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
// 7.根据Scopse 注册bean
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
添加三级缓存的方法如下:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存中不存在此对象
this.singletonFactories.put(beanName, singletonFactory); // 添加至三级缓存
this.earlySingletonObjects.remove(beanName); // 确保二级缓存没有此对象
this.registeredSingletons.add(beanName);
}
}
}
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
通过这段代码,我们可以知道 Spring 在实例化对象的之后,就会为其创建一个 Bean 工厂,并将此工厂加入到三级缓存中。
因此,Spring 一开始提前暴露的并不是实例化的 Bean,而是将 Bean 包装起来的 ObjectFactory。为什么要这么做呢?
这实际上涉及到 AOP,如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是 Spring 一开始并不知道 Bean 是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象?
Spring 的做法就是在ObjectFactory
中去提前创建代理对象。它会执行getObject()
方法来获取到 Bean。实际上,它真正执行的方法如下:
/** 给调用者一次机会,主要就是调用了SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()方法。将getEarlyBeanReference方法的返回值作为提前暴露的对象。
* 我们可以通过实现getEarlyBeanReference()方法来替代Spring提前暴露的对象
* Aop就是在这里将Advice动态织入bean中,若没有bean则直接返回bean,不做任何处理
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 当前类并非合成类 && 在hasInstantiationAwareBeanPostProcessors中
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
因为提前进行了代理,避免对后面重复创建代理对象,会在 earlyProxyReferences 中记录已被代理的对象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 记录已被代理的对象
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
通过上面的解析,我们可以知道 Spring 需要三级缓存的目的是为了在没有循环依赖的情况下,延迟代理对象的创建,使 Bean的创建符合 Spring 的设计原则。
- 如何获取依赖
我们目前已经知道了 Spring 的三级依赖的作用,但是 Spring 在注入属性的时候是如何去获取依赖的呢?
他是通过一个getSingleton()方法去获取所需要的 Bean 的。
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 尝试从单例缓存(一级缓存) singletonObjects 中获取完整的Bean。
Object singletonObject = this.singletonObjects.get(beanName);
// 如果单例缓存(一级缓存)中没有对象,创建中的Bean的名字会被保存在singletonsCurrentlyInCreation中
// 也就是说,当前所需要获取的bean是否是singleton的,并且处于创建中的形态
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 如果单例缓存中不存在该bean,则加锁进行接下来的处理
// 这里作为锁还有一个原因是二级缓存和三级缓存都是HashMap,需要一个锁来控制这两个map的操作
synchronized (this.singletonObjects) {
// 尝试从二级缓存earlySingletonObjects中获取半成品的Bean, 则直接将singletonObject返回。
// 二级缓存存储的是未对属性进行添加的Bean.
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果还获取不到,并且allowEarlyReference为true,则表示可以进行循环引用
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存singletonFactories这个ObjectFactory实例的缓存中尝试获取创建此Bean的单例工厂实例
// ObjectFactory为用户定制(容器中的代理Bean),FactoryBean框架会进行特殊处理(自定义)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用单例工厂的getObject方法获取对象实例
singletonObject = singletonFactory.getObject();
// 将实例放入二级缓存中.
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中删除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
当 Spring 为某个 Bean 填充属性的时候,它首先会寻找需要注入对象的名称,然后依次执行 getSingleton()
方法得到所需注入的对象,而获取对象的过程就是先从一级缓存中获取,一级缓存中没有就从二级缓存中获取,二级缓存中没有就从三级缓存中获取,如果三级缓存中也没有,那么就会去执行doCreateBean()
方法创建这个 Bean。
五、解决循环依赖必须要三级缓存吗
我们现在已经知道,第三级缓存的目的是为了延迟代理对象的创建,因为如果没有依赖循环的话,那么就不需要为其提前创建代理,可以将它延迟到初始化完成之后再创建。
既然目的只是延迟的话,那么我们是不是可以不延迟创建,而是在实例化完成之后,就为其创建代理对象,这样我们就不需要第三级缓存了。因此,我们可以将addSingletonFactory() 方法进行改造。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存中不存在此对象
object o = singletonFactory.getObject(); // 直接从工厂中获取 Bean
this.earlySingletonObjects.put(beanName, o); // 添加至二级缓存中
this.registeredSingletons.add(beanName);
}
}
}
这样的话,每次实例化完 Bean之后就直接去创建代理对象,并添加到二级缓存中。测试结果是完全正常的,Spring的初始化时间应该也是不会有太大的影响,因为如果 Bean 本身不需要代理的话,是直接返回原始 Bean 的,并不需要走复杂的创建代理 Bean 的流程。
- 结论
测试证明,二级缓存也是可以解决循环依赖的。为什么 Spring 不选择二级缓存,而要额外多添加一层缓存呢?
如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理,而Spring 的设计原则是在 Bean 初始化完成之后才为其创建代理。所以,Spring 选择了三级缓存。但是因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。
六、无法解决的循环依赖问题
- 在主bean中通过构造函数注入所依赖的bean。
如下controller为主bean,service为所依赖的bean:
@RestController
public class AccountController {
private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
private AccountService accountService;
// 构造函数依赖注入
// 不管是否设置为required为true,都会出现循环依赖问题
@Autowire
// @Autowired(required = false)
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
}
@Service
public class AccountService {
private static final Logger LOG = LoggerFactory.getLogger(AccountService.class);
// 属性值依赖注入
@Autowired
private AccountController accountController;
}
启动打印如下:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| accountController defined in file [D:\IDEA\learnspace\spring-framework\spring-debug\src\main\java\com\wts\FactoryBeanTest\AccountController.java]
↑ ↓
| accountService (field private com.wts.FactoryBeanTest.AccountController com.wts.FactoryBeanTest.AccountService.accountController)
└─────┘
如果是在主bean中通过属性值或者setter方法注入所依赖的bean,而在所依赖的bean使用了构造函数注入主bean对象,这种情况则不会出现循环依赖问题。
@RestController
public class AccountController {
private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
// 属性值注入
@Autowired
private AccountService accountService;
}
@Service
public class AccountService {
private AccountController accountController;
// 构造函数注入
@Autowired
public AccountService(AccountController accountController) {
this.accountController = accountController;
}
}
- 总结
- 当存在循环依赖时,主bean对象不能通过构造函数的方式注入所依赖的bean对象,而所依赖的bean对象则不受限制,即可以通过三种注入方式的任意一种注入主bean对象。
- 如果主bean对象通过构造函数方式注入所依赖的bean对象,则无论所依赖的bean对象通过何种方式注入主bean,都无法解决循环依赖问题,程序无法启动。(其实在主bean加上@Lazy也能解决)
原因主要是主bean对象通过构造函数注入所依赖bean对象时,无法创建该所依赖的bean对象,获取该所依赖bean对象的引用。因为如下代码所示。
创建主bean对象,调用顺序为:
1.调用构造函数,2. 放到三级缓存,3. 属性赋值。其中调用构造函数时会触发所依赖的bean对象的创建。
// bean对象实例创建的核心实现方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 省略其他代码
// 1. 调用构造函数创建该bean对象,若不存在构造函数注入,顺利通过
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 2. 在singletonFactories缓存中,放入该bean对象,以便解决循环依赖问题
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 3. populateBean方法:bean对象的属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 省略其他代码
return exposedObject;
}
createBeanInstance
是调用构造函数创建主bean对象,在里面会注入构造函数中所依赖的bean,而此时并没有执行到addSingletonFactory
方法来添加主bean对象的创建工厂到三级缓存singletonFactories
中。故在createBeanInstance
内部,注入和创建该主bean对象时,如果在构造函数中存在对其他bean对象的依赖,并且该bean对象也存在对主bean对象的依赖,则会出现循环依赖问题,原理如下:
主bean对象为A,A对象依赖于B对象,B对象也存在对A对象的依赖,创建A对象时,会触发B对象的创建,则B无法通过
三级缓存机制获取主bean对象A的引用(即B如果通过构造函数注入A,则无法创建B对象;如果通过属性注入或者
setter方法注入A,则创建B对象后,对B对象进行属性赋值,会卡在populateBean方法也无法返回)。 故无法创建主
bean对象所依赖的B,创建主bean对象A时,createBeanInstance方法无法返回,出现代码死锁,程序报循环依赖错
误。