此处是我自己的一个理解,防止以后忘记,如若那个地方理解不对,欢迎指出。

一、背景

在我们写代码的过程中一般会使用 @Autowired 来注入另外的一个对象,但有些时候发生了 循环依赖,但是我们的代码没有报错,这个是什么原因呢?

二、前置知识

1、考虑循环依赖的类型

此处我们考虑 单例 + @Autowired 的循环依赖,不考虑使用构造器注入原型作用域的Bean的注入。

2、代理对象何时创建



注意:

正常情况下,即没有发生 循环依赖的时候,aop增强是在 bean 初始化完成之后的 BeanPostProcessor#postProcessAfterInitialization方法中,但是如果有循环依赖发生的话,就需要提前,在 getEarlyBeanReference中提前创建代理对象。

3、3级缓存中保存的是什么对象

缓存字段名 缓存级别 数据类型 解释
singletonObjects 1 Map<String, Object> 保存的是完整的Bean,即可以使用的Bean
earlySingletonObjects 2 Map<String, Object> 保存的是半成品的Bean,即属性还没有设置,没有完成初始化工作
singletonFactories 3 Map<String, ObjectFactory<?>> 主要是生成Bean,然后放到二级缓存中

注意:

ObjectFactory#getObject() 每调用一次,都会产生一个新的对象或返回旧对象,取决于是否存在代理等等。

4、从3级缓存中获取对象

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

5 Spring Bean的简化创建过程

1、实例化一个bean

Object bean = instanceWrapper.getWrappedInstance();

实例化Bean 即 new Bean()

2、加入到三级缓存中

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

加入到三级缓存中是有一些条件判断的,一般都会是成立的,此处认为需要加入到三级缓存。

3、设置bean的属性

populateBean(beanName, mbd, instanceWrapper);

第一步实例化了bean,但是此时是没有填充需要注入的属性的,通过这一步进行属性的填充。

4、初始化bean

Object exposedObject = initializeBean(beanName, exposedObject, mbd);

初始化Bean,执行初始化方法、Aware回调、执行 BeanPostProcessor#postProcessAfterInitialization 方法 (aop的增强是在这个里面实现的)

如果有循环引用的话,则aop的增强需要提前。

5、加入到一级缓存中

addSingleton(......)

三、理解

@Component
class A {
@Autowired
private B b;
} @Transaction (存在代理)
@Component
class B{
@Autowired
private A a;
}

1、假设只有singletonObjects和earlySingletonObjects可否完成循环依赖

缓存字段名 缓存级别 数据类型 解释
singletonObjects 1 Map<String, Object> 保存的是完整的Bean,即可以使用的Bean
earlySingletonObjects 2 Map<String, Object> 保存的是半成品的Bean,即属性还没有设置,没有完成初始化工作

此时需要获取 B的实例,即 getBean("b"),由上方了解到的 Bean 的简化流程可知





由上图可知,对象存在代理时,2级缓存无法解决问题。因为代理对象是通过BeanPostProcessor来完成,是在设置属性之后才产生的代理对象

此时可能有人会说,那如果我在构建完B的实例后,就立马进行Aop代理,这样不就解决问题了吗?那假设A和B之间没有发生循环依赖,这样设计会不会不优雅?

2、假设只有singletonObjects和singletonFactories可否完成循环依赖



由图中可知也是不可以实现的。

3、3级缓存如何实现

1、解决代理问题

因为默认情况下,代理是通过BeanPostProcessor来完成,为了解决代理,就需要提前创建代理,那么这个代理的创建就放到3级缓存中来进行创建。

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference 此方法会返回代理bean

2、解决单例通过第3级缓存多次获取的值不一致



从上图中可知,对象是先从 一级->二级->三级缓存 这样查找,当三级缓存产生了对象后就放入二级缓存中缓存起来,同时删除三级缓存。

3、流程图

四、总结

1、一级缓存 singletonObjects 存放可以使用的单例。

2、二级缓存earlySingletonObjects存放的是早期的Bean,即是半成品,此时还是不可用的。

3、三级缓存singletonFactories 是一个对象工厂,用于创建对象,然后放入到二级缓存中。同时对象如果有Aop代理的话,这个对对象工厂返回的就是代理对象。

那可以在earlySingletonObjects中直接存放创建后的代理对象吗?这样是可以解决问题,但是设计可能就不合理了。因为在Spring中 Aop的代理是在对象完成之后创建的。而且如果没有发生循环依赖的话,有必要提前创建代理对象吗?分成三级缓存,代码结构更清楚,更合理。

