Spring Framework Version: 5.3.x
Gradle Version:7.5.1
看这篇文章之前,建议先看看SpringBean生命周期这篇文章,如果对生命周期已经有了了解,那么可以跳过,直接看这篇文章
简单理解就是A,B 两个bean相互依赖,A依赖B,B依赖A
A->B、B->A大概就是这样.
答案是 “不”, Spring不能够解决循环依赖的构造器注入,其它的注入方式都能解决
**注意:**能解决非构造器注入的循环依赖的前提是开启允许循环依赖(allowCircularReferences = true),在spring中默认开启,如果是在springboot2.x中,那么是默认关闭的
我们来编写一段代码,模拟一下循环依赖场景
public class TestA {
private TestB testB;
public void setB(TestB testB) {
this.testB = testB;
}
public void testCircularReference() {
System.out.println("TestA bean register success...");
}
}
public class TestB {
private TestA testA;
public void setA(TestA testA) {
this.testA = testA;
}
public void testCircularReference() {
System.out.println("TestB bean register success...");
}
}
//使用的注入方式是自动装配,根据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>
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是通过三级缓存来解决循环依赖的,我们来看一张图片,认识一下三级缓存
一级缓存(singletonObjects):存放初始化完成的bean
二级缓存(earlySingletonObjects):存放实例化但未初始化的bean
三级缓存(singletonFactories):存放对象工厂,也就是把实例化但未初始化的bean包装为ObjectFactory
认识了三级缓存后,我们来剖析一下Spring是怎么利用三级缓存来解决循环依赖的
在实例化TestA之后,我们先会把TestA添加到三级缓存,然后进行属性填充,给TestB进行依赖注入,那么到TestB属性填充时,发现TestB也依赖TestA,这个时候去注入TestA,先从一级缓存获取,获取不到,再从二级缓存获取,二级缓存也没有,这个时候从三级缓存获取,获取到了,然后getObject()生成TestA对象(在这个时候,如果有代理,那么生成代理对象,如果没有代理,直接返回原本的对象),获取到TestA对象后移到二级缓存并返回,这个时候TestB就成功注入了TestA,然后TestB顺利的走完生命周期,就回到了第一个TestA,进行初始化,初始化完之后,再从二级缓存中取出再重新赋值(为了保证bean是同一个),最后添加到一级缓存中
这么说可能有点绕,我们结合一张图来看看
大概就是这样
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);
}
}
}
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;
}
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的时候,这时候三级缓存还没有添加,所以不能解决循环依赖。
为什么要设计三级缓存,一级、二级缓存行不行?
这个问题非常值得思考,不过不要陷入其中,大家可以思考一下