在spring中,是支持单实例bean的循环引用(循环依赖)的,循环依赖,简单而言,就是A类中注入了B类,B类中注入了A类,首先贴出我的代码示例

 @Component
public class AddressVO { @Autowired
private UserVO userVO;
} @Component
public class UserVO { @Autowired
private AddressVO addressVO; public void test(){
System.out.println(addressVO.getClass());
}
} @Configuration
@ComponentScan("com.springsource.study.reference")
public class AppConfig {
}

前提是,bean都是单实例的(singleton),我们下面在学习源码的时候,也默认当前bean是单实例的;

1.首先说,循环依赖的处理是在refresh()方法中,finishBeanFactoryInitialization(beanFactory);这个方法中完成的;这个方法的作用是:在spring将所有的bean添加到beanDefinitionMap之后,实例化所有的bean;

先说明spring在解决循环依赖所用到的几个比较重要的collection

   //在实例化bean的时候,如果bean正在被创建,会在beforeSingletonCreation()方法中,将当前bean加入到singletonsCurrentlyInCreation   private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));

   //从狭义上来理解,spring容器就是signletonObjects这个map;但是,详细点来讲,就是由beanDefinition+beanDefinitionMaps+beanFactory+beanFactoryPostprocessor+singletonObjects....等一些列组件的集合
//这是spring单实例池(也就是我们常说的spring容器)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); //我们姑且称为spring的二级缓存,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));在该方法中,会将bean包装为ObjectFactory,存放到map中
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); //暂时称为三级缓存 三级缓存在循坏依赖中,用来防止重复创建
private final Map<String, Object> earlySingletonObjects = new HashMap(16);

上面这几个集合,在源码中会一一解析

上面这里少说了一个变量:allowCircularReferences;这个值默认是true;表示是否允许循环依赖

2.我们直接来说源码吧:

org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons

    org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)
      org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

在doGetBean()方法中,我们首先要说的是Object sharedInstance = getSingleton(beanName);这个方法:

 @Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//这里只需要从二级缓存中拿一次就行,如果没有二级缓存,每次进来都需要从二级缓存get一次,影响效率
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;
}

在第一次初始化bean,走到这个方法的时候,这里返回的肯定是null,因为在第一次创建的时候,单实例池、二级缓存、三级缓存中都是null;

这里先这样理解,在后面还会介绍这个方法;在这里返回null之后,spring就会继续往下进行创建初始化的流程;

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

在doGetBean方法中的,有这样一段代码

 if (mbd.isSingleton()) {
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;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

这里判断到当前bean是单实例的时候,在getSingleton方法中有这么一行代码:beforeSingletonCreation(beanName);

这个方法,就是把当前bean添加到了singletonsCurrentlyInCreation这个set集合中;这个set的作用,在后面会用到;

接下来,会继续执行初始化的代码,中间的创建流程不做介绍了哈,直接说关键点

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

在这个方法中,有这么几行重要的代码

 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");
}
//第四次调用后置处理器 用来解决循环依赖
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

这段代码的意思是:

如果当前bean是单实例的,并且允许循环依赖;isSingletonCurrentlyInCreation这个方法,就是判断当前bean是否在singletonsCurrentlyInCreation这个set中,也就是说判断当前bean是否正在被创建

这三个条件都满足,所以会调用后置处理器

关键代码是这个this.singletonFactories.put(beanName, singletonFactory); 将后置处理器返回的singletonFactory放到了我们所说的二级缓存中

再继续执行流程,初始化bean之后,会在populateBean(beanName, mbd, instanceWrapper);这个方法中,进行属性的注入;

关键点就在这里:

我们就以A类和B类来说了

2.1 首先要明白的是,上面这一整个流程是第一个A这个bean初始化的过程;A类在进行属性注入的时候,会发现依赖了B类,这时候,会调用 org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String);

    2.2 因为这时候,B类还没有初始化,所以,会进行B类的创建(B类的创建,就是再走一步我们上面说的流程,),由于流程是一模一样的,添加到set集合中,调用后置处理器往二级缓存中放入singletonFactory,这些都一样,当B类来到属性注入的时候;会发现B类依赖了A类;也会去调用getBean来判断是否有A类;关键点就是这一次调用getBean的时候

   

@Nullable
2 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
3 Object singletonObject = this.singletonObjects.get(beanName);
4 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
5 synchronized (this.singletonObjects) {
6
7 singletonObject = this.earlySingletonObjects.get(beanName);
8 if (singletonObject == null && allowEarlyReference) {
9 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
10 if (singletonFactory != null) {
11 singletonObject = singletonFactory.getObject();
12 this.earlySingletonObjects.put(beanName, singletonObject);
13 this.singletonFactories.remove(beanName);
14 }
15 }
16 }
17 }
18 return singletonObject;
19 }

这一次在调用getBean的时候,singletonObjects里面,还是获取不到A类,但是:isSingletonCurrentlyInCreation(beanName)这个判断条件会满足,因为A类在初始化的时候,放到了这个set集合中;

这个从earlySingletonObjects中获取不到A类,所以,会执行ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);这一行代码,返回singletonFactory中的object;

2.3 在这一次getBean的时候,A类返回了,所以B类会完成初始化,然后再在A类中注入B类

3 到这里,循环依赖就完成了,不知道有没有描述清楚:

我再描述一下把:

3.1 在A类第一次初始化的时候,会将A类放到表示正在创建的set集合中;将A类放到二级缓存的map中,然后在属性注入的时候,发现A类依赖了B类,会调用getBean,这时候,B类返回的是null,会进行初始化

