3.4 spring5源码系列--循环依赖的设计思想
前面已经写了关于三篇循环依赖的文章, 这是一个总结篇
第一篇: 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
第二篇: 3.2spring源码系列----循环依赖源码分析
第三篇: 3.3 Spring5源码---循环依赖过程中spring读取不完整bean的最终
现在总结循环依赖的思想
思想才是我们可以在工作中借鉴使用的
1. 循环依赖的三级缓存设计
2. 接口函数
一. 循环依赖的三级缓存设计
再循环依赖的过程中设计了三级缓存, 他们的作用分别是
1. 一级缓存: 用来存放完整的bean
2. 二级缓存: 用来存放早期的,纯净的bean
3. 三级缓存: 用来存放接口函数.
/** Cache of singleton objects: bean name to bean instance. */
/**
* 一级缓存 这个就是我们大名鼎鼎的缓存池, 用于保存我们所有的实例bean
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */
/**
* 三级缓存 该map用户缓存key为beanName, value为objectFactory(包装为早期对象)
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */
/**
* 二级缓存, 用户缓存我们的key为beanName, value是我们的早期对象(此时对象属性还没有...)
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
细细想来, 这三个缓存都非常有存在的必要.
1.1 如果去掉二级缓存, 只有一个一级缓存可不可以?
我们知道, 最开始有的是二级缓存. 因为是在createBean的三个步骤中,第一个步骤实例化bean的时候加入到二级缓存的. 这个时候为什么一定要加入二级缓存呢, 如果不加入会怎么样呢?
也就是去掉二级缓存可不可以. 当然是不可以的. 去掉二级缓存, 那就是最后整个bean经历三个过程都被创建成功以后, 将一个成熟的bean放入到一级缓存中. 这中间有什么问题呢?
问题在于多线程. 整个bean创建的过程那么长, 多线程进来. 其实bean已经在创建中了, 可是因为还没有放入到一级缓存, 所以会认为没有创建. 导致重复创建bean.
1.2 三级缓存到底什么用?
三级缓存里存放的是一个接口函数. 这个接口函数什么用呢? 就相当于js中的回调函数. 我在前面定义好, 但是不执行. 直到满足条件了, 才执行. 这个方法, 可以大范围应用到实践工作中.
比如: 调用动态代理创建bean. 刚开始实例化完成以后, 我就赋予你这个能力, 你可以调用动态代理. 但是, 到后面, 你是否真的能够运用这个能力呢? 不一定, 只有满足条件, 才会运用这个能力.
二. 定义接口函数, 也叫钩子函数
在循环依赖源码中, 两次使用到接口函数的方式.
第一个是创建bean的时候. 第二个是三级缓存
下面来看看源码,
第一次: 创建bean的时候, 定义了一个钩子函数createBean()
sharedInstance = getSingleton(beanName, () -> {
try {
// 这里定义了一个钩子函数. 此时只是定义, 并不执行. 在真正需要创建bean的地方才会执行
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
实际上调用的时机是: 在getSingleton方法里面. 回调接口函数.
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
// 第一步: 从一级缓存中获取单例对象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// 第二步: 将bean添加到singletonsCurrentlyInCreation中, 表示bean正在创建
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 第三步: 这里调用getObject()钩子方法, 就会回调匿名函数, 调用singletonFactory的createBean()
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
第二次调用: 是在三级缓存定义的时候
调用addSingletonFactory(...)定义了一个钩子函数. 这里仅仅是定义, 并不执行
// 把我们的早期对象包装成一个singletonFactory对象, 该对象提供了getObject()方法, 把静态的bean放到三级缓存中去了.
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
然后进入到addSingletonFactory内部, 只是把singletonFactory放入到了三级缓存中, 这里只是定义, 也并没有执行
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); // 添加到已经注册的singleton实例.
this.registeredSingletons.add(beanName);
}
}
}
什么时候执行的呢? 再从缓存中获取对象的时候.
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存中获取bean实例对象
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 如果在第一级的缓存中没有获取到对象, 并且singletonsCurrentlyIncreation为true,也就是这个类正在创建.
* 标明当前是一个循环依赖.
*
* 这里有处理循环依赖的问题.-- 我们使用三级缓存解决循环依赖
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
/**
* 从二级缓存中拿bean, 二级缓存中的对象是一个早期对象
* 什么是早期对象?就是bean刚刚调用了构造方法, 还没有给bean的属性进行赋值, 和初始化, 这就是早期对象
*/ singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
/**
* 从三级缓存拿bean, singletonFactories是用来解决循环依赖的关键所在.
* 在ios后期的过程中, 当bean调用了构造方法的时候, 把早期对象包装成一个ObjectFactory对象,暴露在三级缓存中
*/
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
/**
* 在这里通过暴露的ObjectFactory包装对象. 通过调用他的getObject()方法来获取对象
* 在这个环节中会调用getEarlyBeanReference()来进行后置处理
*/
singletonObject = singletonFactory.getObject();
// 把早期对象放置在二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 删除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
在这里调用三级缓存, singletonObject = singletonFactory.getObject(); 回调钩子函数.
3.4 spring5源码系列--循环依赖的设计思想的更多相关文章
- 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...
- 3.2spring源码系列----循环依赖源码分析
首先,我们在3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 中手写了循环依赖的实现. 这个实现就是模拟的spring的循环依赖. 目的是为了更容易理解spring源码 ...
- Spring IOC 容器源码分析 - 循环依赖的解决办法
1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...
- Spring源码解析——循环依赖的解决方案
一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...
- Spring源码之循环依赖
https://www.cnblogs.com/longy2012/articles/12834762.html https://www.bilibili.com/video/BV1iD4y1o7pM ...
- 2.3 spring5源码系列---内置的后置处理器PostProcess加载源码
本文涉及主题 1. BeanFactoryPostProcessor调用过程源码剖析 2. 配置类的解析过程源码 3. 配置类@Configuration加与不加的区别 4. 重复beanName的覆 ...
- 3.3 Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案
根据之前解析的循环依赖的源码, 分析了一级缓存,二级缓存,三级缓存的作用以及如何解决循环依赖的. 然而在多线程的情况下, Spring在创建bean的过程中, 可能会读取到不完整的bean. 下面, ...
- 【spring源码系列】之【xml解析】
1. 读源码的方法 java程序员都知道读源码的重要性,尤其是spring的源码,代码设计不仅优雅,而且功能越来越强大,几乎可以与很多开源框架整合,让应用更易于专注业务领域开发.但是能把spring的 ...
- Ioc容器依赖注入-Spring 源码系列(2)
Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...
随机推荐
- ansible-任务控制tags
1. ansible-任务控制tags介绍 如果你有一个大型的剧本,那么只能运行它的特定部分而不是在剧本中运行所有内容可能会很有用.因此,Ansible支持"tags:&quo ...
- Springboot+Redis(发布订阅模式)跨多服务器实战
一:redis中发布订阅功能(http://www.redis.cn/commands.html#pubsub) PSUBSCRIBE pattern [pattern -]:订阅一个或者多个符合pa ...
- 如何把base64格式的图片上传到到阿里云oss c#版
今天碰到需要把canvas上的的图片转存到阿里云oss,于是百度了半天,一个能打的答案都没有.怒了,自己搞起. 代码超级简单,需要先引入nuget 中啊里云的oss api 1 byte[] arr ...
- 阿里百秀后台管理项目笔记 ---- Day01
摘要 在此记录一下阿里百秀项目的教学视频的学习笔记,部分页面被我修改了,某些页面效果会不一样,基本操作是一致的,好记性不如烂笔头,加油叭!!! step 1 : 整合全部静态页面 将静态页面全部拷贝到 ...
- linux(centos8):安装prometheus服务端/node_exporter客户端(prometheus 2.18.1)
一,prometheus的用途 Prometheus是一个开源的系统监控和警报工具包 相比其他监控系统,它更适用于微服务的体系架构 它使用各种专用exporter,用来实现对硬件/存储/数据库/web ...
- selenium 浏览器最大化
from time import sleep from selenium import webdriver from selenium.webdriver.chrome.options import ...
- Python-selenium显示等待
#coding=utf-8 from selenium import webdriver from selenium.webdriver.common.by import By from seleni ...
- javascript arcgis 取区域中心点
javascript arcgis 取区域中心点 //graphic是绘制完多边形之后返回的对象 //获得多边形的中心点坐标 var centerPoint=graphic.geometry.getE ...
- QT/C++插件式框架、利用智能指针管理内存空间的实现、动态加载动态库文件
QT.C++插件式框架.主要原理还是 动态库的动态加载. dlopen()函数.下面为动态加载拿到Plugininstance对应指针.void**pp=(void**)dlsym(handle,&q ...
- 想买保时捷的运维李先生学Java性能之 JIT即时编译器
前言 本文记录日常学习<深入理解Java虚拟机>,不知道为啥感觉看一遍也就过了,喜欢动动手理解理解,这样才有点感觉,静不下心来的时候,看书抄书也可以用这个办法. 一.什么是JIT(Just ...