Spring IOC——循环依赖

循环依赖概述

什么是循环依赖?循环依赖是指A类依赖B类,B类也依赖A类,比如:

1
2
3
4
5
6
class A{
public B b;
}
class B{
public A a;
}

在不使用Spring时,这是一种很常见的现象,循环依赖完全没有问题。但是当我们使用Spring管理系统中所有bean时,循环依赖就会出现一些问题。要想研究这个问题,我们需要先了解Spring中是如何获取bean的,也就是getBean()方法。

为什么产生循环依赖——getBean()

getBean()是通过调用doGetBean()来获取Bean实例的,下面是其源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 参数typeCheckOnly:bean实例是否包含一个类型检查
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 解析bean的真正name,如果bean是工厂类,name前缀会加&,需要去掉
String beanName = transformedBeanName(name);
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 无参单例从缓存中获取
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// 如果bean实例还在创建中,则直接抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 如果 bean definition 存在于父的bean工厂中,委派给父Bean工厂获取
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
...
}
if (!typeCheckOnly) {
// 将当前bean实例放入alreadyCreated集合里,标识这个bean准备创建了
markBeanAsCreated(beanName);
}
...
try {
...
// 确保它的依赖也被初始化了.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
...
}
// 创建Bean实例:单例
if (mbd.isSingleton()) {
...
}
// 创建Bean实例:原型
else if (mbd.isPrototype()) {
...
}
// 创建Bean实例:根据bean的scope创建
else {
...
}
}
...
}
return adaptBeanInstance(name, beanInstance, requiredType);
}

获取bean的流程大致如下:

  • 解析bean的真正name,如果bean是工厂类,name前缀会加&,需要去掉

  • 无参单例先从缓存中尝试获取

  • 如果bean实例还在创建中,则直接抛出异常

  • 如果bean definition 存在于父的bean工厂中,委派给父Bean工厂获取

  • 标记这个beanName的实例正在创建

  • 确保它的依赖也被初始化

  • 真正创建

    • 单例时
    • 原型时
    • 根据bean的scope创建

其中有一步是:“确保它的依赖也被初始化”,这是导致产生循环依赖的本质原因。

解决循环依赖——三级缓存

Spring是如何解决循环依赖的呢?答案也是隐藏在前文提到的getBean()方法中。下面是doGetBean()另外一个视角的部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
......
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 获取Bean名称
String beanName = transformedBeanName(name);
Object bean;
// 从三级缓存中获取目标Bean实例
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
......
// 不为空,则进行后续处理并返回
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
......
try {
......
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
......
// 从三级缓存中没有获取到Bean实例,并且目标Bean是单实例Bean的话
if (mbd.isSingleton()) {
// 通过getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法创建Bean实例
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建Bean实例
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
......
}
});
// 后续处理,并返回
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
......
}
catch (BeansException ex) {
......
}
finally {
......
}
}
......
return adaptBeanInstance(name, beanInstance, requiredType);
}

......

}

看上面代码注释,可以整理出如下流程:

  1. 先执行getSingleton(beanName)看看能否从三级缓存中获取bean实例,如果获取到了直接进行后续处理,不再获取。
  2. 三级缓存中没有,执行getMergedLocalBeanDefinition(beanName),获取bean的定义,也就是beanDefinition对象。
  3. 判断该bean是否是单例,如果是单例,再次执行了getSingleton方法,不过这次多传了一个参数,ObjectFactory<?> singletonFactory

到这里我们看到,关于解决循环依赖的核心,来到了getSingleton(),下面我们看看它的源码。

getSingleton(String beanName)

这里有点类似于懒汉模式,用DCL获取单例,先锁外获取,锁外获取不到,进入锁内再次尝试获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
//singletonObjects是一个map,缓存还没有实例化的bean,也就是常说的一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
//若是获取不到而且对象在建立中,则尝试从earlySingletonObjects(二级缓存)中获取
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
//二级缓存中没有,并且允许提前曝光
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
// 在锁内重新从一级缓存中往下查找
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//这里singletonFactories也是一个Map,相当于三级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//调用getObject方法实例化Bean
singletonObject = singletonFactory.getObject();
//将bean放到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//从三级缓存中删除该bean
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}

三级缓存

前面getSingleton()方法中出现了三个map对象,singletonObjects earlySingletonObjects singletonFactories ,分别称之为一级、二级、三级缓存。

  • 一级缓存(singletonObjects ):单例对象缓存池,已经实例化并且完成属性赋值的单例对象,可直接使用;
  • 二级缓存(earlySingletonObjects ):单例对象缓存池,已经实例化但是未完成属性赋值,这里的对象是半成品对象, 用于提前曝光,供别的Bean引用,解决循环依赖。 ;
  • 三级缓存(singletonFactories ):单例工厂缓存, 在Bean实例化后,属性填充之前,如果允许提前曝光,Spring会把该Bean转换成Bean工厂并加入到三级缓存。在需要引用提前曝光对象时再通过工厂对象的getObject()方法获取。

现在我们知道,获取bean的时候是循着三级缓存,层层深入,去寻找目标bean,那么这三个缓存中的bean是从哪里来的呢?

