SpringBoot之IOC容器循环依赖问题

前提假设:类A、B互相依赖,并且先解析类A。

1.DefaultSingletonBeanRegistry

public class DefaultSingletonBeanRegistry {
	/** Cache of singleton objects: bean name --> bean instance */
	/** 一级缓存:用于存放完全初始化好的 bean **/
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
	 
	/** Cache of early singleton objects: bean name --> bean instance */
	/** 二级缓存:存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
	
	/** Cache of singleton factories: bean name --> ObjectFactory */
	/** 三级级缓存:存放 bean 工厂对象,用于解决循环依赖 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
	
	public Object getSingleton(String beanName) {
		//只有此处方能触发解决循环依赖
		return getSingleton(beanName, true);
	}
	//解析类B过程中存在属性填充之类A,此时会将类A添加到二级缓存中
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					//二级缓存 & 三级缓存转换之处
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						//触发循环依赖
						singletonObject = singletonFactory.getObject();
						//添加二级缓存元素的唯一之处
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
	
	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}
}

在这里插入图片描述
allowEarlyReference:参数表示是否需要过早暴露当前bean。首先第三级缓存singletonFactories其value类型为工厂类ObjectFactory,其次该工厂类主要服务于AOP或者事务解析过程中涉及的代理,最后allowEarlyReference控制的就是代理bean的过早暴露。

通常情况下,AOP或者事务涉及的类代理基本都是Spring IOC对该类完成全部初始化之后触发的。所以,第三级缓存singletonFactories完全是针对类A、B需要代理情况下才发挥作用。

类A、B需要代理:如果是AOP则类A、B是切点关注的目标类,如果是事务则类A、B存在事务的核心注解@Transactonal。

如果一个类能过早暴露则表明该类一定充当循环依赖的一方,而且该过早暴露的类基本都是发生了代理,最后必须存在缓存临时存放过早暴露的类。

2.循环依赖涉及的关键流程

2.1.首次尝试获取目标bean

public abstract class AbstractBeanFactory{
	
	protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly){
		String beanName = transformedBeanName(name);
		Object bean;
		//只有此处触发二级缓存添加元素
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			...
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}else {
			...
			RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			...
			if (mbd.isSingleton()) {
				sharedInstance = getSingleton(beanName, () -> {
					return createBean(beanName, mbd, args);
				});
				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
			}
		}
		...
		return (T) bean;
	}
}

如上所示,尝试从IOC容器中获取目标bean实例。获取的bean可能是目标bean也可能是被代理后的bean。

2.2.第三缓存singletonFactories缓存每个bean

不管是2级缓存还是3级缓存都需要在bean实例化后将该实例添加到高级缓存中。

public abstract class AbstractAutowireCapableBeanFactory{
	
	protected Object doCreateBean(final String beanName,RootBeanDefinition mbd, Object[] args){
		
		BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
		Object bean = instanceWrapper.getWrappedInstance()
		...//添加三级缓存元素的唯一之处 ~ 所有bean实例化之后,初始化之前都会被添加三级缓存singletonFactories中
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		...
		Object exposedObject = bean;
		populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
		if (earlySingletonExposure) {
			//解析类B的过程中,二级缓存中只有类A的实例信息。类B解析完成后添加到IOC容器中,并且填充为类A的属性。
			// 后续继续类A的初始化流程,但是此时二级缓存中类A的实例信息earlySingletonReference完全等价于exposedObject、bean
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				//如果类A、类B均需要代理处理。此时earlySingletonReference指向类A的代理对象,
				// exposedObject、bean均指向类A原始的实例对象
				if (exposedObject == bean) {
					// 将类A的代理对象添加到IOC容器中
					exposedObject = earlySingletonReference;
				}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					...
				}
			}
		}
		...
		return exposedObject;
	}
	
	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;
					// 常见只有后置处理器 AbstractAutoProxyCreator 实现方法 getEarlyBeanReference,并且是利用代理机制返回代理对象
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}
}

启动过程中,解析类B时在populateBean属性赋值时需要初始化成员属性类A,此时尝试从IOC容器中获取类A从而引发创建类A的代理类,并将其添加到第二级缓存earlySingletonObjects中,但是在IOC 容器中第一级缓存singletonObjects并没有添加类A的代理类,类B完成成员属性的初始化。类B完成初始化之后,类A继续后续的初始化,只不过类A不会再次生成代理得到的还是类A最初的实例,最后再次尝试从IOC 容器各级缓存中获取目标bean,最终返回第二级缓存earlySingletonObjects中的代理类,并且添加到第一级缓存singletonObjects中。

综上所述:如果IOC容器不存在任何代理则二级缓存也可以解决循环依赖,否则必须存在第三级缓存。

  1. 2级缓存其value必须是工厂类ObjectFactory,三级缓存其value是目标类的代理类。
  2. 循环依赖涉及的代理类会被提前完成代理过程,但是此时还有目标类初始化流程尚未执行。所以当前不能将其代理类直接添加到IOC容器中。
  3. 执行完初始化流程后将类A最初实例的引用替换为二级缓存中对应代理类的引用,最后添加到IOC引用中。

4、Spring为何不能解决非单例Bean的循环依赖

Spring 为何不能解决非单例 Bean 的循环依赖? 这个问题可以细分为下面几个问题

Spring 为什么不能解决构造器的循环依赖?
Spring 为什么不能解决 prototype 作用域循环依赖?
Spring 为什么不能解决多例的循环依赖?

4.1、Spring 为什么不能解决构造器的循环依赖

对象的构造函数是在实例化阶段调用的。

上文中提到,在对象已实例化后,会将对象存入三级缓存中。在调用对象的构造函数时,对象还未完成初始化,所以也就无法将对象存放到三级缓存中。

在构造函数注入中,对象 A 需要在对象 B 的构造函数中完成初始化,对象 B 也需要在对象 A的构造函数中完成初始化。此时两个对象都不在三级缓存中,最终结果就是两个 Bean 都无法完成初始化,无法解决循环依赖问题。

4.2、Spring 为什么不能解决prototype作用域循环依赖

Spring IoC 容器只会管理单例 Bean 的生命周期,并将单例 Bean 存放到缓存池中(三级缓存)。Spring 并不会管理 prototype 作用域的 Bean,也不会缓存该作用域的 Bean,而 Spring 中循环依赖的解决正是通过缓存来实现的。

4.3、Spring 为什么不能解决多例的循环依赖

多实例 Bean 是每次调用 getBean 都会创建一个新的 Bean 对象,该 Bean 对象并不能缓存。而 Spring 中循环依赖的解决正是通过缓存来实现的。

4.4、非单例Bean的循环依赖如何解决

对于构造器注入产生的循环依赖,可以使用 @Lazy 注解,延迟加载。
对于多例 Bean 和 prototype 作用域产生的循环依赖,可以尝试改为单例 Bean。

作者:JavaPub
链接:https://www.zhihu.com/question/388753875/answer/3053314439
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Spring Bean 的循环依赖场景:

  1. 构造器循环依赖:BeanA 的构造器注入 BeanB,BeanB 的构造器又注入 BeanA。
  2. Setter 循环依赖:BeanA 在 setter 方法中注入 BeanB,BeanB 的 setter 方法又注入 BeanA。
  3. 代理循环依赖:BeanA 依赖 BeanB 的代理对象,BeanB 依赖 BeanA 的代理对象。

Spring 解决循环依赖问题的方式:
4. 对代理循环依赖,Spring 会首先创建目标对象,然后再创建代理对象。
5. 对 Setter 循环依赖,Spring 会在对象创建完成后,提前将对象注入到 BeanFactory 中。然后在注入依赖时,直接从 BeanFactory 中获取已经提前实例化的对象。
6. 对构造器循环依赖,Spring 在实例化对象后,会提前暴露一个 ObjectFactory,再从 ObjectFactory 中获取目标对象完成依赖注入。

Spring 循环依赖带来的问题:
7. 破坏 Bean 的单例性。因为循环依赖导致一个 Bean 被实例化多次,破坏了 Spring 容器中 Bean 的唯一性。
8. 容器无法正常结束注入过程。循环依赖会导致 Bean 无法完全实例化,一直在循环注入过程中,无法结束。
9. 降低程序的可读性和维护性。循环依赖关系使得程序难以理解,也难以维护。

避免 Spring 循环依赖方式:
10. 编程方式避免。在 Bean 中提供 set 方法,但不在构造器中注入依赖。这样只有在 Bean 完全实例化后,才会注入依赖对象。构造器注入避免。只使用构造器注入,不用 Setter 方法注入。因为构造器是在 Bean 实例化阶段完成的,避免了循环依赖。
11. 避免过于提前暴露 Bean。如果一个 Bean 不需要频繁使用,不要将其设置为 Singleton,推迟其实例化时间。
12. 分模块避免。将循环依赖的 Bean 拆分到不同的模块中,模块间采用接口隔离,避免循环依赖。
13. 采用重构手段。如果上述方式都不可行,那么需要通过重构来避免循环依赖。比如拆分过于庞大的 Bean 为多个小 Bean 等。

在2.6.0之前,spring会自动处理循环依赖的问题,2.6.0 以后的版本默认禁止 Bean 之间的循环引用,如果存在循环引用就会启动失败报错。解决办法:spring.main.allow-circular-references。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值