《系列二》-- 5、单例bean缓存的获取
阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。
写在开始前的话:
阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了:
- beans
- core
- context
实际上我在博客里贴出来的还只是一部分内容,更多的内容,我放在了个人,fork自 spring 官方源码仓了; 而且对源码的学习,必须是要跟着实际代码层层递进的,不然只是干巴巴的文字味同嚼蜡。
这个仓设置的公共仓,可以直接拉取。
回到 doGetBean 初始的位置:


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

前边讲到 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.2:缓存中获取单例bean
5.2 缓存中获取单例bean 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了.前面已经提到过,单例在Spring的同一个容器内只会被创建一次,后续再获取bean直接从单例 ...
- Spring源码分析(十三)缓存中获取单例bean
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了 ...
- Spring IOC(三)单例 bean 的注册管理
Spring IOC(三)单例 bean 的注册管理 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 在 Spring 中 ...
- Spring IOC 容器源码分析 - 创建单例 bean 的过程
1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...
- Spring IOC 容器源码分析 - 获取单例 bean
1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...
- 【Spring源码分析】非懒加载的单例Bean初始化过程(上篇)
代码入口 上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了f ...
- Spring源码分析:非懒加载的单例Bean初始化过程(上)
上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了finish ...
- Spring单例Bean和线程安全
Spring的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢?例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框 ...
- Spring(六)核心容器 - 注册单例 Bean 实例、SingletonBeanRegistry 简介
前言 上篇文章我们对注册 Bean 的核心类 BeanDefinitionRegistry 进行了讨论,这里的注册 Bean 是指保存 Bean 的相关信息,也就是将 Bean 定义成 BeanDef ...
- Spring 源码学习 - 单例bean的实例化过程
本文作者:geek,一个聪明好学的同事 1. 简介 开发中我们常用@Commpont,@Service,@Resource等注解或者配置xml去声明一个类,使其成为spring容器中的bean,以下我 ...
随机推荐
- [转帖]数据库Ingres、Oracle、PostgreSQL、MySQL的历史总结
http://www.codeforest.cn/article/192# Ingres 1973年,加州大学伯克利分校的Michael Stonebraker和EugeneWong,从Edgar F ...
- [转帖]nginx中rewrite和if的用法及配置
nginx中rewrite和if的用法及配置 文章目录 nginx中rewrite和if的用法及配置 @[toc] 一.rewrite应用 1.rewrite跳转场景 2.rewrite实际场景 3. ...
- [转帖]badboy与jmeter的结合使用
https://blog.csdn.net/weixin_41754309/article/details/107106855 欢迎关注[无量测试之道]公众号,回复[领取资源], Python编程学习 ...
- CentOS7 上面升级git 2.24的方法
本来想使用tar包进行安装 但是发现tar包安装时总是报错如下: [root@centos76 git-2.25.0]# make LINK git-imap-send imap-send.o: In ...
- Zabbix6.0的安装与IPMI的简单使用
zabbix简介 1.zabbix的安装与使用 建议使用CentOS8进行部署, 不建议使用CentOS7, rpm包直接部署的话,CentOS8比较容易一些 支持mysql数据库.建议先期部署mys ...
- 信创-飞腾CPU路线图
- 拜占庭将军问题和 Raft 共识算法讲解
作者: 京东物流 郭益如 导读 在分布式系统中, 什么是拜占庭将军问题?产生的场景和解决方案是什么?什么是 Raft 共识算法?Raft 算法是如何解决拜占庭将军问题的?其核心原理和算法逻辑是什么?除 ...
- 深入浅出RPC服务 | 不同层的网络协议
导读: 本系列文章从RPC产生的历史背景开始讲解,涉及RPC核心原理.RPC实现.JSF的实现等,通过图文类比的方式剖析它的内部世界,让大家对RPC的设计思想有一个宏观的认识. 作者:王禹展 京东 ...
- 使用Lua做为MMOARPG游戏逻辑开发脚本的一点体会
项目背景 目前在一个大型MMOARPG游戏中使用Lua做为逻辑开发语言,Lua占整体代码量的80%. 我们这个MMO游戏开发近2年,客户端8人,项目组总体人数在100人(美术占70%),目前代码量很大 ...
- 手撕Vue-监听数据变化
经过上一篇的介绍,已经实现了将模板编译成具体数据,接下来要介绍的是如何监听数据的变化,本章主要完成这个需求即可. 在我们文章的开始,我写了一个 Vue 双向数据绑定原理的文章当中封装了一个 Obser ...