一级缓存我们知道是bean完全创建好后,注册到容器里面的。从前面getSingleton()源码可以看出,二级缓存是把三级缓存里面的数据挪进去的。那三级缓存中的数据什么时候添加进去的呢?其实是在执行doCreateBean()时放进去的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {

......
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

BeanWrapper instanceWrapper = null;

......
// 实例化Bean
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();

......

synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
// 执行MergedBeanDefinitionPostProcessor类型后置处理器
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
......
}
}
}

// 如果该Bean是单例,并且allowCircularReferences属性为true(标识允许循环依赖的出现)以及该Bean正在创建中
// 的话,earlySingletonExposure就为true,标识允许单实例Bean提前暴露原始对象引用(仅实例化)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 添加到单实例工厂集合中,即三级缓存对象,该方法第二个参数类型为ObjectFactory<?> singletonFactory,
// 前面提到过,它是一个函数式接口,这里用lambda表达式() -> getEarlyBeanReference(beanName, mbd, bean)表示
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

Object exposedObject = bean;
try {
// 属性赋值操作
populateBean(beanName, mbd, instanceWrapper);
// 初始化Bean(初始化操作主要包括xxxxAware注入,BeanPostProcessor后置处理器方法调用以
// 及InitializingBean接口方法调用,感兴趣的可以自己查看源码)
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
......
}

// 如果earlySingletonExposure为true
if (earlySingletonExposure) {
// 第二个参数为false表示仅从一级和二级缓存中获取Bean实例
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
// 如果从一级和二级缓存中获取Bean实例不为空,并且exposedObject == bean的话,
// 将earlySingletonReference赋值给exposedObject返回
exposedObject = earlySingletonReference;
}
......
}
}

......
// 返回最终Bean实例
return exposedObject;
}
......

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
// SmartInstantiationAwareBeanPostProcessor类型后置处理,常见的场景为AOP代理
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
}

getSingleton(String beanName, ObjectFactory<?> singletonFactory)

我们从doGetBean()源码可以看出,如果第一次getSingleton(beanName)没获取到目标bean,会执行getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法,其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
//这里调用了singletonFactory的get方法
Object singletonObject = this.singletonObjects.get(beanName);
......
return singletonObject;
}
}
//ObjectFactory是个函数式接口,只定义了一个getObject方法
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;

}

我们看看doGetBean()中传进来的是啥参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (mbd.isSingleton()) {
//第二个参数是一个lambda表达式,返回的是createBean方法
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

从上述源码可以看出,this.singletonObjects.get(beanName)最终执行的是createBean(beanName, mbd, args),也就是说如果三级缓存中没找到该bean,就去创建一个bean。

总结

这里,我们简单理一下getBean()的过程,总结下Spring是如何依靠三级缓存解决单例bean的循环依赖的(图片来自这里):

总之,解决循环依赖的核心在于:创建Bean时,把已经实例化但是未初始化的bean放到三级缓存中,提前曝光

“A对象setter依赖B对象,B对象setter依赖A对象”,A首先完成了初始化的第一步,而且将本身提早曝光到singletonFactories中,此时进行初始化的第二步,发现本身依赖对象B,此时就尝试去get(B),发现B尚未被create,因此走create流程,B在初始化第一步的时候发现本身依赖了对象A,因而尝试get(A),尝试一级缓存singletonObjects(确定没有,由于A还没初始化彻底),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,因为A经过ObjectFactory将本身提早曝光了,因此B可以经过ObjectFactory.getObject拿到A对象(半成品),B拿到A对象后顺利完成了初始化阶段一、二、三,彻底初始化以后将本身放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成本身的初始化阶段二、三,最终A也完成了初始化,进去了一级缓存singletonObjects中,并且更加幸运的是,因为B拿到了A的对象引用,因此B如今hold住的A对象完成了初始化。下图总结了这一过程:

其他

  1. Spring解决循环依赖的局限性

    • 对于单例循环依赖,只能解决属性注入下的循环依赖,不能解决构造器注入下的循环依赖
      • 因为循环依赖的解决依赖于提前曝光机制,即把实例化完成,但是没有初始化的bean放到三级缓存中。但是执行构造器函数时,bean还没完成实例化,也就无法加入三级缓存。
    • 不能prototype作用域循环依赖
      • 因为循环依赖依赖三级缓存,多例bean压根用不到缓存。
  2. 其他循环依赖该怎么解决?@DependsOn注解、构造器注入、多例等

    • 多例:可以把bean改成单例
    • 构造器:使用@Lazy懒加载
    • @DependsOn:找到使用地方,让它不产生循环依赖
  3. 为什么使用三级缓存?能不能把第二级去掉?

    可以参考这篇文章

    我们先回顾下Spring三级缓存分别存放哪些东西:一级缓存(成品bean),二级缓存(半成品bean),三级缓存(beanFactories)。

    为什么要包装一层ObjectFactory对象?

    如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:

    1. 不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
    2. 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。

    Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?
    Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map earlySingletonObjects

参考资料

  1. https://pdai.tech/md/spring/spring-x-framework-ioc-source-3.html
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Yin Peng
  • 引擎: Hexo   |  主题:修改自 Ayer
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信