Spring源码之循环依赖


前言

上篇我们讲了bean的初始化流程,要搞懂循环依赖,必须要先清除流程,不清楚的请移步上一篇文章。
这篇我们就来讲讲Spring是如何解决循环依赖的


一、什么是循环依赖

循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:
在这里插入图片描述
循环依赖,其实就是一个死循环的过程,在初始化 A 的时候发现引用了 B,这时就会去初始化 B,然后又发现 B 引用 C,跑去初始化 C,初始化 C 的时候发现引用了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件。

1. 循环依赖有几种情况

有两种

  • 基于构造函数的注入方式的循环依赖
  • 基于属性注入的循环依赖

Spring只能解决属性注入的循环依赖,构造函数注入方式的循环依赖是没法解决的,原因等我们分析完循环依赖后再来个总结.
其实倒不是说不能使用构造函数注入,而且Spring从4.3开始推荐使用构造函数注入的方式,举例具体来说
A 依赖 B, B又依赖A
下面这几种情况

  • A中采用构造函数注入依赖B, B中采用构造函数依赖A, 无法解决
  • A中采用构造函数注入依赖B, B中采用属性注入A, 可以解决
  • A中采用属性注入依赖B, B中采用构造函数依赖A 无法解决
  • A中采用属性注入依赖B. B中采用属性注入A 可以解决

细心的异能已经发现了,只要B中采用构造函数注入A,就无法解决,A采用什么方式则没什么影响
下面我们以A,B 都采用属性注入的方式且无AOP代理的情况下来看Spring是如何解决循环依赖的,这也是最常见的方式

二、循环依赖的解决

1.getSingleton(beanName)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

话不多说,直接上图,看不清的点击放大来看
转载注明出处
但是这只是最简单的循环依赖的一种情况, 有AOP的会更加复杂,以后有机会再来更新

2 为什么不能解决构造函数注入方式的循环依赖

如果熟悉了循环依赖的解决流程,那么这个问题自然也很好回答, 因为如果采用构造函数注入的方式,那么会去看构造函数的参数是否初始化,如果没有会初始化这个bean,如此一来,便陷入了死循环,没法提前曝光半初始化的bean了,
说的简单点就是,指定了构造函数就不会走默认的无参构造,就无法走下面的提前曝光,加入三级缓存,也就无法解决循环依赖了

2.疑问

这个疑问我在上篇文章中提过,就是这段代码

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 " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

这里也与循环依赖有关
//到这里又会走getSingleton(beanName, false),是不是很熟悉,没错就是我们进doGetBean时一开始会走的从缓存中取bean的方法
//不过之前传的是true,这里传的是false
//先提前说明下这个方法的流程,举例说明

//对于循环依赖: A依赖B, B依赖A,且属性注入(构造函数注入无法解决嘛)
//初始化A, 走到填充属性时,会去初始化B, 由于B中又依赖的A, 又去getBean(A), 此时就会走 getSingleton(beanName, true)
//而此时容器中一,二级缓存中没有A实例的,只有三级缓存中有,就会把三级缓存中那个半初始化的beanA取出(注意:此时会把A放在的二级缓存中,从三级缓存中移除)
//然后B的属性填充完毕, 然后走初始化完成, 当B走到下面这段代码时, 即走到getSingleton(beanName, false)时, 一二级缓存均为空,
//但是传参是false,所以Object earlySingletonReference 为null,那么返回的就是初始化完成的exposedObject
//即exposedObject = initializeBean(beanName, exposedObject, mbd);
// 最后后会把初始化好的B放入一级缓存中
//然后继续走之前的A属性填充(留意一下,A填充好了),初始化,但是当A走到这段代码时,因为之前往二级缓存中放了A,
//所以此时取二级缓存不为空,会返回缓存中的这个引用,而且如果这个引用和增强后的bean是一个对象实例,那么会返回这个引用

//此时有人可能会有疑问: 缓存中的这个bean不是半初始化的么,怎么返回使用了呢?
//这是因为,二级缓存中存放的是A的引用,在之前这个引用指向的对象实例被填充了啊,这就是之前让你留意一下的原因

//不过读到这里我有个疑问: 为什么Spring要再去缓存中取一下这个bean呢?我直接使用初始化返回的那个bean嘛,就是exposedObject,
//为什么要现在这样设计呢?还查下缓存,然后再比较bean有没有变,变了返回exposedObject, 没变返回缓存中的bean
//我看有的博客说的是此处代码的作用是: 循环依赖检查,判断是否需要抛出异常
//存疑?有知道的小伙伴可以留言告诉我,我感觉这里是和AOP代理有关

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值