Spring源码-循环依赖源码解读


  笔者最近无论是看书还是从网上找资料,都没发现对Spring源码是怎么解决循环依赖这一问题的详解,大家都是解释了Spring解决循环依赖的想法(有的解释也不准确,在《Spring源码深度解析》作者也是看别人的博客说明了一下),没有从源码的角度分析是怎么解决循环依赖的,笔者就把自己看源码的过程写一下。

  写这一篇文章算是个引路的,Spring为了程序的健壮性做了大量分析校验,调用的方法繁多复杂,我这篇文章为读者清理出解决循环依赖的流程。

Spring中对象可以配置成单例模式也可配置为原型模式(原型模式很值得一看)。
Spring中可以通过构造函数注入、setter注入的方式来解决对象与对象间的依赖。
对象间的循环依赖只能配置单例、setter注入的方式来解决,其他方法就会报错,下面我们通过源码分析一下。

一、单例、setter注入解决循环依赖

假如有TestA、TestB、TestC三个对象,其中TestA依赖TestB,TestB依赖TestC,TestC依赖TestA。

下面具体通过代码分析Spring是如何解决单例通过Setter注入的循环依赖。
在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类中有几个集合类型的成员变量,用来做缓存用的需要特别留意,源码如下:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
......
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
......
}

上面的代码中:
singletonsCurrentlyInCreation:保存对象的BeanName,在创建对象之前就会把对象的beanName保存起来。
singletonFactories:保存对象的BeanName和创建bean的工厂AbstractAutowireCapableBeanFactory(ObjectFactory),(对象的构造函数是在这一步完成的)
earlySingletonObjects:保存对象BeanName和对象的早期实例(ObjectFactory#getObject得到的对象)(此时对象还没注入属性),此时可以作为对象填充依赖。
singletonObjects:保存BeanName和bean的实例(此时对象已经完成了属性注入)

通过Spring获取testA对象:

通过Spring获取testA对象
ApplicationContext factory=new ClassPathXmlApplicationContext("classpath:applicationContext-beans.xml");
TestA testA = (TestA) factory.getBean("testA");
System.out.println(testA);

factory.getBean最终调用的是org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

下面看doGetBean源码:

protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
......
//重点1
Object sharedInstance = this.getSingleton(beanName); ...... if (mbd.isSingleton()) {
//重点2
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
//重点3
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;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}

在上面源码中:

1、重点1:

  //重点1 根据beanName试图从缓存中获取已经创建的对象,第一次进入是肯定返回null,这个函数放在后面解释。

2、重点2:

  在//重点2中才是真正创建TestA对象的方法,下面是//重点2 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton的源码:

//重点2
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
......
//重点2-1
beforeSingletonCreation(beanName);
......
try {
//重点2-2
singletonObject = singletonFactory.getObject();
newSingleton = true;
}catch (IllegalStateException ex) {
......
}catch (BeanCreationException ex) {
......
}
finally {
//重点2-3
afterSingletonCreation(beanName);
} }
}

  在//重点2-1中beforeSingletonCreation方法中只做了一件事,就是保存beanName到singletonsCurrentlyInCreation(注意),这个时候testA就保存在singletonsCurrentlyInCreation里面了,源码如下:

//重点2-1
protected void beforeSingletonCreation(String beanName) {
//this.singletonsCurrentlyInCreation.add(beanName)
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

  在 //重点2-2 会调用 //重点3 。

3、重点3:

  在//重点3中org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean调用doCreateBean,doCreateBean方法源码:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
......
//重点3-1
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//重点3-2
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
//重点3-3
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
...... }

  在//重点3-1判断testA必须是单例,并存在在singletonsCurrentlyInCreation中,此时才会调用//重点3-2的addSingletonFactory方法,//重点3-2的addSingletonFactory方法源码,

//重点3-2
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) { //重点3-2-1
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName);
}
}
}

  在上面的代码中有一个singletonFactory的参数,这个参数是//重点3-3调用getEarlyBeanReference得到的,getEarlyBeanReference返回的就是ObjectFactory对象,是完成构造方法的。

  在//重点3-2-1 向singletonFactories添加 ObjectFactory(注意),这个时候,testA和testA的ObjectFactory对象保存在singletonFactories,并移除earlySingletonObjects(现在earlySingletonObjects里面并没有testA)。

  执行完//重点3-2,发现testA依赖TestB对象,此时会递归调用getBean获取TestB,testB执行步骤和上面testA一样,然后testB依赖TestC,递归调用TestC,此时singletonFactories里面保存的数据如下:
  testA -> ObjectFactory(TestA)
  testB -> ObjectFactory(TestB)
  testC -> ObjectFactory(TestC)

  创建testC过程中执行完//重点3-2,发现依赖testA,此时会递归调用getBean获取TestA,这时候执行到//重点1,下面开始解析//重点1 Object sharedInstance = getSingleton(beanName);方法。

