Spring 获取单例流程(三)
读完这篇文章你将会收获到
Spring何时将bean加入到第三级缓存和第一级缓存中Spring何时回调各种Aware接口、BeanPostProcessor、InitializingBean等
相关文章
- Spring 获取单例流程(一)
- Spring 获取单例流程(二)
- Spring 循环依赖 (公众号内查看(同时发布无法获取到链接))
概述
上两篇文章 Spring 获取单例流程(一) 和 Spring 获取单例流程(二) 介绍了 getBean 前面的流程,今天最后的收尾,把后面的流程继续一起学习下

源码分析
// 我依赖的大哥都好了
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
// 从三级缓存中移除这个 beanName 因为它可能被放进去了 因为放进去三级缓存可以解决 setter 的循环依赖
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
如果我们要创建的 bean 是一个单例,
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// 看看第一级缓存中有没有
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 将 beanName 加入到 singletonsCurrentlyInCreation 中,代表它正在创建中
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException ex) {
throw ex;
} catch (BeanCreationException ex) {
throw ex;
} finally {
// singletonsCurrentlyInCreation 从这里面移除掉
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 加入缓存中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
删减了部分不重要的代码,我们大致来看看其流程
- 获取同步锁,然后判断第一级缓存是否已经存在这个
bean了 - 如果不存在则将
beanName加入到singletonsCurrentlyInCreation中,代表它正在创建中 - 然后调用参数的
ObjectFactory的getObject方法获得一个bean - 最后将其从
singletonsCurrentlyInCreation中移除、代表其已经创建完成了 - 最后将其加入到第一级缓存中、从第二级和第三级缓存中移除掉
全篇完结.终 !!!
其实真正的秘密藏身在参数的 ObjectFactory 中,从上面的流程中可以宏观的知道 Spring 创建 bean 的一个流程
现在我们在看看参数的 ObjectFactory 究竟干啥子了
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
...........
...........
try {
// 真正 处理逻辑
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(xxxx);
}
}
干活的还是 do 开头的大佬
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 根据指定 bean 使用对应的策略创建新的实例、如工厂方法、构造函数自动注入、简单初始化
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
..........
.........
// 是否需要提前曝光、用来解决循环依赖的问题
// 是单例&允许循环依赖&正在创建中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 为了避免后期循环依赖、可以在 bean 初始化前将创建实例的ObjectFactory 加入工厂
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 填充属性
populateBean(beanName, mbd, instanceWrapper);
// 调用初始方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
.........
}
........
.......
return exposedObject;
}
上面的流程大致就是
createBeanInstance这个方法根据你的配置以及你的bean的情况选择出一种创建bean的方法、可能是工厂方法、可能是某个构造函数、可能是默认的构造函数。这里包含了当一个构造函数的参数是另一个bean的时候、它会通过getBean的方法获取这个参数的bean然后将创建好的
bean加入到第三级缓存中,默认设置我们是允许循环依赖的populateBean方法就是我们填充属性了、如果你依赖的其他Spring的其他bean是通过这种方式注入的话(autowireByNameautowireByType)、就是在这一步注入的了,他获取其他bean也是通过getBean的方式获取protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
.........
......... if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// Add property values based on autowire by type if applicable.
if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
.........
.........
}
initializeBean则是调用我们的各种回调接口、Aware类型的、BeanPostProcessor、InitializingBean、自定义初始化函数protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {。
// 调用各种 Aware 接口
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
} else {
// 调用各种 Aware 接口
invokeAwareMethods(beanName, bean);
} Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 调用 BeanPostProcessor postProcessBeforeInitialization
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
} try {
// 调用 InitializingBean 、自定义的初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
// 调用 BeanPostProcessor postProcessAfterInitialization
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
其实整体的流程就差不多了
总结
- 根据参数中的
name找出对应的beanName、无论这个name是别名或者是一个factoryBean的beanName - 查看缓存中是否包含这个
beanName对象- 先从一级缓存
singletonObjects中看看有没有 - 然后从二级缓存
earlySingletonObjects - 都没有的话再从三级缓存
singletonFactories中看看有没有
- 先从一级缓存
- 如果缓存中有
bean、那么我们还是需要处理一下这个bean- 如果
Spring缓存中返回的bean是factoryBean、而用户也想要的是一个beanFactory(参数name中的前缀是&)、那么我们直接返回 - 如果
Spring缓存中返回的bean是普通的bean、而用户也想要的是一个普通的bean、那么就直接返回 - 如果
Spring缓存中返回的bean是一个factoryBean、而用户想要的是一个普通的bean、那么我们就要从factoryBean中获取这个bean - 而从
factoryBean中获取这个bean的过程中、需要调用到前置处理、后置处理和我们常用的接口回调BeanPostProcessor
- 如果
- 如果缓存中没有
bean、则判断是否是prototype类型并且循环依赖 - 如果没有则尝试能否在父容器中找到该
bean - 如果父容器也没有则获取该
beanName对应的beanDefinition找出其依赖的beanName - 判断该
beanName与 依赖的beanName是否循环依赖、没有则注册其依赖关系并调用getBean方法去创建依赖的beanName - 将
beanName加入到singletonsCurrentlyInCreation中 - 根据指定 bean 使用对应的策略创建新的实例、如工厂方法、构造函数、创建一个不完整的 bean
- 将创建好的 bean 加入到第三级缓存中
- 进行属性填充、进行各种接口回调
- 最后将其从
singletonsCurrentlyInCreation中移除、代表其已经创建完成了 - 最后将其加入到第一级缓存中、从第二级和第三级缓存中移除掉
- 返回
bean给调用方
其实总体的流程还是不算复杂把、我们也可以从中收获到一些东西。其实我们最关心也是面试最常问的一个问题就是、Spring 如何解决循环依赖的问题、感兴趣的可以看看这篇文章公众号内的 Spring 循环依赖 这篇文章


Spring 获取单例流程(三)的更多相关文章
- Spring 获取单例流程(二)
读完这篇文章你将会收获到 Spring 中 prototype 类型的 bean 如何做循环依赖检测 Spring 中 singleton 类型的 bean 如何做循环依赖检测 前言 继上一篇文章 S ...
- Spring 获取单例流程(一)
读完这篇文章你将会收获到 在 getBean 方法中, Spring 处理别名以及 factoryBean 的 name Spring 如何从多级缓存中根据 beanName 获取 bean Spri ...
- Spring IOC 容器源码分析 - 获取单例 bean
1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...
- Spring源码分析(十五)获取单例
本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 之前我们讲解了从缓存中获取单例的过程,那么,如果缓存中不存在已经加载的单例be ...
- 5.2:缓存中获取单例bean
5.2 缓存中获取单例bean 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了.前面已经提到过,单例在Spring的同一个容器内只会被创建一次,后续再获取bean直接从单例 ...
- Spring源码分析(十三)缓存中获取单例bean
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了 ...
- 【转】Spring Bean单例与线程安全
一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...
- Spring Bean单例与线程安全
一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...
- Spring的单例实现原理-登记式单例
单例模式有饿汉模式.懒汉模式.静态内部类.枚举等方式实现,但由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式(登记式单例). 什么是单例注册表呢, 登 ...
随机推荐
- (Java实现) 洛谷 P1025 数的划分
题目描述 将整数n分成k份,且每份不能为空,任意两个方案不相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5; 1,5,1; 5,1,1. 问有多少种不同的分法. 输 ...
- Java实现 LeetCode 744 寻找比目标字母大的最小字母(二分法)
744. 寻找比目标字母大的最小字母 给定一个只包含小写字母的有序数组letters 和一个目标字母 target,寻找有序数组里面比目标字母大的最小字母. 在比较时,数组里字母的是循环有序的.举个例 ...
- Java实现P2102 -- 正整数序列
P2102 – 正整数序列 给定正整数n, 你的任务是用最少的操作次数把序列1,2,-,n中的所有数都变成0.每次操作可从序列中选择一个或多个整数, 同时减去一个相同的正整数.比如,1,2,3可以把2 ...
- Java实现 蓝桥杯VIP 算法提高 进制转换
算法提高 进制转换 时间限制:1.0s 内存限制:256.0MB 问题描述 程序提示用户输入三个字符,每个字符取值范围是0-9,A-F.然后程序会把这三个字符转化为相应的十六进制整数,并分别以十六进制 ...
- Java实现子序列问题
一个串的子串是指该串的一个连续的局部.如果不要求连续,则可称为它的子序列. 比如对串: "abcdefg" 而言,"ab","abd",&q ...
- Java实现LeetCode_0012_IntegerToRoman
package javaLeetCode.primary; import java.util.Scanner; public class IntegerToRoman_12 { public stat ...
- C++实现车轮轨迹
标题:车轮轴迹 栋栋每天骑自行车回家需要经过一条狭长的林荫道.道路由于年久失修,变得非常不平整.虽然栋栋每次都很颠簸,但他仍把骑车经过林荫道当成一种乐趣. 由于颠簸,栋栋骑车回家的路径是一条上下起伏的 ...
- PE文件介绍 (2)-DOS头,DOS存根,NT头
PE头 PE头由许多结构体组成,现在开始逐一学习各结构体 0X00 DOS头 微软创建PE文件格式时,人们正广泛使用DOS文件,所以微软充分考虑了PE文件对DOS文件的兼容性.其结果是在PE头的最前面 ...
- 纯 HTML/CSS 高仿 Win10 加载动画
自己做的超高仿Win10加载动画(应该是全网最像的 HTML 实现了),自己想用就拿去用吧 转圈加载 在线演示 HTML: <div class="loading"> ...
- 2019-02-09 python爬取mooc视频项目初级简单版
今天花了一下午时间来做这东西,之前没有自己写过代码,50几行的代码还是查了很多东西啊,果然学起来和自己动起手来完全是两码事. 方案:requests库+正则表达式提取视频下载链接+urlretriev ...