Spring中怎么解决循环依赖?

本文深入探讨了Spring框架中循环依赖的原理,解释了构造注入如何引发循环依赖错误,并提出了三种解决方案,包括允许循环依赖、使用非构造注入和提供无参构造函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前文

在写Spring之getBean
的时候提到过在这个过程中要解决循环依赖。

什么是循环依赖?

A类依赖B类,B类依赖A类。 这就是循环依赖。
如下就是一段在Spring中会造成循环依赖的代码

@Component
public class A {
    private B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }
}
@Component
public class B {
    private A a;
    @Autowired
    public B(A a) {
        this.a = a;
    }
}

启动后会报错:error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

哪儿在抛错?

跟踪源码查看一下为何会报错。

Spring之getBean
知道了启动的时候会去创建所有非lazy的单实例bean

主要跟踪getSingleton:
1.没有已经加载好的实例,那么准备创建了。
2.创建前先使用beforeSingletonCreation(beanName);去检查是否【正在创建】,抛出循环依赖的异常。

	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
		//关注:从【一级缓存】中拿【已经加载好的】bean实例
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				//关注:先检查一下是否已经在创建。 就是 在这里检查了循环依赖。如果没有就添加【正在创建】标志
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					//关注:创建实例过程--调用createBean
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					//关注:移出【正在创建】标志
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

但是为何会抛错呢?

debug跟一下。 发现在第getBean(“a”)的时候,会有如下步骤:
1.getBean(“a”)。
2.标记A【正在创建】。
3.得到A的构造函数,尝试去创建A的实例。
4.在创建A的实例之前,要得到构造函数的参数B的实例。
5.getBean(“b”)。
6.标记B【正在创建】。
7.重复步骤2-3. 不过此时是得到B的构造函数,并尝试获取A的实例。
8.getBean(“a”) 。ps:注意,这里是第二次getBean(“a”)了。
9.再次标记A【正在创建】中。—标记失败,抛出异常。

具体过程参考下图:
ps:网图改了一下
在这里插入图片描述
由此,知道为何构造注入会引起循环依赖的时候抛错。
如果对这个注入过程不熟悉的,可以参考我之前的Spring之getBean

那么如何避免抛错呢?

1.允许循环依赖(默认是允许的)。①
2.使用非构造注入,如@Autowired写在成员变量或者setter方法上。
3.要有无参构造函数,或者是不包含被依赖的成员变量的构造函数(避免在一开始注入的时候就需要一个拥有完整成员变量的B)。

例如之前的例子:

开始A实例的创建
1.getBean(“a”)->标记A【正在创建中】。
2.创建A的实例的时候,用无参构造创建一个A的实例a。
3.将半成品(无成员变量)a放到三级缓存中。②
4.populate的时候,尝试注入成员变量b。


开始B的实例创建

  5.getBean(“b”)创建一个B的实例>标记B【正在创建中】
  6.B通过createBeanInstance创建实例后,
  7.将半成品(无成员变量)b放到三级缓存中。
  8. populate去注入成员变量a.
  9.getBean(“a”)->getSingleton(“a”, true)->从一级缓存(singletonObjects)中取->取不到则从二级缓存(earlySingletonObjects)中取->取不到则从三级缓存(singletonFactories)中取->从三级缓存中取出ObjectFactory得到A的实例a,将a放入二级缓存,并将ObjectFactory从三级缓存中移出。③
  10.得到了a的实例(半成品),注入到b中。继续走完B创建。
b实例的创建完成


继续A实例的创建
11.将完成品B的实例b,注入到A中。 继续走完A创建。


非构造注入整个流程
ps:偷来的图
在这里插入图片描述

①不允许循环依赖当然要报错
在这里插入图片描述

②将半成品(无成员变量)a放到三级缓存中

将半成品(无成员变量)a放到三级缓存中在这里插入图片描述

③从三级缓存中取出ObjectFactory得到A的实例a,将a放入二级缓存,并将ObjectFactory从三级缓存中移出

在这里插入图片描述

为何是三级缓存?

个人认为,第三级缓存的作用, 是留了一个钩子,方便【获取半成品实例】的时候做一些定制。(如abstractAutoProxyCreator会利用该位置去提前创建代理对象)
如下源码:实现SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference可以完成该定制。

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

三级缓存钩子作用举例

//AbstractAutoProxyCreator#getEarlyBeanReference 提前生成代理对象

    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return this.wrapIfNecessary(bean, beanName, cacheKey);
    }
//根据earlyProxyReferences.remove可以看出创建实例完成后就不会重复生成代理对象了
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return this.wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;
    }

参考资料

https://blog.csdn.net/f641385712/article/details/92801300
https://blog.csdn.net/weixin_42228338/article/details/97163101

源码深度解析是一种深入研究源代码的方法,通过仔细阅读和理解源代码中的细节和逻辑,以获得对代码的深刻理解和洞察。这样的分析可以帮助开发者更好地理解代码的实现方式,从而更好地理解并使用该代码库。 关于spring如何解决循环依赖的问题,我们可以从源码的角度来分析。Spring采用了三级缓存来解决循环依赖的问题。 第一级缓存是singletonFactories缓存,用于存储正在创建的Bean的工厂对象。当容器正在创建一个Bean时,会将这个Bean的工厂对象存储在singletonFactories缓存中。 第二级缓存是earlySingletonObjects缓存,用于存储已经完成了属性填充但尚未初始化完成的Bean。当容器创建一个Bean时,会将正在创建的Bean存储在earlySingletonObjects缓存中。 第三级缓存是singletonObjects缓存,用于存储已经完成初始化的Bean。当一个Bean初始化完成后,会将其存储在singletonObjects缓存中。 Spring在创建Bean的过程中,会先查找一级缓存,如果找到了对应的工厂对象,则直接返回该对象,避免了创建过程中的循环依赖。如果一级缓存中没有找到对应的工厂对象,则通过递归的方式创建依赖的Bean。 在创建Bean的递归过程中,如果发现正在创建的Bean已经在二级缓存中,说明发生了循环依赖。此时,Spring会从二级缓存中获取正在创建的Bean的代理对象,以解决循环依赖。 当一个Bean创建完成后,会将其放入三级缓存中,并从一级缓存和二级缓存中移除。 总结来说,Spring通过三级缓存的方式解决循环依赖的问题,保证了Bean的创建过程中不会陷入无限递归的循环。这种机制的实现使得Spring解决循环依赖问题上具有较好的性能和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值