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

第一篇: 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. 项目使用eslint

    今天eslint版本更新了,然后昂,有些奇奇怪怪的错误提示了,然后想,这我得 1.配置一个保存时根据eslint规则自动修复 2.欸,之前编码遇到未使用的变量都会有标记黄线,我很好定位,这会怎么没了 ...

  2. devops-持续集成管理之SonarQube

    1. devops-持续集成管理之SonarQube  1) 代码质量七宗罪 编码规范:是否遵守了编码规范,遵循了最佳实践. 潜在的BUG:可能在最坏情况下出现问题的代码,以及存在安全漏洞的代码. 文 ...

  3. 2014年 实验二 B2C网上购物

    实验二 B2C网上购物 [实验目的] ⑴.熟悉虚拟银行和网上支付的应用 ⑵.熟悉并掌握消费者B2C网上购物和商家的销售处理 [实验条件] ⑴.个人计算机一台 ⑵.计算机通过局域网形式接入互联网 (3) ...

  4. 多测师讲解python_模块(导入模块和内置模块)_高级讲师肖sir

    #自定义模块# from aaa import * #指定导入某个包中具体的类.函数.方法## A.fun1(2,2) #import +模块名 :# # import +模块名+.+.+# # 导入 ...

  5. Spring系列 SpringMVC的请求与数据响应

    Spring系列 SpringMVC的请求与数据响应 SpringMVC的数据响应 数据响应的方式 y以下案例均部署在Tomcat上,使用浏览器来访问一个简单的success.jsp页面来实现 Suc ...

  6. .NetCore 异步编程 - async/await

    前言: 这段时间开始用.netcore做公司项目,发现前辈搭的框架通篇运用了异步编程方式,也就是async/await方式,作为一个刚接触的小白,自然不太明白其中原理,最重要的是,这个玩意如果不明白基 ...

  7. Linux操作系统的基本介绍

    01 操作系统的概述介绍 操作系统(Operating System,简称OS)是管理计算机硬件与软件资源的计算机程序.操作系统需要处理如管理与配置内存.决定系统资源供需的优先次序.控制输入设备与输出 ...

  8. jenkins:用jenkins通过ssh部署jar包到远程linux机器(jdk 15 / jenkins 2.257)

    一,远程linux机器上安装java15(如已安装,跳过此步) 说明:演示用的linux机器ip: 192.168.1.47 1,下载: [root@blog ~]# cd /usr/local/so ...

  9. 皕杰报表:连接数据库失败,请检查数据源配置(oracle.jdbc.driver.OracleDriver)

    皕杰报表:连接数据库失败,请检查数据源配置(oracle.jdbc.driver.OracleDriver)问题解决: 缺少了classes12.jar 在窗口--首选项--报表运行时配置--添加-- ...

  10. 【git冲突解决】: Please commit your changes or stash them before you merge.

    刚刚使用 git pull 命令拉取代码时候,遇到了这样的问题: error: Your local changes to the following files would be overwritt ...