《系列二》-- 5、单例bean缓存的获取

系列,bean,缓存,获取 · 浏览次数 : 11

小编点评

**1. 判断bean是否完成整个加载流程** * `singletonObjects` 缓存加载的单例 bean。 * `earlySingletonObjects` 缓存提前暴露的 bean。 * `singletonFactories` 缓存对象工厂。 **2. 判断当前bean是否被加载过,是否已作为提前暴露的bean** * `isSingletonCurrentlyInCreation` 方法用于判断当前bean是否正在创建中。 * `earlySingletonObjects` 用于记录提前暴露的 bean。 **循环依赖相关概念:** * `singletonObjects` 和 `earlySingletonObjects` 缓存创建的 bean。 * `singletonFactories` 缓存对象工厂。 * `doCreateBean` 方法在创建过程中从 `singletonFactories` 中获取 bean。

正文

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。

写在开始前的话:

阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了:

  • beans
  • core
  • context

实际上我在博客里贴出来的还只是一部分内容,更多的内容,我放在了个人,fork自 spring 官方源码仓了; 而且对源码的学习,必须是要跟着实际代码层层递进的,不然只是干巴巴的文字味同嚼蜡。

https://gitee.com/bokerr/spring-framework-5.0.x-study

这个仓设置的公共仓,可以直接拉取。



Spring源码阅读系列--全局目录.md



回到 doGetBean 初始的位置:

img.png

img.png

1 判断bean是否完成整个加载流程

    /** Cache of singleton objects: bean name --> bean instance */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

如下是 singletonObjects 的定义,一个线程安全的HashMap,顾名思义只有是单例的bean被成功加载后才会被它缓存,所以非单例bean 是不可能从中获取到的。

如果我们请求的bean 是单例bean且已经被加载过,完成了整个加载流程,此时程序在方法第一行就已经可以退出了。

不是就接着往下:

2 判断当前bean是否被加载过,是否已作为提前暴露的bean

这里的逻辑其实很简单,无非就是双重锁机制。第一次获取到null后,判断当前bean是否已经进入创建流程 isSingletonCurrentlyInCreation 这个方法名很直观。

isSingletonCurrentlyInCreation 返回true时,说明该bean已经在别的线程 (或递归循环依赖) 中进入创建流程了。

【PS * 前边提到过,bean被加载时,都会递归的去加载它所依赖的 bean】

这里还有另一位主角:

    /** Cache of early singleton objects: bean name --> bean instance */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

它用于提前暴露bean, 你看这里先取了一次,没取到才正式进入创建流程,同样也是为了避免提前暴露动作重复。

关于循环依赖

还有另一位需要被重视的成员:

    /** Cache of singleton factories: bean name --> ObjectFactory */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

看到了么,它保存的对象实现了 ObjectFactory 接口,不陌生吧,前边讲循环依赖消解的文章介绍过它。

这里还有另一个很有意思的现象不知道你有没有注意到:earlySingletonObjects保存该bean之后,就立即从 singletonFactories 中移除了。

这里说明他们极有可能是一个传递关系:当一个bean 第一次被加载时,默认会保存到 singletonFactories,
这时候这个bean 还在创建流程中,可能出现的情况是这时有另一个 bean 也依赖于它 (参考循环依赖,如果 spring 加载bean的过程是单线程,这时铁定出现循环依赖了。)

根据前边讲循环依赖的文章:

this.earlySingletonObjects.put(beanName, singletonObject);

bean的提前暴露它不就来了么?别看这里只有几行代码,结合上下文之后才会知道它的重要性。

img.png

前边讲到 bean 从 singletonFactories 传递到 earlySingletonObjects

那么我们再去追究下, singletonFactories 中是什么时候注入的,然后发现了只有一个地方调用了其上的:singletonFactories.put() 方法

再往上追踪,我们发现了一个名叫: doCreateBean 的方法,这是我们后续流程中关注的方法;它处于从 0 开始创建一个bean 的流程中。

先记住它,后边我们会再见到它的。

结合上边分析的流程,这里还有另一个定义,那就是三级缓存;spring 在消解循环依赖时引入了三级缓存:

  • 一级缓存: Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256)

  • 二级缓存: Map<String, Object> earlySingletonObjects = new HashMap<>(16)

  • 三级缓存: Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)

首先时创建全新bean 时,将其注入了 三级缓存中。

所以创建bean 的流程时,先从一级缓存获取,如果成功直接返回;

否则从二级缓存中,成功获取到bean,那么直接返回;

如果从二级缓存也无法获取,那么尝试从三级缓存获取,若从三级缓存成功获取,那么从三级缓存中移除该bean,并转移到二级缓存中。

最终,若三级缓存也无法获取,说明是在获取一个从未被加载的 bean。

最终,第一次被加载的bean,最初会被缓存到,三级缓存中,bean 创建流程中,关注上述提到的 doCreateBean()方法即可闭环。

与《系列二》-- 5、单例bean缓存的获取相似的内容:

《系列二》-- 5、单例bean缓存的获取

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 8、单例bean的创建

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 1、BeanFactory.getBean 总览

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 3、FactoryBean 的使用

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 2、bean 的作用域: Scope 有哪些

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 4、循环依赖及其处理方式

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 6、从零开始的 bean 创建

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 7、后置处理器-PostProcessor

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 9、bean属性填充

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

《系列二》-- 10、initialize-初始化bean

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了