概述
在介绍 Spring 是如何解决循环依赖之前,先介绍一下什么是 Bean 的循环依赖,下面通过一个小示例介绍一下 Bean 的循环依赖
setter方式单例
A 类
@Componentpublic class A {private B b;public A( B b){this.b = b;}public void getB(){System.out.println(b);}}
B 类
@Componentpublic class B {private A a;public B(A a){this.a = a;}public void getA(){System.out.println(a);}}
SpringCircleConfig 类
@Configuration@ComponentScan("com.zlp.spring.circle.set")public class SpringCircleConfig {}
测试类
public class AnnotationSetCircleTest {public static void main(String[] args) {ApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircleConfig.class);A a = ac.getBean("a",A.class);System.out.println(a.toString());a.getB();}}
首先回忆一下之前介绍的 Spring 初始化过程的内容,在 Spring 初始化 Bean 的过程中,有一个重要很重要的步骤就是属性赋值。这个属性赋值的过程简单来讲就是获取每一个Bean对象内部的属性,然后通过反射把属性值赋给这个Bean对象。对于基本类型的值,例如String、Integer等,很简单,直接解析赋值即可。
对于对象类型的值,例如上述示例中,它的处理过程是什么呢?
第一步:A对象初始化完毕,此时开始进行属性填充,发现 A中需要注入B,则按照创建Bean的流程去创建B;
第二步:在创建 B的过程中,首先去初始化 B,然后进行属性填充,发现 B 中需要注入 A,则按照创建Bean的流程在去创建A;
按照上述的过程会发现创建带有依赖关系的 Bean 的过程就会变成一个死循环,一直转圈。但是上述的示例可以正常运行,这是因为 Spring 中通过引入了早期对象以及使用多级缓存解决了这个问题。
整体的工作流程如下

