Spring 循环依赖(图例+源码分析)

1.循环依赖概述

什么是循环依赖呢?如下图所示,有两个或两个以上的对象之间引用,也就是A对象中有属性B 引用了B对象,B对象中有属性C 引用了C对象,而C对象中有属性A 引用了A对象。构成一个环状结构,这就是循环依赖。

在这里插入图片描述

对于普通的java对象而言,对象之间的循环依赖可以通过set方式设置之间的引用关系。而SpringBean中的循环依赖就复杂的很多,单例Bean的创建过程是交给Spring管理的,并不是简简单单的通过set就可以解决的。那么Spring是如何解决的循环依赖的呢?

1.1 相关集合说明

Spring解决循环依赖会用到如下的几个集合,在这里先对它们简单的说明一下分别存的是什么,方便后面的源码分析。

  • Map<String, Object> singletonObjects

    key是Bean的名称,value是完整的单例实例对象,

  • Map<String, Object> earlySingletonObjects

    key是Bean的名称,value是不完整的单例对象,也就是还没有初始化完毕。

  • Map<String, ObjectFactory<?>> singletonFactories

    key是Bean的名称,value是单例工厂,用于创建key所对应的Bean对象。

  • Set< String > singletonsCurrentlyInCreation

    存放正在创建的Bean的名称,创建完成后,从这个集合中删除。

下面的分析我们以画图的形式,来分析Bean创建过程中,这4个集合里都存了些什么。

在这里插入图片描述

2.Spring 循环依赖源码分析

编写测试代码,准备如下几个类。

/**
 * @author gongsenlin
 * @version 1.0
 * @date 2020-09-23 10:58
 */
@Component
public class A {
    
    

   @Autowired
   B b;
}

@Component
public class B {
    
    

	@Autowired
	A a;

	@Autowired
	C c;
}

/**
 * @author gongsenlin
 * @version 1.0
 * @date 2020-09-23 10:59
 */
@Component
public class C {
    
    

	@Autowired
	A a;

}

/**
 * @author gongsenlin
 * @version 1.0
 * @date 2020-09-23 10:57
 */
@Configuration
@ComponentScan("com.gongsenlin3")
public class Appconfig {
    
    
}

public class Test {
    
    
	public static void main(String[] args) {
    
    
		AnnotationConfigApplicationContext annotationConfigApplicationContext =
				new AnnotationConfigApplicationContext(Appconfig.class);
	}
}

它们之间的引用关系如下图所示:

在这里插入图片描述

A 和 B之间构成了循环依赖。 A-B-C三者之间也构成了循环依赖。接下来就来Debug调试代码,分析一下Spring是如何解决的循环依赖。

在上一篇博客《Spring IOC—Bean的生命周期》中分析了Bean的创建过程,所以下面的分析只会贴出关键的步骤。

首先将代码定位到DefaultListableBeanFactory# preInstantiateSingletons

该方法会遍历BeanDefinitionMap,找到单例的Bean对他进行创建。
在这里插入图片描述

因为A是个普通Bean所以会走这里getBean的逻辑。getBean会调用doGetBean,接下来就来到了AbstractBeanFactory# doGetBean
在这里插入图片描述

doGetBean中的getSingleton方法是解决循环依赖的关键。如下:
在这里插入图片描述

此时的beanName是“a“ 先从单例池中找,此时肯定是找不到的,返回null。

isSingletonCurrentlyCreation方法是判断当前Bean是否在singletonsCurrentlyInCreation集合当中。此时也是没有的,返回false。那么就不会进第一个if。直接返回null值。

表示从缓存中获取不到 “a” 对应的Bean

方法返回后,代码定位到 AbstractBeanFactory# doGetBean 如下位置

在这里插入图片描述

因为上面没有获取到Bean,这里就要去准备创建一个Bean,进入getSingleton方法

DefaultSingletonBeanRegistry# getSingleton

在这里插入图片描述

在创建Bean之前会将BeanName放入到 singletonsCurrentlyInCreation中。此时的4个集合如下所示:

在这里插入图片描述

接着会调用lambda表达式中的createBean来创建Bean

AbstractAutowireCapableBeanFactory# createBean 中调用doCreateBean
在这里插入图片描述

AbstractAutowireCapableBeanFactory# doCreateBean

在这个方法里会创建java对象,此例子中就是创建A对象。在填充A的属性之前,会将创建Bean A的工厂放入到singletonFactories中。

在这里插入图片描述

此时4个集合如下所示:

