19. spring-容器: 如何解决Spring的循环依赖问题

该问题由上一章引出,上一章跟踪了spring实例化bean的整个过程。 中间出现了疑似用于解决bean循环依赖的问题,这个问题在面试中考官会经常问,能完美的答出来是个不错的加分项。

什么是循环依赖?

即循环引用,即两个或两个以上的bean相互引入。 如 A --> B --> C --> A。 类似代码结构:

public class A {
	private B b;
}

public class B {
	private C c;
}

public class C {
	private C a;
}

从类示例能看的出来。是A依赖B, B依赖C,然后C又反过来依赖A。这种依赖关系其实是个死循环,除非有终结条件。

在spring中存在循环依赖的场景有:
1)构造器的循环依赖
2)field属性的循环依赖

其中构造器的循环依赖无解,容器检测到此种情况会抛出BeanCurrentlyInCreationException异常。原因是构造器方式注入时要求先注入目标bean再实例化。当递归到实例化C实例时,发现无法注入A实例。

而第二种field属性的循环依赖可以解决,spring采用的是提前暴露对象的方式。

如何检测是否存在循环依赖?

spring容器是在bean创建的时候给bean打上标签,如果递归回来发现bean正在创建中时,就说明存在循环依赖了。

field属性的循环依赖

先回顾一下经典bean的生命周期图,如下:
在这里插入图片描述

从图中可知,是先实例化bean,再设置对象属性。所以spring解决field方式的循环依赖时具备了可行性。
spring实例化A后会将对象放到一个Map中,并且spring提供了获取该实例化bean的引用方法(虽然此时的bean还未设置好属性,但已经可以引用了)。以之前的A,B,C三个类为例,当spring依次实例化了A,B,C三个类的实例后,会开始设置他们的实例属性,因为A依赖B,所以设置A类中B属性时,就会从Map中去获取引用,这样就不会存在循环引用问题。

解决循环依赖的原理

它的理论依据是上面提的:实例化和设置实例属性可以分开。实例化后就可暴露引用。

spring实例化singleton bean主要分为三步:

  • createBeanInstance(实例化bean)
  • populateBean(填充属性)
  • initializeBean(初始化)

注:后面代码跟踪时还能看到

createBeanInstance:实例化,即通过构造方法实例化对象
populateBean: 填充实例属性
initializeBean: 执行初始化方法和Bean后置处理器

我们要解决循环依赖问题就是在第一和第二阶段。对于sington来说,在容器的整个生命周期中有且仅有一个对象,所以很自然会想到应该缓存起来供后面复用。spring为了解决循环依赖问题,使用了三级缓存,如下:

  • 一级:singletonObjects,单例对象的缓存
  • 二级:earlySingletonObjects,提前曝光的单例对象缓存
  • 三级:singletonFactories,对象工厂的缓存

在创建Bean的时候,首先想到的是从一级缓存(singletonObjects)中获取,该缓存是真正存实例化bean的地方,其他两个都是临时的。

如果获取不到,并且对象正在创建中,就再从二级缓存(earlySingletonObjects)中获取。如果还是获取不到,再从三级缓存(singletonFactories)中获取。如果获取到了则从三级中移除,转移到二级中。

等完全初始化完成了,再从二级缓存移到一级缓存中。

解决循环依赖的原理(代码跟踪)

上面从整体上描述了解决循环依赖的原理,本节从代码层面跟踪容器解决循环依赖的具体实现,加深印象

AbstractAutowireCapableBeanFactory.doCreateBean方法

	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			// 1. 实例化bean
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = instanceWrapper.getWrappedInstance();
		
		
		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		// 意思很明确:生成实例后,填充属性和执行初始化前做缓存,用于解决循环依赖
		// 判断是否需要提前缓存bean是这几个条件:
		// 1)本身是singleton; 2) 容器允许bean循环引用(默认允许)3)当前bean是否正在创建中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			// 添加到SingletonFactories三级缓存,下面会展开
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			// 2. 填充属性
			populateBean(beanName, mbd, instanceWrapper);
			
			// 3. 执行初始化和bean后置处理器
			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);
			}
		}
		
		// 如果是提前暴露bean引用的情况,先从一级缓存(singletonObjects)中取,再从earlySingletonObjects中取,最后从singletonFactories中取。 细节下面再展开
		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				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);
						}
					}
					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 " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}
DefaultSingletonBeanRegistry.addSingletonFactory

当bean刚实例化出来后,容器会判断该bean是否需要提前缓存,判断条件:

  • 是单例
  • 容器允许循环引用
  • 当前的bean还在创建中

当满足时,执行:

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);
			}
		}
	}
AbstractBeanFactory.doGetBean

还是假设 A -> B -> C -> A的依赖情况,当从实例化后填充属性需要获取A实例时,又回调到以下方法(省略了当前不关心的部分):


	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

		final String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		// 提起检查该bean是否已经存在,此时就是会从三个缓存中找,跟进去看看是不是
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		// ignore...
		
		return (T) bean;
	}
DefaultSingletonBeanRegistry.getSingleton

在该方法中执行了三层缓存的查询逻辑,跟之前的猜想一致

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;
}
小结

本节系统讨论了spring中如何解决循环依赖问题,从理论到源码都过了一遍,希望能在面试中遇到此类问题时从容一些。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值