Spring是怎样处理循环依赖的?

文章目录

    • 环境
    • 前言
    • 什么是循环依赖?
    • 所有注入场景的循环依赖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是同一个),最后添加到一级缓存中

这么说可能有点绕,我们结合一张图来看看

在这里插入图片描述

大概就是这样

  1. 添加半成品的TestA(实例化但没进行属性注入与初始化)到三级缓存,然后注入TestB,在TestB中也要进行属性注入,然后就去注入TestA
  2. 在TestB中注入TestA时,直接从缓存获取,因为这时三级缓存已经存在TestA,然后调用getEarlyBeanReference方法生成对象(如果有代理也是在此生成)
  3. 添加到二级缓存中并删除三级缓存,最后返回,TestB属性注入完成,继续走Bean的生命周期
  4. 回到原本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的时候,这时候三级缓存还没有添加,所以不能解决循环依赖。

为什么要设计三级缓存,一级、二级缓存行不行?

这个问题非常值得思考,不过不要陷入其中,大家可以思考一下