文章目录
-
- 环境
- 前言
- 什么是循环依赖?
- 所有注入场景的循环依赖Spring都能解决吗?
- 场景
-
- TestA.java
- TestB.java
- spring-context.xml
- Main.java
- 测试结果
- Spring是怎么解决循环依赖的?
-
- 前置说明
- 源码解析
-
- addSingletonFactory
- getSingleton
- getEarlyBeanReference
- 总结
环境
Spring Framework Version: 5.3.x
Gradle Version:7.5.1
前言
看这篇文章之前,建议先看看SpringBean生命周期这篇文章,如果对生命周期已经有了了解,那么可以跳过,直接看这篇文章
什么是循环依赖?
简单理解就是A,B 两个bean相互依赖,A依赖B,B依赖A
A->B、B->A大概就是这样.
所有注入场景的循环依赖Spring都能解决吗?
答案是 “不”, Spring不能够解决循环依赖的构造器注入,其它的注入方式都能解决
**注意:**能解决非构造器注入的循环依赖的前提是开启允许循环依赖(allowCircularReferences = true),在spring中默认开启,如果是在springboot2.x中,那么是默认关闭的
场景
我们来编写一段代码,模拟一下循环依赖场景
TestA.java
public class TestA { private TestB testB; public void setB(TestB testB) { this.testB = testB; } public void testCircularReference() { System.out.println("TestA bean register success..."); } }
TestB.java
public class TestB { private TestA testA; public void setA(TestA testA) { this.testA = testA; } public void testCircularReference() { System.out.println("TestB bean register success..."); } }
spring-context.xml
//使用的注入方式是自动装配,根据type自动装配 <bean id="testA" class="com.spring.demo.circularreference.TestA" autowire="byType"></bean> <bean id="testB" class="com.spring.demo.circularreference.TestB" autowire="byType"></bean>
Main.java
public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); TestA testA = context.getBean(TestA.class); TestB testB = context.getBean(TestB.class); testA.testCircularReference(); testB.testCircularReference(); } }
测试结果
a bean register success... b bean register success...
好,代码编写完毕,测试也没问题,接下来我们来进行源码解析
Spring是怎么解决循环依赖的?
前置说明
Spring是通过三级缓存来解决循环依赖的,我们来看一张图片,认识一下三级缓存
一级缓存(singletonObjects):存放初始化完成的bean
二级缓存(earlySingletonObjects):存放实例化但未初始化的bean
三级缓存(singletonFactories):存放对象工厂,也就是把实例化但未初始化的bean包装为ObjectFactory
认识了三级缓存后,我们来剖析一下Spring是怎么利用三级缓存来解决循环依赖的
在实例化TestA之后,我们先会把TestA添加到三级缓存,然后进行属性填充,给TestB进行依赖注入,那么到TestB属性填充时,发现TestB也依赖TestA,这个时候去注入TestA,先从一级缓存获取,获取不到,再从二级缓存获取,二级缓存也没有,这个时候从三级缓存获取,获取到了,然后getObject()生成TestA对象(在这个时候,如果有代理,那么生成代理对象,如果没有代理,直接返回原本的对象),获取到TestA对象后移到二级缓存并返回,这个时候TestB就成功注入了TestA,然后TestB顺利的走完生命周期,就回到了第一个TestA,进行初始化,初始化完之后,再从二级缓存中取出再重新赋值(为了保证bean是同一个),最后添加到一级缓存中
这么说可能有点绕,我们结合一张图来看看
大概就是这样
- 添加半成品的TestA(实例化但没进行属性注入与初始化)到三级缓存,然后注入TestB,在TestB中也要进行属性注入,然后就去注入TestA
- 在TestB中注入TestA时,直接从缓存获取,因为这时三级缓存已经存在TestA,然后调用getEarlyBeanReference方法生成对象(如果有代理也是在此生成)
- 添加到二级缓存中并删除三级缓存,最后返回,TestB属性注入完成,继续走Bean的生命周期
- 回到原本TestA,这时TestB注入已经完成,然后初始化,最后从二级缓存中获取最新的bean,避免不是同一个对象(代理对象)
源码解析
addSingletonFactory
? TestA在实例化之后添加三级缓存
?
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); } } }
getSingleton
TestB属性注入TestA过程中,从缓存获取TestA
在这里会获取到早期对象,移除三级缓存中的ObjectFactory
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 从一级缓存中获取TestA,这个时候是没有的 Object singletonObject = this.singletonObjects.get(beanName); //isSingletonCurrentlyInCreation 在实例化之前就添加了创建标识,所以这里为true if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //从二级缓存中获取TestA,这个时候二级缓存也是没有的 singletonObject = this.earlySingletonObjects.get(beanName); //allowEarlyReference 是否允许循环依赖 if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { //双重检查 singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { //从三级缓存中获取ObjectFactory ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //获取早期暴露对象(代理对象也是在此生成) singletonObject = singletonFactory.getObject(); //添加到二级缓存中 this.earlySingletonObjects.put(beanName, singletonObject); //根据beanName从三级缓存中移除ObjectFactory this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { //遍历smartInstantiationAware类型的后处理器 for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { //获取早期bean引用 exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
此时获取到的TestA是个代理对象,那么TestB里注入的是代理对象,然后TestA成功注入TestB,但是原本的TestA还是个普通的对象,怎么办呢?
很简单,之前不是将对象放到二级缓存了吗,所以在TestA注入完TestB并且初始化之后,这个时候会去二级缓存中获取最新的bean,并重新赋值,保证是同一个对象,看代码
//二级缓存提前暴露 if (earlySingletonExposure) { //从二级缓存中获取到最新的bean Object earlySingletonReference = getSingleton(beanName, false); //如果能获取到并且exposedObject和实例化之后的bean是保持一致的,那么就进行重新赋值 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."); } } } }
至此,循环依赖就解析完毕了,这里有个需要注意的点,就是生成代理对象那一块,需要依赖于SmartInstantiationAwareBeanPostProcessor生成代理的,是不能被处理的,例如@Async注解,它需要依赖于SmartInstantiationAwareBeanPostProcessor,被它代理的类,生成代理时机是初始化bean之后,那么如果在循环依赖里出现,例如TestA、TestB互相依赖,那么TestA使用了@Async注解,那么它的代理生成时机在bean的初始化之后,这样就会出现问题了,在TestB注入TestA时,从缓存中获取TestA,这时是没有被代理的,当原始的TestA注入完成后,在初始化之后生成代理,这个时候就会造成TestB里注入的TestA不是代理对象,而原始的TestA已经变成代理对象了,就会造成不是同一个对象
总结
看完源码之后,相信大家都有了一些了解,如果看完还是不太明白也没关系,自己跟着debug一遍然后做总结,加深印象,接下来我们对以上做一下总结吧。
如果被问到Spring是如何解决循环依赖的?
答: Spring是通过三级缓存去解决的循环依赖,具体来说就是在TestA实例化之后,属性填充之前,把Test包装成ObjectFactory对象并存入三级缓存中,这时注入TestB,然后在TestB里注入TestA时,就会从三级缓存里getObject,取出TestA半成品对象(如果是代理对象就进行创建),并且配合二级缓存,把它存入二级缓存中并在三级缓存中删除,最后回到原始TestA,在初始化原TestA之后,进行重新赋值,避免不是同一个对象。
为什么构造器注入不能解决循环依赖?
**答:**因为构造器注入是在实例化bean的时候,这时候三级缓存还没有添加,所以不能解决循环依赖。
为什么要设计三级缓存,一级、二级缓存行不行?
这个问题非常值得思考,不过不要陷入其中,大家可以思考一下