spring5源码篇(7)——循环依赖

文章详细阐述了SpringFramework在处理循环依赖时的一级、二级和三级缓存机制,特别是早期单例对象的提前暴露和AOP代理在其中的作用。此外,提到了多例模式、构造器注入以及@Async注解的循环依赖问题,并指出@Lazy注解如何作为解决方案。
摘要由CSDN通过智能技术生成

spring-framework 版本:v5.3.19

getSingleton

众所周知,getBean时会先去单例池是否已经存在,如果存在则直接返回。但其实不仅仅只会在单例池找,getSingleton方法如下。
DefaultSingletonBeanRegistry#getSingleton
在这里插入图片描述
在getSingleton时,会先去单例池也就是一级缓存找,如果不存在就去二级缓存找,如果还是不存在就去三级缓存找。

singletonObjects 一级缓存,单例池
earlySingletonObjects 二级缓存,存放提前暴露的半成品bean
singletonFactories 三级缓存,存放一个lambda表达式,如果没有aop表达式返回其本身,如果有aop表达式返回aop后的代理对象

目前我们只知道一级缓存是单例池,那么其他两个缓存都存放了什么?

二三级缓存

三级缓存
看到doCreateBean方法,在实例化bean后,spring会检查,如果是单例且发生了循环依赖就会提前暴露bean,将一个lambda表达式存到三级缓存(注意这里只是存了lambda,并不会执行)。


	protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		
		//....
		
		//实例化bean
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}	
		
		//...
		
		//提前暴露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");
			}
			//存放lambda表达式到三级缓存
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
		
		//...
	}

lambda表达式 ——() -> getEarlyBeanReference(beanName, mbd, bean)
getEarlyBeanReference

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

这个lambda表达式的内容就是执行SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法,而在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,该类就是用来进行AOP的。所以这里可以理解成,三级缓存存放了一个lambda表达式,如果没有aop表达式返回其本身,如果有aop表达式返回aop后的代理对象


二级缓存
其实从一开始的getSingleton方法中也可以看出,当一二级缓存不存在时,就会去三级缓存找,找到了就执行三级缓存里的lambda表达式,然后将返回值存到二级缓存。所以二级缓存存放的就是三级缓存中lambda表达式执行后的返回值,无论表达式返回其本身还是aop后的代理对象,当前这个bean都是不完整的,也就是一个半成品bean。


循环依赖解决流程

所以假如 A,B互相依赖

public class A{
	@Autowired
	private B b;
}

public class B{
	@Autowired
	private A a;
}

整个循环依赖的解决流程大概就是

1、createBean A的时候把一个aop的lambda表达式存到三级缓存
2、getBean B 的时候先从一级缓存找,没有就去二级,还是没有就去三级,此时返回null,执行createBean B
3、同理,createBean B 的时候会执行到 getBean A。先去一级缓存找,没有就去二级,也没有就去三级。此时三级缓存有A,就会执行lambda表达式,得到一个A的早期对象放到二级缓存并移除三级缓存,接着继续创建B。
4、B创建完之后放到一级缓存(单例池)并移除二级缓存,然后A创建继续往下走,最后A也创建完成并放到一级缓存并移除二级缓存

实际上,从代码上看,添加到一级缓存的时候会同时remove二三级缓存


什么时候会从二级缓存获取

当bean在初始化期间,如果出现循环依赖,首先会从三级缓存取出lambda表达式把执行结果存入二级缓存。注意,一开始这里只是存入二级缓存,并没有从二级缓存获取。那么什么时候会从二级缓存获取?当下一次再次循环依赖这个bean的时候就可以直接从二级缓存获取对应的bean了。
如:

public class A{
	@Autowired
	private B b;
	@Autowired
	private C c;
}

public class B{
	@Autowired
	private A a;
}

public class C{
	@Autowired
	private A a;
}

首先注入b.a时就会从三级缓存获取lambda并执行将结果存入二级缓存,后面再注入c.a时就可以直接从二级缓存获取了。
那能不能注入c.a时还是从三级缓存执行lambda获取?不能,因为假如有aop的话,这样b.a和c.a注入将会得到两个不一样的代理bean。

为什么需要三级缓存

假如没有aop的话,其实确实可以不用三级缓存,一个map存a实例化后的引用后续注入时直接注入这个引用即可。但是spring为了兼容aop,所以再加了一个三级缓存。因为aop后生成的是一个新的代理对象,而我们真正要注入的是这个代理对象才对。假如还是只有一个map单纯保存实例化后的引用的话,那后续注入的将会是没有aop代理的bean。
竟然如此,那map不要存原始引用,存aop代理后的引用不就可以了。单从解决循环依赖来看的话,这么做也确实是可以。不过有个问题就是不管有没有发生循环依赖,都会把aop代理步骤提前到实例化后,明显不符合bean生命周期,所以spring用一个map(三级缓存),先存一个aop的lambda,只有当发生循环依赖的时候才会把aop步骤提前,且是提前到填充属性的那一步。
竟然如此,那就用两个map,一个存aop的lambda,一个存实例引用不就可以了。单从解决循环依赖来看的话,这么做也确实是可以。不过有个问题就是这样单例池将区分不出哪些是真正已经完成创建的bean,哪些是正在创建的bean。换句话说区分不出半成品和成品,这势必会造成将半成品的bean当成成品bean来使用,从而导致一些异常。


为什么多例模式的循环依赖无法解决

多例模式下的循环依赖就是一个无限套娃,每次注入的时候都会创建一个新的bean,新的bean有需要注入依赖,然后又创建新的bean又依赖…

为什么构造器注入的循环依赖无法解决

spring解决循环依赖是依靠提前暴露一个实例化后的bean引用(注意不是初始化后)以打破循环的。但假如是构造器注入的话, 此时都还没有完成实例化,也更别说提前暴露实例化后的引用了。

为什么带有@Async的循环依赖无法解决

前面说到aop代理是通过 AbstractAutoProxyCreator 来生成代理类的,不过@Async有点特殊,他的aop
是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,AbstractAdvisingBeanPostProcessor 没有实现 SmartInstantiationAwareBeanPostProcessor,所以二级缓存里的半成品还是原始对象而不是提前代理后的对象。

其实上面这些场景都可以通过 @Lazy 懒加载的方式以解决循环依赖。因为懒加载会生成一个懒加载代理对象先去代替这个bean,当后续用到时再真正去创建他,所以一开始不会真正去创建一个bean,也就没有属性填充,没有依赖注入,更别说循环依赖了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值