前面已经写了关于三篇循环依赖的文章, 这是一个总结篇

第一篇: 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源码系列--循环依赖的设计思想的更多相关文章

  1. 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖

    本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...

  2. 3.2spring源码系列----循环依赖源码分析

    首先,我们在3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 中手写了循环依赖的实现. 这个实现就是模拟的spring的循环依赖. 目的是为了更容易理解spring源码 ...

  3. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  4. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  5. Spring源码之循环依赖

    https://www.cnblogs.com/longy2012/articles/12834762.html https://www.bilibili.com/video/BV1iD4y1o7pM ...

  6. 2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

    本文涉及主题 1. BeanFactoryPostProcessor调用过程源码剖析 2. 配置类的解析过程源码 3. 配置类@Configuration加与不加的区别 4. 重复beanName的覆 ...

  7. 3.3 Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案

    根据之前解析的循环依赖的源码, 分析了一级缓存,二级缓存,三级缓存的作用以及如何解决循环依赖的. 然而在多线程的情况下, Spring在创建bean的过程中, 可能会读取到不完整的bean. 下面, ...

  8. 【spring源码系列】之【xml解析】

    1. 读源码的方法 java程序员都知道读源码的重要性,尤其是spring的源码,代码设计不仅优雅,而且功能越来越强大,几乎可以与很多开源框架整合,让应用更易于专注业务领域开发.但是能把spring的 ...

  9. Ioc容器依赖注入-Spring 源码系列(2)

    Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...

随机推荐

  1. 连Python产生器(Generator)的原理都解释不了,还敢说Python用了5年?

      最近有很多学Python同学问我,Python Generator到底是什么东西,如何理解和使用.Ok,现在就用这篇文章对Python Generator做一个敲骨沥髓的深入解析.   为了更好地 ...

  2. git pull设置为无需密码

    https方式每次都要输入密码,按照如下设置即可输入一次就不用再手输入密码的困扰而且又享受https带来的极速 设置记住密码(默认15分钟): git config --global credenti ...

  3. Jmeter之参数化函数助手__CSVRead

    1.在Tool->函数对话框中选择__CSVRead,2处填写测试用例的文档地址(测试用例要以csv格式保存),3处是测试用例中参数的位置,第一栏参数的CSV文件列号填0,第二栏参数的CSV文件 ...

  4. go读取excel表格数据

    go读取excel表格数据 使用工具 github.com/Luxurioust/excelize 百度到的都是使用这个 实际上已经改名了 github.com/360EntSecGroup-Skyl ...

  5. Compareto方法

    很多时候我们写Compareto方法是用于排序,那么排序就涉及到数据位置交换. 所以要注意compareto返回值的含义,通过一个例子来看一下: 假设对象的num属性作为比较标准,对象为testVO ...

  6. 联赛模拟测试20 C. Weed

    题目描述 \(duyege\) 的电脑上面已经长草了,经过辨认上面有金坷垃的痕迹. 为了查出真相,\(duyege\) 准备修好电脑之后再进行一次金坷垃的模拟实验. 电脑上面有若干层金坷垃,每次只能在 ...

  7. Vue中封装axios组件实例

    首先要创建一个网络模块network文件夹  里面要写封装好的几个组件 在config.js里面这样写 在index.js要这样写 core.js文件里面内容如下 然后要在main.js文件里面要设置 ...

  8. ServletContext使用介绍

    ServletContext是一个容器(域对象)可以存储键值对数据(String key,Object value),保存在ServletContext中的数据不仅可以提供给所有的servlet使用, ...

  9. 一篇文章 图解Python 玩转Python

    0 Python 解释器:1.Python数据结构:2.变量与运算符3 Python 流程控制 4 Python 文件处理5 python 输入输出6 Python 异常7 Python 函数和模块8 ...

  10. servlet 验证生命周期过程调用方法的次数

    1.书写一个servlet并编译,如: package testservlet; import java.io.IOException;import java.io.PrintWriter; impo ...