Spring 循环引用(二)源码分析
Spring 循环引用(二)源码分析
Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)
Spring 循环引用相关文章:
- 《Spring 循环引用(一)一个循环依赖引发的 BUG》:https://www.cnblogs.com/binarylei/p/10325698.html
 - 《Spring 循环引用(二)源码分析》:https://www.cnblogs.com/binarylei/p/10326046.html
 
一、Spring 中单例 bean 的管理
Spring 对单例 bean 的管理都是在 DefaultSingletonBeanRegistry 中完成的,这里会涉及到其内部所使用的几个内部属性:
// 1.1 保存最终创建成功的单例 beanName -> beanInstance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 1.2 中间变量,beanName -> Objectfactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 1.3 中间变量,bean 还在创建的时候就可以获取,用于检测循环引用
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
这里涉及用于存储 bean 的不同的 Map,可能让人感到崩溃,简单解释如下:
singletonObjects:用于保存 beanName 和创建 bean 实例之间的关系。beanName -> beanInstancesingletonFactories:用于保存 beanName 和对象工厂的引用关系,一旦最终对象被创建(通过 objectFactory.getObject()),此引用信息将删除。beanName -> ObjectfactoryearlySingletonObjects:用于保存 beanName 和创建的原始 bean 的引用关系,注意这里是原始 bean,即使用工厂方法或构造方法创建出来的对象,一旦对象最终创建好,此引用信息将删除。 与 singletonObjects 的不同之处在于,此时 bean 还在创建过程中,而且之后还可以进行增强,也就是代理后这两个 bean 就不是同一个了。可以通过 getBean 方法获取到了,其目的是用来检测循环引用。
从上面的解释,可以看出,这 singletonFactories 和 earlySingletonObjects 都是一个临时的辅助状态。在所有的对象创建完毕之后,此两个对象的 size 都为 0。那么再来看下这两个对象如何进行协作:
(1) 方法1:创建单例对象
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 1. 将这个 bean 添加到 singletonsCurrentlyInCreation 集合中,这样就可以判断 bean 是否存在创建
            beforeSingletonCreation(beanName);
            // 2. 初始化 bean,委托给 ObjectFactory 完成
            singletonObject = singletonFactory.getObject();
            // 3. 从 singletonsCurrentlyInCreation 移除该 bean
            afterSingletonCreation(beanName);
            // 4. 创建完成进行注册,这样下次就可以从缓存中直接获取这个对象了
            addSingleton(beanName, singletonObject);
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}
(2) 方法2:单例对象创建完成进行注册
protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}
(3) 方法3:将实例化后的对象暴露到容器中
Spring 在 bean 实例化后就会调用 addSingletonFactory 将这个对象提前暴露到容器中,这们就可以通过 getBean(A) 得到这个对象,即使这个对象仍正在创建。用于解决循环依赖。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}
(4) 方法4:从缓存中获取 bean
这个方法也是专门用于解决循环依赖的问题,当不存在循环依赖时 earlySingletonObjects 总是 null。
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) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}
二、Spring 创建 bean 过程
我们从 BeanFactory#getBean(beanName) 调用说起,看一下这几个方法的调用顺序:
2.1 AbstractBeanFactory#doGetBean
这个方法先从缓存中获取 bean,没有再创建 bean,因此会调用方法 4 和方法 1,我们看一下调用过程。
(1) getSingleton(beanName, true)
doGetBean 首先从缓存中获取数据,Object sharedInstance = getSingleton(beanName),这个方法最终会调用 getSingleton(beanName, true)。
(2) getSingleton(beanName, singletonFactory)
如果缓存中没有 bean,则会调用 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 来创建一个新 bean,代码如下:
if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
一旦调用 getSingleton(beanName, singletonFactory) 方法,这个方法创建开始时就会标记这个 bean 为正在创建,创建结束后移除对应的标记。直接创建 bean 的过程实际上是委托给了 createBean 方法。继续跟踪这个方法。
2.2 AbstractAutowireCapableBeanFactory#doCreateBean
doCreateBean 方法中完成了单例的 bean 有以下几个主要的步骤:
createBeanInstance实例化 bean 对象,一般是通过反射调用默认的构造器。populateBeanbean 属性注入,在这个步骤会从 Spring 容器中查找对应属性字段的值,解决循环依赖问题。initializeBean调用的 bean 定义的初始化方法。
(3) addSingletonFactory(beanName, singletonFactory)
在 createBeanInstance 后 populateBean 前 Spring 会将这个实例化的 bean 提前暴露到容器中,这样 populateBean 属性注入时就可以通过 getBean(A) 查找到。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
(4) getSingleton(beanName, false)
在 bean 初始化完成还后还需要进行依赖的检查,这时因为提前暴露的这个 bean(即使用工厂方法或构造方法创建出来的对象) initializeBean 还可以进行增强,这样这两个 bean 就不是同一个了。Spring 默认是不允许这种情况发生的。
if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                    actualDependentBeans.add(dependentBean);
                }
            }
            if (!actualDependentBeans.isEmpty()) {
                throw new BeanCurrentlyInCreationException(beanName,
                        "Bean with name '" + beanName + "' has been injected into other beans [" +
                        StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                        "] in its raw version as part of a circular reference, but has eventually been " +
                        "wrapped. This means that said other beans do not use the final version of the " +
                        "bean. This is often the result of over-eager type matching - consider using " +
                        "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
        }
    }
}
(5) addSingleton(beanName, singletonObject)
在 bean 创建结束后还有一步是在 getSingleton(beanName, singletonFactory) 中完成的,调用 addSingleton(beanName, singletonObject),即注册最终的 bean,同时清空中间的辅助状态。
这样单例 bean 的创建过程就完成了,下面就需要分析循环引用下 singletonFactories、earlySingletonObjects 这两个集合的状态。
三、循环引用下 Bean 状态分析
3.1 正常情况
在正常的情况下,调用顺序如下:以下有无,表示是否持有对指定 Bean 的引用
| 过程 | 方法 | singletonFactories | earlySingletonObjects | singletonObjects | 
|---|---|---|---|---|
| 缓存中获取 | getSingleton(beanName, true) | 无 | 无 | 无 | 
| 创建 bean | getSingleton(beanName, singletonFactory) | 无 | 无 | 无 | 
| 提前暴露到容器中 | addSingletonFactory(beanName, singletonFactory) | 有 | 无 | 无 | 
| 依赖检查 | getSingleton(beanName, false) | 有 | 无 | 无 | 
| 注册 | addSingleton(beanName, singletonObject) | 无 | 无 | 有 | 
可以看到正常情况下,单例 bean 暴露的对象只会出现在 singletonFactories 集合中,不可能出现在 earlySingletonObjects 集合中,除非在创建 bean 的过程中又调用了 getSingleton(beanName, true) 方法,也就是此时出现了循环引用。
3.2 循环引用
但是出现循环引用之后呢,就会出现这种情况:
| 过程 | 方法 | singletonFactories | earlySingletonObjects | singletonObjects | 
|---|---|---|---|---|
| 缓存中获取 A | getSingleton(A, true) | A无B无 | A无B无 | A无B无 | 
| 创建 A | getSingleton(A, singletonFactory) | A无B无 | A无B无 | A无B无 | 
| 暴露 A 到容器中 | addSingletonFactory(A, singletonFactory) | A有B无 | A无B无 | A无B无 | 
| populateBean(A, mbd, instanceWrapper) A 注入 B 时又依赖了 A,此时由 B 准备解析 A…… | ||||
| 缓存中获取 A | getSingleton(A, true) | A无B有 | A有B无 | A无B无 | 
| 注册 B | addSingleton(B, singletonObject) | A无B无 | A有B无 | A无B有 | 
| populateBean(A, mbd, instanceWrapper) 完成属性注入 | ||||
| A- = initializeBean(beanName, exposedObject, mbd) 在 initializeBean 之后 A 变为 A- | ||||
| 依赖检查 | getSingleton(beanName, false) | A无B无 | A有B无 | A无B有 | 
| 注册 A | getSingleton(beanName, false) | A无B无 | A无B无 | A有B有 | 
在上面这个过程中,在对 A 进行验证时,就会从 earlySingletonObjects 中取得一个 A,但是这个 A 和后面的 A- 可能不是同一个对象,这是因为有了 beanPostProcessor 存在,它可以改变 bean 的最终值,比如对原始 bean 进行封装,代理等。在这个过程中,出现了 3 个对象 A, A-, B,而 B 中所持有的 A 对象为原始的 A。如果这里的 A 和 A- 不是同一个对象,即产生了 beanA 有了 beanB 的引用,但 beanB 并没有 beanA 的引用,而是另一个 beanA 的引用。
每天用心记录一点点。内容也许不重要,但习惯很重要!
Spring 循环引用(二)源码分析的更多相关文章
- Spring 循环引用(三)源码深入分析版
		