Spring的3级缓存和循环引用的理解的更多相关文章

  1. Spring如何使用三级缓存解决循环依赖

    Spring如何使用三级缓存解决循环依赖 首先来了解一下什么是循环依赖 @Component public class A { @Autowired B b; } @Component public ...

  2. Spring如何解决循环引用

    概念 什么是循环引用? 故名思义,多个对象形成环路. 有哪几种循环引用? 在Spring中存在如下几种循环引用,一一举例分析一下 注入循环引用(Set注入 注解注入) package c.q.m; i ...

  3. Spring 循环引用(二)源码分析

    Spring 循环引用(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环引用相关文章: & ...

  4. Spring IOC 源码简单分析 03 - 循环引用

    ### 准备 ## 目标 了解 Spring 如何处理循环引用 ##测试代码 gordon.study.spring.ioc.IOC03_CircularReference.java   ioc03. ...

  5. spring源码学习(三)--spring循环引用源码学习

    在spring中,是支持单实例bean的循环引用(循环依赖)的,循环依赖,简单而言,就是A类中注入了B类,B类中注入了A类,首先贴出我的代码示例 @Component public class Add ...

  6. Spring 循环引用(三)源码深入分析版

    @ 目录 前言 正文 分析 doGetBean 为什么Prototype不可以 createBean doCreateBean getEarlyBeanReference getSingleton b ...

  7. 【源码】spring循环引用

    spring在单例,非构造方法注入的情况下允许循环依赖 1.循环依赖 a引用b,b引用a.a创建的时候需要b,但是b没有创建,需要先去创建b,b创建的时候又没有a,这就出现的循环依赖的问题 2.为什么 ...

  8. Spring三级缓存解决循环依赖

    前提知识 1.解决循环依赖的核心依据:实例化和初始化步骤是分开执行的 2.实现方式:三级缓存 3.lambda表达式的延迟执行特性 spring源码执行逻辑 核心方法refresh(), popula ...

  9. spring jpa 实体互相引用返回restful数据循环引用报错的问题

    spring jpa 实体互相引用返回restful数据循环引用报错的问题 Java实体里两个对象有关联关系,互相引用,比如,在一对多的关联关系里 Problem对象,引用了标签列表ProblemLa ...

随机推荐

  1. Axure7.0 以及 中文汉化语言包下载 axure汉化包

    支持 Axure RP Pro 正式版 当前最新版本 7.0.0.3184 不兼容6.5及以下版本! Axure7.0 下载地址:http://pan.baidu.com/s/1dEuR8YX Axu ...

  2. 【自动化基础】手把手教零基础小白搭建APP的UI自动化环境

    前言 帮助零基础小白一步步搭建UI自动化环境,完成Python+Appium+模拟器/真机的UI自动化环境搭建. 环境准备: jdk1.8.0 sdk Node.js appium python Ap ...

  3. Http请求的Get和Post的区别?

    1. get从地址栏以明文的方式提交请求信息内容?username=admin&password=123,用户可见, 而post从请求正文提交请求信息内容,用户不可见. 2. get提交因为是 ...

  4. Could not find the main class

    最近开发了一个短信报警的服务,打成程序包之后,再本地windows启动(start.bat)没有问题,但是发到生产环境,报如下错: Could not find the main class 莫名其妙 ...

  5. C++11最常用的新特性如下

    1.auto关键字:编译器可以根据初始值自动推导出类型.但是不能用于函数传参.定义数组以及非静态成员变量. 2.nullptr关键字:是一种特殊类型的字面值,它可以被转换成任意其它类型的指针:而NUL ...

  6. 用maven建立一个工程4

    在命令行里面输入cd C:\Users\admin\Documents\hello 然后按回车 再输入这行代码 mvn archetype:generate -DgroupId=com.liyongz ...

  7. DRF 简单使用(详细注释版)

    1.djangorestframework使用 下载安装 pip install djangorestframework ## djangorestframework pip install djan ...

  8. ACM - 图论 - P3385 负环

    P3385 负环 题目描述 给定一个 \(n\) 个点的有向图,请求出图中是否存在从顶点 \(1\) 出发能到达的负环. 负环的定义是:一条边权之和为负数的回路. 输入格式 本题单测试点有多组测试数据 ...

  9. Docker镜像构建之docker commit

    我们可以通过公共仓库拉取镜像使用,但是,有些时候公共仓库拉取的镜像并不符合我们的需求.尽管已经从繁琐的部署工作中解放出来了,但是在实际开发时,我们可能希望镜像包含整个项目的完整环境,在其他机器上拉取打 ...

  10. css 垂直居中方法汇总

    查看原文可以有更好的排版效果哦 前言 居中是平时工作中的最常见的一种需求,各种图片居中或者各种弹窗,水平居中还好,特别是垂直居中,很多初学者表示太难写了,现在列举一些常用的方法. 实战 这里只讲述cs ...