3.2 B类初始化的流程和A类初始化流程是一样的,将B类放到表示当前bean正在创建的set集合中,将B类放到二级缓存中,然后进行B类的属性注入,在这是,会发现,B类依赖了A类,然后会调用getBean,这时候,虽然spring单实例池中,还没有A类,但是:二级缓存中有,所以就返回了二级缓存中的A类,让B类完成了初始化流程,

3.3 B类完成了初始化,那A类依赖的B也就注入了

最后再贴一张debug的流程图

红色的是A --> B --> A

绿色的是 属性注入具体调用的方法

最后再附上个人在学习spring源码时,在源码上加的注释

https://github.com/mapy95/spring-sourceCode

spring源码学习(三)--spring循环引用源码学习的更多相关文章

  1. Spring Boot 项目学习 (三) Spring Boot + Redis 搭建

    0 引言 本文主要介绍 Spring Boot 中 Redis 的配置和基本使用. 1 配置 Redis 1. 修改pom.xml,添加Redis依赖 <!-- Spring Boot Redi ...

  2. Zookeeper 源码(三)Zookeeper 客户端源码

    Zookeeper 源码(三)Zookeeper 客户端源码 Zookeeper 客户端主要有以下几个重要的组件.客户端会话创建可以分为三个阶段:一是初始化阶段.二是会话创建阶段.三是响应处理阶段. ...

  3. JVM 字节码(三)异常在字节码中的处理(catch 和 throws)

    JVM 字节码(三)异常在字节码中的处理(catch 和 throws) 在 ClassFile 中到底是如何处理异常的呢? 一.代码块异常 catch catch 中的异常代码块在异常是如何处理的呢 ...

  4. Spring IOC 源码简单分析 03 - 循环引用

    ### 准备 ## 目标 了解 Spring 如何处理循环引用 ##测试代码 gordon.study.spring.ioc.IOC03_CircularReference.java   ioc03. ...

  5. Spring的3级缓存和循环引用的理解

    此处是我自己的一个理解,防止以后忘记,如若那个地方理解不对,欢迎指出. 一.背景 在我们写代码的过程中一般会使用 @Autowired 来注入另外的一个对象,但有些时候发生了 循环依赖,但是我们的代码 ...

  6. Swift 学习笔记 (三) 之循环引用浅析

    原创:转载请注明出处 110.自动引用计数实践 下面的例子展示了自动引用计数的工作机制.例子以一个简单的Person类开始,并定义了一个叫name的常量属性: class Person {     l ...

  7. C语言/C++编程学习三种循环用法和区别

    C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...

  8. OC学习篇之---循环引用问题

    在之前的一片文章中,我们介绍了数组操作对象的时候引用问题以及自动释放池的概念: http://blog.csdn.net/jiangwei0910410003/article/details/4192 ...

  9. Spring Cloud系列(三):Eureka源码解析之服务端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-ser ...

随机推荐

  1. vuejs之路由应用之一

    什么是‘路由’,路由相当于一个映射,一个url地址对应一个组件,当url地址A变为url地址B,那么对应地址A的组件就会改变为对应地址B的组件.应用于spa,即:单页应用,url地址改变,它不会跳转页 ...

  2. python3 之 迭代器与生成器

    迭代器 迭代是Python最强大的功能之一,是访问集合元素的一种方式. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束. 迭代器只能往前不会后 ...

  3. css+js相关笔记

    作者:故事我忘了c个人微信公众号:程序猿的月光宝盒 css部分: 1.内联元素垂直居中的设置: (1) 设置父级元素的行高 line-height,和高度 height ​ 原则:line-heigh ...

  4. 使用Xdroid进行端口映射,出现adb server version (36) doesn't match this client (39); killing...的解决方案

    第一反应就是adb冲突了,因为Xdroid这个产品看起来就不像是给开发人员用的模拟器,因为不能选择各种版本进行适配,所以肯定自带了一个adb. whereis命令发现果然有两个adb,一个直接是安装在 ...

  5. Python和BeautifulSoup进行网页爬取

    在大数据.人工智能时代,我们通常需要从网站中收集我们所需的数据,网络信息的爬取技术已经成为多个行业所需的技能之一.而Python则是目前数据科学项目中最常用的编程语言之一.使用Python与Beaut ...

  6. Swift - 给UICollectionview设置组背景和圆角

    钟情圆角怎么办 最近由于我们的UI钟情于圆角搞得我很方,各种圆角渐变,于是就有了下面这篇给UICollection组设置圆角和背景色的诞生,不知道在我们平时有没有遇到这样子的一些需求,就是按照每一组给 ...

  7. 深入SQL Server 日期和时间的内部存储

    在SQL Server的内部存储中,日期和时间不是以字符串的形式存储的,而是使用整数来存储的.使用特定的格式来区分日期部分和时间部分的偏移量,并通过基准日期和基准时间来还原真实的数据. 一,DateT ...

  8. 通过Python将Excel表格信息导入数据库

    前言 公司原采用Excel表格方式记录着服务器资产信息,随着业务的增加,相应的硬件资产也增加,同时物理机虚拟化出多台虚拟机,存在表格管理杂乱.变更资产信息不能及时相互同步, 为了紧跟时代的步伐,老大搞 ...

  9. Git实战指南----跟着haibiscuit学Git(第五篇)

    笔名:  haibiscuit 博客园: https://www.cnblogs.com/haibiscuit/ Git地址: https://github.com/haibiscuit?tab=re ...

  10. 09-kubernetes configMap secret

    目录 配置容器化应用配置的方式 命令创建和测试configMap 创建一个Pod 挂在测试 命令行文件类创建方式 创建Pod测试 创建后测试 贴近实际进行测试 创建后测试 secret 举例测试 ge ...