早期对象
什么是早期对象呢?
早期对象指的是 Bean 的一个半成品。更确切的说,就是实例化完成(没有初始化),并执行了一部分后置处理器,但是还未填充属性的对象,这个对象不能直接使用。例如对于上例中的 A 对应的早期对象其实就是一个空的 A 实例,如下:
如何使用这个早期对象?
这个早期对象 A 创建出来之后,会被保存在一个地方【Spring的二级缓存中】。在进行B实例的属性赋值过程中,发现依赖了A这个对象,然后就会去找A对象。没找到初始化完成的A对象会再去找这个早期的A对象,找到之后,直接完成B实例的属性赋值,将这个没有初始化完成的A对象赋值给B中的a属性,此时,B完成属性填充。
然后 A 中依赖了 B,A 属性填充过程中同样会将未初始化完成的B对象填充给b属性,至此,完成依赖Bean的属性填充。
使用半成品的对象进行属性填充会有问题吗?
不会有问题。在属性填充过程完成之后,会进行bean的初始化过程,初始化过程中,初始化的 bean对象就是这个半成品对象,因为属性赋值的过程其实就是把对象的引用赋值给依赖Bean的成员变量【反应在内存中就是同一个地址引用】。所以当这个对象被初始化之后,之前已经完成赋值的半成品对象自然就会变成初始化完成的对象。
多级缓存
底层代码是通过三级缓存实现,对应三个Map,代码过于繁多,具体可参考:
DefaultSingletonBeanRegistry.java类中
/*** Cache of singleton objects: bean name to bean instance.* 一级缓存:单例对象的缓存,也被称作单例缓存池*/private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/*** Cache of early singleton objects: bean name to bean instance.* 二级缓存:提前曝光的单例对象的缓存,用于检测循环引用*/private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);/*** Cache of singleton factories: bean name to ObjectFactory.* 三级缓存:保存对单实例bean的包装对象*/private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/*** 按照注册顺序所保存的已经注册的单例对象的名称*/private final Set<String> registeredSingletons = new LinkedHashSet<>(256);/*** Add the given singleton object to the singleton cache of this factory.* <p>To be called for eager registration of singletons.* @param beanName the name of the bean* @param singletonObject the singleton object*/protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {// 将创建好的单实例bean放入到单例缓存池中this.singletonObjects.put(beanName, singletonObject);// 从三级缓存中删除this.singletonFactories.remove(beanName);// 从二级缓存中删除(早期对象:已经实例化,但是未完成属性赋值的对象)this.earlySingletonObjects.remove(beanName);// 保存到已注册单实例Bean名称集合中this.registeredSingletons.add(beanName);}}/*** 添加三级环境* <p>To be called for eager registration of singletons, e.g. to be able to* resolve circular references.* @param beanName the name of the bean* @param singletonFactory the factory for the singleton object*/protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {// 如果当前的单实例缓存池中还没有beanName对应的单实例beanif (!this.singletonObjects.containsKey(beanName)) {// 将当前beanName对应的ObjectFactory放入到三级缓存singletonFactories中this.singletonFactories.put(beanName, singletonFactory);// 从早期的单例对象缓存中移除beanName对应的bean实例this.earlySingletonObjects.remove(beanName);// 将当前的beanName保存到已经注册的bean对应的Set集合中,标识其已经注册过this.registeredSingletons.add(beanName);}}}@Override@Nullablepublic Object getSingleton(String beanName) {// 从缓存中获取单实例Beanreturn getSingleton(beanName, true);}/*** Return the (raw) singleton object registered under the given name.* <p>Checks already instantiated singletons and also allows for an early* reference to a currently created singleton (resolving a circular reference).* @param beanName the name of the bean to look for* @param allowEarlyReference whether early references should be created or not* @return the registered singleton object, or {@code null} if none found*/////////////////////////////////////////////////////////////////////////////// ******该段代码是 Spring 解决循环引用的核心代码******//// 解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),// 并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存),// 通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,// 从而避免出现循环引用导致的死循环。//// 这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,// 该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。// 另外这个代码块中引进了4个重要缓存:// singletonObjects 缓存:beanName -> 单例 bean 对象。// earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。// singletonFactories 缓存:beanName -> ObjectFactory。// singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。// singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “三级缓存” 的概念。/////////////////////////////////////////////////////////////////////////////*** 注意:* (1)通过 setter 注入方式产生的循环引用是可以通过以上方案解决的。* (2)构造器注入方式产生的循环引用无法解决,因为无法实例化出 early singleton bean 实例。* (3)非单例模式的循环引用也无法解决,因为 Spring 框架不会缓存非单例的 bean 实例。*/@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// 根据beanName从单实例对象缓存中获取单例对象(singletonObjects为一个ConcurrentHashMap,就是用来保存所有的单实例Bean的,// key:beanName value:beanInstance) 相当于一级缓存Object singletonObject = this.singletonObjects.get(beanName);// 如果缓存中不存在,而且beanName对应的单实例Bean正在创建中.if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 加锁操作.synchronized (this.singletonObjects) {// 从早期单实例对象缓存中获取单例对象(之所以称为单实例早期对象,// 是因为earlySingletonObjects里面的对象都是通过提前曝光的ObjectFactory创建出来的,还未进行属性的填充)singletonObject = this.earlySingletonObjects.get(beanName);// 如果早期单实例对象缓存中没有,而且允许创建早期单实例对象引用if (singletonObject == null && allowEarlyReference) {// 则从单例工厂缓存中获取BeanName对应的单例工厂.ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 如果存在着单例对象工厂,则通过工厂创建一个单例对象,// 调用的是:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))中的拉姆达表达式singletonObject = singletonFactory.getObject();// 将通过单例对象工厂创建的单例对象放入到早期单例对象缓存中,这个早期对象指的是一个空的未完成属性赋值和初始化的对象。this.earlySingletonObjects.put(beanName, singletonObject);// 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放入到earlySingletonObjects缓存中了,// 所以,后续通过beanName获取单例对象,可以通过earlySingletonObjects缓存获取到,不需要再用到该单例工厂.this.singletonFactories.remove(beanName);}}}}return singletonObject;}/*** Return the (raw) singleton object registered under the given name,* creating and registering a new one if none registered yet.* @param beanName the name of the bean* @param singletonFactory the ObjectFactory to lazily create the singleton* with, if necessary* @return the registered singleton object*/public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {// 校验bean的名称,不能为空Assert.notNull(beanName, "Bean name must not be null");synchronized (this.singletonObjects) {// 首先从单例缓存池singletonObjects【Map<String,Object>】中尝试获取单实例beanObject singletonObject = this.singletonObjects.get(beanName);// 如果未获取到,则通过如下的过程去创建单实例beanif (singletonObject == null) {/** 如果当前bean工厂中的实例bean正在被销毁,则不允许执行bean的创建过程 */if (this.singletonsCurrentlyInDestruction) {throw new BeanCreationNotAllowedException(beanName,"Singleton bean creation not allowed while singletons of this factory are in destruction " +"(Do not request a bean from a BeanFactory in a destroy method implementation!)");}if (logger.isDebugEnabled()) {logger.debug("Creating shared instance of singleton bean '" + beanName + "'");}/** 创建单实例bean之前的检查,根据名称校验当前的单实例bean是否正在创建中. */beforeSingletonCreation(beanName);boolean newSingleton = false;/** 初始化用来保存异常信息的Set集合 */boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {/** 回调ObjectFactory的getObject方法,进行单实例Bean的创建. */singletonObject = singletonFactory.getObject();/** 标注单实例bean创建成功 */newSingleton = true;}catch (IllegalStateException ex) {// Has the singleton object implicitly appeared in the meantime ->// if yes, proceed with it since the exception indicates that state.singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {throw ex;}}catch (BeanCreationException ex) {if (recordSuppressedExceptions) {for (Exception suppressedException : this.suppressedExceptions) {ex.addRelatedCause(suppressedException);}}throw ex;}finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}/** 创建完成之后,将bean名称从检查列表中删除. */afterSingletonCreation(beanName);}if (newSingleton) {/** 如果bean创建成功,将其加入单实例bean的map中 */addSingleton(beanName, singletonObject);}}return singletonObject;}}}
具体调用方法 AbstractBeanFactory.doGetBean方法中的 getSingleton(beanName)方法。
三级缓存对象
- singletonObjects:一级缓存,也被称为单实例缓存池,用来缓存所有初始化完成的Bean;
- earlySingletonObjects:二级缓存,用来保存早期对象,这个早期对象就是Bean的一个半成品对象,只完成了实例化化,未进行属性填充和初始化的对象;
- singletonFactories:三级缓存,用来保存获取早期对象的一个回调函数,通过这个回调函数,可以获取到未初始化完成的早期对象;
查询流程
- 根据Bean的名称首先查询一级缓存,查询到直接返回;
- 未查询到单实例Bean而且允许使用早期对象,则查询二级缓存,查询到则直接返回;
- 二级缓存中未查询到早期对象,则通过回调获取早期对象的方法获取早期对象,然后保存到二级缓存中并返回。
获取早期对象的实现可参考:
AbstractAutowireCapableBeanFactory.doCreateBean 中的 getEarlyBeanReference 方法。
获取 bean 调用流程方法

构造器参数循环依赖
构造器循环依赖
通过使用早期对象以及多级缓存解决了这种通过set方法注入时存在的循环依赖问题。但是通过构造器注入的方式无法解决,因为通过构造器的方式无法产生早期对象。如下示例:
A类
@Componentpublic class A {private B b;public A(@Lazy B b){this.b = b;}public void getB(){System.out.println(b);}}
B类
@Componentpublic class B {private A a;public B(@Lazy A a){this.a = a;}public void getA(){System.out.println(a);}}
测试类不变,运行之后,会提示如下异常:
BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?
解决方式
构造器方式注入产生的循环依赖,可以通过指定 Bean 的延迟加载,先注入代理对象,然后在需要使用这个bean的时候再去真实创建,如下:
A类
@Componentpublic class A {private B b;public A(@Lazy B b){this.b = b;}public void getB(){System.out.println(b);}}
B类
@Componentpublic class B {private A a;public B(@Lazy A a){this.a = a;}public void getA(){System.out.println(a);}}
通过其他例如@PostConstruct,或者使用后置处理器的方式也可以解决,但是如果真有这种循环依赖问题,建议还是使用最简单,也最容易理解的set方式去注入,因为这种方式框架本身就已经解决了这种问题。
setter方式原型,prototype
我们在A类和B类中,添加 @Scope(value = “prototype”) 注解
@Component@Scope(value = "prototype")public class A {@Autowiredprivate B b;public void getB(){System.out.println(b);}}
scope=”prototype” 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。
测试从新启动报错
Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a';nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
为什么原型模式就报错了呢 ?
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
几个 QA?
三级缓存放流程

QA 问题
- 三级缓存存对象,在获取数据的时候什么顺序来获取的
先获取一级缓存,没有在获取二级缓存,没有再获取三级缓存,所以当前面的缓存中存在了对象
级缓存。
- 一级能解决循环依赖问题
有一级缓存,那么成品对象和半成品对象会放到一起,这个是没办法区分了,所以需要两个缓存来分别存放不同状态的对象,一级缓存放成品,二级缓存放半成品。
3、如果只有二个缓存,能否解决循环依赖问题?
在刚刚的整个流程中,三级缓存一共出现了几次? getsingleton, doCreateBean
如果对象的创建过程中不包含aop,那么二级缓存就可以解决循环依赖问题,但是包含aop的操作,
循环依赖问题是解决不了的。
4、为什么添加了aop的操作之后就需要添加三级缓存来解决这个问题?三级缓存加了什么操作?
添加了一个 getEarlyBeanReference 的方法在创建代理对象的时候,
是否需要生成原始对象?
需要当创建完成原始对象之后,后续有霃要创建代理对象,那么对象在引用的时候应该使用哪一个
换句话说,就是一个 beanName对应有两个对象,(原始对象和代理对象在整个容器中,有且仅能有一个同名的对象,当需要生成代理对象的时候,就要把代理对象覆盖原
程序是怎么知道在什么时候要进行代理对象的创建的呢?
需要一个类似于回调的接口判断,当需要第一次对外暴露使用的时候,来判断当前对象是否需要去
创建代理对象, getEarlyBeanReference 方法的判断,如果需要代理就返回代理对象,如果没有代
理就返回原始对象。
参数文档