@ 目录 前言 正文 分析 doGetBean 为什么Prototype不可以 createBean doCreateBean getEarlyBeanReference getSingleton b ...
 - spring boot 2.0 源码分析(四)
		
在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...
 - Spring中Bean命名源码分析
		
Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Crea ...
 - Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)
		
Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...
 - springboot bean的循环依赖实现 源码分析
		
springboot bean的循环依赖实现 源码分析 本文基于springboot版本2.5.1 <parent> <groupId>org.springframework. ...
 - spring boot 2.0 源码分析(一)
		
在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...
 - Spring JPA实现逻辑源码分析总结
		
1.SharedEntityManagerCreator: entitymanager的创建入口 该类被EntityManagerBeanDefinitionRegistrarPostProcesso ...
 - Spring Boot 自动配置 源码分析
		
Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...
 - spring boot 2.0 源码分析(二)
		
在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...
 - 设计模式(二十一)——解释器模式(Spring 框架中SpelExpressionParser源码分析)
		
1 四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求 1) 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复 2) 在分别输入 a ,b, c, ...
 
随机推荐
- shell脚本-删除当天日期前3个月的数据表
			
#!/bin/bash #author:skycheng #get current date string datestr=`date +'%Y-%m-%d'` start_time=`date +' ...
 - linux安装mysql5.1
			