4、重点1:

  下面是调用//重点1 getSingleton(beanName),调用getSingleton(beanName, true)的源码:

//重点1
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) {
//重点1-1
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) { //重点1-2
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

  在上面代码中isSingletonCurrentlyInCreation就是判断对象(testA)是否被保存过(在//重点2-1的时候testA就被保存了)

  在//重点1-1中,从singletonFactories缓存中获取到ObjectFactory(TestA),

  在//重点1-2并通过模板模式获取TestA对象,保存在earlySingletonObjects缓存中,并移除singletonFactories中的testA,

  此时testC获取到TestA的早期对象,可以注入TestA对象,自此TestC完成依赖注入,并把testC保存到singletonObjects中。

  TestC创建完成,返回给testB,testB也算是完成了创建,然后返回给testA,自此循环依赖算是完成了。

总结:

  单例的setter注入,会先把创建testA、testB、testC对象的ObjectFactory(对象工厂)保存在singletonFactories里面,然后testC依赖testA,那就从singletonFactories缓存中拿到testA的ObjectFactory,通过ObjectFactory的getObject获取TestA的对象,并保存在earlySingletonObjects缓存中,清除singletonFactories缓存中的testA,此时testC就可以获取earlySingletonObjects缓存中TestA的对象,完成注入TestA的过程。TestC对象创建完成就可以注入到TestB对象中,然后TestB注入到TestA中。

  singletonsCurrentlyInCreation就是用来保存是否试图创建某个对象的beanName,不管有没有创建成功,为后来从singletonFactories缓存中或earlySingletonObjects缓存中取值做个标识。

二、单利、构造函数注入循环依赖

  假如有TestA、TestB两个对象,TestA依赖TestB,TestB依赖TestA;

  构造函数注入和setter注入的不同在于,构造函数注入无法先调用构造函数实例化对象,当TestA依赖TestB,会先把testA保存到singletonsCurrentlyInCreation中,然后getBean("testB"),然后把testB保存到singletonsCurrentlyInCreation中,发现TestB依赖TestA,然后再getBean("testA"),此时执行下面的代码(和模块一,重点2是同一块代码):

//重点2 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton的源码:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
//重点2-0
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
......
//重点2-1
beforeSingletonCreation(beanName);
......
try {
//重点2-2
singletonObject = singletonFactory.getObject();
newSingleton = true;
}catch (IllegalStateException ex) {
......
}catch (BeanCreationException ex) {
......
}
finally {
//重点2-3
afterSingletonCreation(beanName);
} }
}

  在上面的代码中 //重点2-0  Object singletonObject = this.singletonObjects.get(beanName); 此时singletonObject为空会执行beforeSingletonCreation方法,源码如下:

//重点2-1
protected void beforeSingletonCreation(String beanName) {
//this.singletonsCurrentlyInCreation.add(beanName)
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

  结果发现singletonsCurrentlyInCreation已经存在testA,抛出BeanCurrentlyInCreationException。

setter注入为什么不会执行这一步呢,因为setter注入中会实例化TestA、TestB保存在缓存中,所以在执行 //重点2-0  Object singletonObject = this.singletonObjects.get(beanName); 时 singletonObject 不为空,并不会执行beforeSingletonCreation所以不会保存。

三、原型模式下循环依赖

  假如有TestA、TestB两个对象,TestA依赖TestB,TestB依赖TestA,当调用getBean("testA")时,会先把beanName(testA)保存到isPrototypeCurrentlyInCreation里面,发现TestA依赖TestB,就会去getBean("testB"),然后把beanName(testB)也保存到isPrototypeCurrentlyInCreation里面,此时TestB发现依赖TestA,去getBean("testA")时,发现isPrototypeCurrentlyInCreation已经存在testA,就会抛出BeanCurrentlyInCreationException异常,具体代码如下

  factory.getBean最终调用的是org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,

下面看doGetBean源码:

protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
......
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
......
} //isPrototypeCurrentlyInCreation源码
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

