spring如何解决循环依赖

什么是循环依赖?

多个对象互相依赖着对方。比如最常见的A类依赖B类,而B类又依赖C类,最后C类也依赖A类。这样就形成了一个闭环,这三个类互相依赖的对方。

在spring中有两种依赖,第一种是构造函数依赖。第二种是filed属性依赖

spring是如何解决循环依赖

在了解spring如何解决循环依赖的时候,我们首先知道spring中IOC容器是如何创建对象,如何初始化对象的。

Spring的IOC容器如何初始化Bean

这里概述下IOC容器初始化bean的步骤,不准备细讲,因为如果细讲可能会让读者失去耐心(因为我们本意是想知道如何解决循环依赖)。但是如果知道如何初始化bean会对下面的内容更容易读懂。我推荐一篇文章,大家最好抽空先看下,Spring IOC 容器源码分析

spring的单例对象初始化分为三个步骤:

1.首先根据构造函数创建单例Bean的实例

2.populateBean、对单例中的属性进行依赖的注入

3.最后对bean进行初始化即创建完bean

很明显spring中两种依赖,构造函数依赖是对应第一步,filed属性依赖是对应第二步。

spring为了解决循环依赖,实现全局下单例bean,当初始化完bean以后需要有一个容器存储起来,方便之后需要此bean时,不用再进行初始化创建bean,只需要从这个容器中拿出来就可以了。这样保证在spring容器生命周期内保持bean是单例的。而这个容器在spring中叫三级缓存

DefaultSingletonBeanRegistry.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

/** Cache of singleton objects: bean name to bean instance. */
// 一级缓存,存放初始化好的单例Bean,也就是完成了初始化的三个步骤的bean会存放在这个Map中
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance. */
// 二级缓存,提前曝光的单例对象,下面会讲什么时候bean会存放该map中
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Cache of singleton factories: bean name to ObjectFactory. */
// 三级缓存,在单例对象初始化的第一步构造函数创建实例完成后,且还未完成初始化,则为存放该map中
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

DefaultSingletonBeanRegistry.java:150

当spring初始化bean到根据构造函数创建bean后,会调用addSingletonFactory将起加入第三级缓存中

1
2
3
4
5
6
7
8
9
10
11
12
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);
}
}
}

上面的三级缓存中解释了在创建单例Bean过程中,不同阶段会存放不同缓存容器。在创建完以后,接下来就是要讲一下如何容器是如何获取bean的。

DefaultSingletonBeanRegistry.java:176

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//第一步先从一级缓存中获取单例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//获取不到且正在创建中
synchronized (this.singletonObjects) {
//在从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//如果获取不到且允许提前引用
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//最后再从第三级缓存中获取
if (singletonFactory != null) {
//获取到单例的引用,接着放入二级缓存中并从三级缓存中移除
singletonObject = singletonFactory.getObject();
//这里就解释了二级缓存容器是在从三级缓存获取后再放入的
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

从上面的源码,我们可以清楚的知道,spring容器在获取bean时,首先从一级缓存中获取bean,如果获取不到且该bean正在创建中则从二级缓存中获取,如果还获取不到且运行提前引用,则从第三级缓存中获取。

综上所述,我们清楚的知道spring是如何创建和获取bean后。接下来举个最常见的例子,分析下spring解决循环依赖的过程。A类中属性依赖于B类,而B类中属性也依赖A类,Spring首先根据构造函数初始化A类(此时放入三级缓存中),在进行初始化的第二步发现属性中需要依赖B类,这是需要创建B类实例,所以先构造函数初始化实例,接着第二步属性依赖中发现需要A类的bean,此时就调用getBean(A),先从一级缓存中读取,读取不到就去二级缓存,还是读不到就到三级缓存获取,这时候从三级缓存(因为前面已经通过构造函数实例且存放到了三级缓存)获取到了A类实例的引用,然后初始化最后一步B类,这样B类就创建完成了。A类也获取到B的实例后,代表初始化A中的第二步完成,最后到了第三步完成了整个Bean的初始化。这样就完美解决了两个类属性相互依赖造成的循环依赖。但是如果是构造函数依赖的话,在初始化第一步未完成时,此时bean不会出现在三级缓存中,所以会造成循环依赖最终会抛出循环依赖异常。

最后总结下,spring用三级缓存解决了属性循环依赖,而不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题。是因为放入三级缓冲之前需要根据构造函数初始化实例。


推荐:

Spring IOC 容器源码分析

参考:

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


  Spring

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×