一.卸载mysql 1.检测系统是否已经安装过mysql或其依赖,若已装过要先将其删除 # yum list installed | grep mysql mysql-libs.i686 ...
 - html实现导航栏效果
			
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
 - ServiceWork的五种状态
			
[ServiceWork的五种状态] installing.installed.activating.activated.redundant 参考:https://developer.mozilla. ...
 - arguments对象的callee属性和caller属性
			
js中的arguments对象代表正在执行的函数和调用它的函数的参数.arguments对象有两个属性,callee和caller.collee表示当前正在执行的方法,caller表示调用该方法的对象 ...
 - C/s程序过时了吗?
			
目前的程序从原来的形态演变成了 C/s,B/s,和手机端. 其实应该各有自己的客户群,及定位. 比如C/s为单机版的可以完成个性化突出的复杂客户端应用,企业级别的应用. B/s的特点安装简单,功能制作 ...
 - HDU-1002.大数相加(字符串模拟)
			
本题大意:给出两个1000位以内的大数a 和b,让你计算a + b的值. 本题思路:字符串模拟就能过,会Java的大佬应该不会点进来...... 参考代码: #include <cstdio&g ...
 - elasticsearch的索引操作和文档操作总结
			
参考文档:https://es.xiaoleilu.com/010_Intro/00_README.html 一.索引操作 1.查看当前节点的所有的index 查看当前节点的所有的index [roo ...
 - java发送http连接
			
原生方式:@转载文章 import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamRead ...
 - JQuery|jstl判断是否为空
			
//有如下三种判断 var A=$("#**).val(); if(A==null||A==undefined||A==""){ //处理 } //参考文章1说下面方法效 ...