Spring源码-循环依赖源码解读的更多相关文章

  1. 3.3 Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案

    根据之前解析的循环依赖的源码, 分析了一级缓存,二级缓存,三级缓存的作用以及如何解决循环依赖的. 然而在多线程的情况下, Spring在创建bean的过程中, 可能会读取到不完整的bean. 下面, ...

  2. 3.2spring源码系列----循环依赖源码分析

    首先,我们在3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 中手写了循环依赖的实现. 这个实现就是模拟的spring的循环依赖. 目的是为了更容易理解spring源码 ...

  3. 面试必杀技,讲一讲Spring中的循环依赖

    本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...

  4. 面试阿里,腾讯,字节跳动90%都会被问到的Spring中的循环依赖

    前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...

  5. Spring 如何解决循环依赖问题?

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能 ...

  6. Spring如何解决循环依赖问题

    目录 1. 什么是循环依赖? 2. 怎么检测是否存在循环依赖 3. Spring怎么解决循环依赖 本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可 ...

  7. Spring 如何解决循环依赖的问题

    Spring 如何解决循环依赖的问题 https://blog.csdn.net/qq_36381855/article/details/79752689 Spring IOC 容器源码分析 - 循环 ...

  8. spring怎么避免循环依赖

    1.循环依赖 (1)概念 对象依赖分为强依赖和弱依赖: 强依赖指的是一个对象包含了另外一个对象的引用,例如:学生类中包含了课程类,在学生类中存在课程类的引用 创建课程类: @Data public c ...

  9. 一张图彻底理解Spring如何解决循环依赖!!

    写在前面 最近,在看Spring源码,看到Spring解决循环依赖问题的源码时,不得不说,源码写的太烂了.像Spring这种顶级的项目源码,竟然存在着这种xxx的代码.看了几次都有点头大,相信很多小伙 ...

随机推荐

  1. 实验-12-JSP简单入门

    参考资料 JSP实验参考文件 主要看实验任务书 实验1. 第一个HTML页面与Tomcat 实验内容:任务书中的JSP-实验1. 1.1 EclipseJEE的使用 新建Tomcat Server 新 ...

  2. PAT 乙级 1092 最好吃的月饼 (20 分)

    1092 最好吃的月饼 (20 分) 月饼是久负盛名的中国传统糕点之一,自唐朝以来,已经发展出几百品种. 若想评比出一种“最好吃”的月饼,那势必在吃货界引发一场腥风血雨…… 在这里我们用数字说话,给出 ...

  3. go语言学习--go中的map切片

    //定义一个结构 type Car struct { Brand string Age int } func Pluck() map[int][]Car { carMap := make(map[in ...

  4. python之路——15

    王二学习python的笔记以及记录,如有雷同,那也没事,欢迎交流,wx:wyb199594 复习 1.迭代器 1.可迭代协议:含有iter 2.迭代器协议:含有iter和next 3.特点:节省内存, ...

  5. [UE4]Size Box

    一.Size Box用来指定一个特定的尺寸 二.Size Box只能放一个子控件 三.Size Box一般作为Canvas Panel的子控件,并勾选Size To Content选项,而不作为根节点 ...

  6. Windows下,python pip安装时ReadTimeoutError解决办法

    一般情况下PIP出现ReadTimeoutError都是因为被GFW给墙了,所以一般遇到这种问题,我们可以选择国内的镜像来解决问题. 在Windows下: C:\Users\Administrator ...

  7. ado.net调用返回多结果集的存储过程

  8. 向量的卷积(convolution)运算

    一.向量的卷积运算 给定两个n维向量α=(a0, a1, ..., an-1)T,β=(b0, b1, ..., bn-1)T,则α与β的卷积运算定义为: α*β=(c0, c1, ..., c2n- ...

  9. winfrom

    WINFORM(winform) windows窗体应用程序(.NET Framework4,版本太高了不好,选中Visual c#) 客户端应用程序的特点是:所见即所得,就是说,编辑的什么样,启动之 ...

  10. Python全栈开发记录_第三篇(linux(ubuntu)的操作)

    该篇幅主要记录linux的操作,常见就不记录了,主要记录一些不太常用.难用或者自己忘记了的点. 看到https://www.cnblogs.com/resn/p/5800922.html这篇幅讲解的不 ...