Fork me on GitHub

Spring怎么解决循环依赖?

Spring怎么解决循环依赖?

循环依赖

循环依赖是个啥?在创建TestA类时,构需要TestB类,那将去创建TestB,在创建TestB类时又需要TestA,从而形成一个环,没办法创建。

spring解决这个问题与三特别有缘分,对于三种不同程度的循环依赖,spring能解决的程度不同;而解决的办法正是用到了三级缓存。

下面就分别来讨论三种不同程度的循环依赖。

第一种:构造器的循环依赖,无法解决,会报错

情况

A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象

无法解决原因

一、Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

Spring容器先创建单例StudentA,StudentA依赖StudentB,然后将A放在“当前创建Bean池”中,此时创建StudentB,StudentB依赖StudentA, 但是,此时StudentA已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)

二、循环依赖发生的时候还没有执行完构造器,也就无法使用三级缓存解决问题。spring解决循环依赖的手段是三级缓存,而三级缓存的的前提是执行了构造器,才会调用addSingletonFactory加入三级缓存,所以构造器的循环依赖没法解决。这在下面一种情况中会有解释,就先不展开了。

第二种:field属性的单例循环依赖,可解决。

情况

A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象

解决前提

  • 前提一:比第一种情况,此时循环依赖发生在字段属性中,而不是构造器中。
  • 前提二:比第三种情况,因为是单例的,所以才会用到三级缓存。

前提一:是先实例化在设置对象属性的,否则解决field属性的setter方式单例循环依赖将无从谈起。先来看看spring bean生命周期的一张图。

1564554784479

大体的流程就是:实例化>填充属性>initializeBean(调用spring xml中的init 方法)

循环依赖主要发生在实例化>填充属性这两步。

前提二:对于单例来说,在Spring容器会把对象放在缓存中,而为了解决单例的循环依赖问题,使用了Spring三级缓存

解决手段

三级缓存,这三级缓存分别指:

  1. singletonObjects:单例对象的cache,一级缓存。
  2. earlySingletonObjects :提前暴光的单例对象的Cache ,二级缓存。
  3. singletonFactories : 单例对象工厂的cache ,三级缓存。

由上往下,缓存中的bean是逐渐完善的,三级缓存中的bean 是最初始的状态。

解决过程

“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。

A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories三级缓存中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程。

B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A提前曝光了,所以B能够通过三级缓存singletonFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀)。

B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。

而且由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

源码

getSingleton()

获取单例的方法,逐层从缓存中取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先从一级缓存singletonObjects中获取
Object singletonObject = this.singletonObjects.get(beanName);
//如果获取不到,并且对象正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//再从二级缓存earlySingletonObjects中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//从三级缓存singletonFactory获取
singletonObject = singletonFactory.getObject();
//放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
//从三级缓存中删除,也就是把bean自动三级移到了二级缓存中
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
addSingletonFactory()

这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

注意:这里有一个细节就是ObjectFactory,三级缓存的类型是ObjectFactory并非一个bean(还没实例化好呢)。

1
2
3
4
5
6
7
8
9
10
11
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
//三级缓存的类型是ObjectFactory
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

知道了这个原理时候,肯定就知道为啥Spring不能解决第一种情况“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”。因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

第三种:field属性的prototype循环依赖,无法解决解决,报错

情况

A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象。

不能解决的前提

因为没有缓存。

scope=”prototype” 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。

对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

Spring循环依赖的三种方式

Spring源码初探-IOC(4)-Bean的初始化-循环依赖的解决

Spring-bean的循环依赖以及解决方式