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 对象,一般是通过反射调用默认的构造器。populateBean
bean 属性注入,在这个步骤会从 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, ...
随机推荐
- OpenCV之Vec3f
Vec3f表示的是3通道float类型的 Vect,就相当于3通道float类型的图像(这是其中一个具体化),解释可以从源代码中看出来. 下面给出一个具体的例子: Vec3f point = Vec3 ...
- CGLIB代理基础
本文意在讲解CGLIB的基础使用及基本原理. 一.CGLIB的基本原理: 依赖ASM字节码工具,通过动态生成实现接口或继承类的类字节码,实现动态代理. 针对接口,生成实现接口的类,即implement ...
- 进制转换&数据类型(1)
一: 进制转换 在计算机中, 数据都是以0和1来表示的 进制: 进位制 十进制: 数字由0~9这10个数字来表示, 逢10进1位 0 1 2 3 4 5 6 7 8 9 10 二进制: 数字由0和1这 ...
- ssh 使用 aws
使用 PuTTY 从 Windows 连接到 Linux 实例 启动您的实例之后,您可以连接到该实例,然后像使用您面前的计算机一样来使用它. 注意 启动实例后,需要几分钟准备好实例,以便您能连接到实例 ...
- tomcat部署war包
部署步骤 1.下载tomcat 直接在网上下载即可,随便把包下到一个地方 下面文中的xxx均代表tomcat的安装目录 2.将java工程导出war包 在intellij idea的执行左侧选中t ...
- C# 写App.config配置文件的方法
private static void AccessAppSettings() { //获取Configuration对象 Configuration config = ConfigurationMa ...
- Win7下VB6.0不能加载mscomctl.ocx的解决办法
下载这个:http://pan.baidu.com/s/1sjJgrbJ 然后在命令框下注册这个组件: regsvr32 mscomctl.ocx 即可
- idea使用maven打包jar包
1.在pom.xml中加入以下内容: <?xml version="1.0" encoding="UTF-8"?> <project xmln ...
- UA判断跳转
<script type="text/javascript"> UA = navigator.userAgent.toLowerCase(); url = window ...
- 五:python 对象类型详解二:字符串(上)
一:常量字符串 常量字符串用起来相对简单,也许最复杂的事情就是在代码中有如此多的方法来编写它们. eg:单引号:'spam"m' , 双引号: “spa'm” , 三引号:‘’‘... ...