在这里插入图片描述

接下来就是开始对A的属性填充,A中有属性B。

代码定位到AbstractAutowireCapableBeanFactory# populateBean

在这里插入图片描述

在属性填充之前,会先去获得属性值,但是此时的B对象并没有被实例化,所以需要先去实例化B才可以。此时递归的去构建B对象。

代码又会来到AbstractBeanFactory# doGetBean

在这里插入图片描述

和之前一样先调用getSingleton方法,4个集合中,都没有和“b“有关的缓存,所以返回null。

然后调用 AbstractBeanFactory# doGetBean 中的getSingleton 去准备创建“b” 的Bean对象
在这里插入图片描述

进入getSIngleton方法,将“b“ 放入到 singletonsCurrentlyInCreation 集合中
在这里插入图片描述

此时的四个集合如下所示:

在这里插入图片描述

然后和之前的逻辑一样,创建了B对象,在填充B的属性之前,会将创建Bean B的工厂放入到singletonFactories中。

在这里插入图片描述

此时的四个集合如下所示:

在这里插入图片描述

然后执行属性填充,准备填充B的属性

在这里插入图片描述

B的属性有A和C,所以会去获取A和C的属性值。也就是去获取A 实例Bean和C 实例Bean。

在这里插入图片描述

代码又回来到AbstractBeanFactory# doGetBean
在这里插入图片描述

首先调用getSingleton 来从缓存中获取。进入getSingleton 看看能不能得到A的实例

在这里插入图片描述

看上面的4个集合的结果图,就可以很明显的发现,此时单例池中没有,所以第一行代码返回null。然后判断第一个if,因为singletonsCurrentlyInCreation 有 “a” 所以返回true。然后去earlySingletonObjects 获取,发现没有。最后他在singletonFactories中发现了有可以生产A的Bean的工厂对象!拿到这个工厂对象。然后通过工厂对象生成一个Bean,将这个Bean添加到earlySingletonObjects集合当中。然后将这个工厂 从工厂集合中删除,他完成了他的使命。

此时4个集合的结果如下图所示:

在这里插入图片描述

这会儿获取a就比较顺利了,从缓存中得到了。接着B还有另一个属性C。他要去获取C的实例对象。所以代码又回回到AbstractBeanFactory# doGetBean

同样先调用getSingleton ,判断能否在缓存中获取c

在这里插入图片描述

此时的4个集合中,并没有和c有关的,所以getSingleton返回null。

然后调用 AbstractBeanFactory# doGetBean 中的getSingleton 去准备创建“c” 的Bean对象

在这里插入图片描述

进入getSIngleton方法,将“c“ 放入到 singletonsCurrentlyInCreation 集合中

在这里插入图片描述

此时的4个集合如下图所示:

在这里插入图片描述

之后的逻辑不说也应该清楚了吧,创建了C的java对象后,在属性填充之前,会将创建Bean C的工厂放入到singletonFactories中。

在这里插入图片描述

此时的4个集合如下图所示:

在这里插入图片描述

填充C的属性前需要获取C的属性,此例子中也就是获取A对象。

代码又又又来到了AbstractBeanFactory# doGetBean 去执行getSingleton 判断缓存中是否有A

在这里插入图片描述

在这里插入图片描述

很显然,此时可以从earlySingletonObjects得到A。 直接返回这个不完整的A。

之后就是完成A注入到C中。此时C中已经引用了A。

然后代码会执行到DefaultSingletonBeanRegistry# getSingleton 中的这里

在这里插入图片描述

将 “c” 从singletonsCurrentlyInCreation中删去,然后代码继续往下执行。

在这里插入图片描述

addSIngleton中的逻辑很简单,将单例对象添加到单例池中。从工厂集合和提前暴露的不完整的bean集合中删去相应的缓存。

在这里插入图片描述

此时的4个集合如下图所示:

在这里插入图片描述

然后代码会到B填充属性的地方,此时A对象和C对象都可以被B拿到了,所以B也就可以执行他的属性填充。

B就构成了一个完整的B,完成了他的构建,和C的逻辑一样,执行下面的代码更新4个集合。

在这里插入图片描述

执行完之后,此时的4个集合如下图所示:

在这里插入图片描述

得到了B的Bean对象,A第一层递归的地方,就可以执行填充属性B了。

和之前逻辑一样,变更4个集合。

在这里插入图片描述

最终4个集合结果如下,单例对象都被实例化并放入到单例池当中。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/gongsenlin341/article/details/108753560
今日推荐