在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. 阿里云ECS搭建kubernetes1.11

    环境信息 说明 1.使用kubeadm安装集群 虚拟机信息 hostname memory cpu disk role node1.com 4G 2C vda20G vdb20G master nod ...

  2. AE安装部署以及监测ArcEngine runtime 9.3是否安装

    目的:用ArcEngine9.3开发项目以后,用Visual Studio2008打包工具打包: 同时监测别的机器上是否有ArcEngine Runtime或者Desktop的支持. 解决方案: 1. ...

  3. C语言基础 -- 变量

    常用变量类型 ​​ 地址 小端 低地址保存低位,高地址保存高位 常用于 PC(复杂指令集) 大端 低地址保存高位,高地址保存低位 常用于 ARM/手机/网络(精简指令集)

  4. spring奇怪异常记录(会逐渐记录)

    1 严重: Context initialization failedorg.springframework.beans.factory.BeanCreationException: Error cr ...

  5. 金蝶天燕中间拒绝put、delete请求解决方案

    项目要求支持国产化,那就国产化呗!使用金蝶天燕中间件替代weblogic,一切部署好后发现所有以put.delete请求的按钮全部无效,原因是中间件配置文件默认拒绝put.delete请求 解决方案为 ...

  6. java 运算符&表达式

    1. java中,模运算符%可以获取整数除法的余数,同样适用于浮点类型数据.double y = 23.56; y%5;(即y mod 5 =3.56) [c/c++中,要求%两侧均为整数数据.] 2 ...

  7. Spring Boot SpringApplication启动类(二)

    目录 前言 1.起源 2.SpringApplication 运行阶段 2.1 SpringApplicationRunListeners 结构 2.1.1 SpringApplicationRunL ...

  8. Spring Boot SpringApplication启动类(一)

    目录 目录 前言 1.起源 2.SpringApplication 准备阶段 2.1.推断 Web 应用类型 2.2.加载应用上下文初始器 ApplicationContextInitializer ...

  9. RubyGem 下载时连接失败的解决方法

    RubyGem 下载 gem 包失败,有一定原因是 https 导致的. 搜索了很久,找到一个解决的方法. 1.下载 cacert.pem,也就是 curl 的证书. http://curl.haxx ...

  10. 阿里巴巴 Service Mesh 落地的架构与挑战

    点击下载<不一样的 双11 技术:阿里巴巴经济体云原生实践> 本文节选自<不一样的 双11 技术:阿里巴巴经济体云原生实践>一书,点击上方图片即可下载! 作者 | 方克明(溪翁 ...