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

一、背景

在我们写代码的过程中一般会使用 @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 {@Autowiredprivate B b;
}@Transaction (存在代理)
@Component
class B{@Autowiredprivate 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. [iOS]-NSTimer与循环引用的理解

    目录: 参考的博客: 问题引入 循环引用 简单的循环引用 Block中的循环引用强弱共舞 Delegate中的循环引用 NSTimer 创建NSTimer 销毁NSTimer invalidate f ...

  2. 京东一面:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?我懵了。。...

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:cnblogs.com/semi-sub/p/13548479.html 前言 bean生命周期 三级缓存解决循环依赖 总结 ...

  3. spring无法用三级缓存解决循环依赖的问题分析

    spring无法解决构造器的循环依赖,对上述例子稍微进行改动: @Component("b") public class B {private A a;public B(A a) ...

  4. spring三级缓存以及@Async产生循环引用

    spring三级缓存以及@Async产生循环引用 spring三级缓存介绍 三级缓存解除循环引用原理 源码对应 1.获取A,从三级缓存中获取,没有获取到 2.构造A,将A置入三级缓存 构造A(创建A实 ...

  5. spring生命周期七个过程_Spring杂文(三)Spring循环引用

    众所周知spring在默认单例的情况下是支持循环引用的 Appconfig.java类的代码 @Configurable @ComponentScan("com.sadow") p ...

  6. iOS: NSTimer的循环引用(解决)

    首先有两个概念写在最前: 内存泄漏:系统分配的内存空间在使用完毕之后没有进行及时的回收,称之为发生了内存泄漏. 内存溢出:指在申请内存的时候,没有足够的内存空间可以使用,包括栈溢出和堆溢出. 下面开始 ...

  7. spring 源码_spring源码系列(一)——spring循环引用

    众所周知spring在默认单例的情况下是支持循环引用的 为了节省图片大小我把那些可以动得gif图片做成了只循环一次,如果看到图片不动了请右键选择在新标签打开,那么图片就会动,手机用户则更简单,直接手指 ...

  8. 为什么Spring需要三级缓存解决循环依赖,而不是二级缓存?

    来源:https://www.cnblogs.com/semi-sub/p/13548479.html 在使用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring已经帮我们去解决 ...

  9. 解鞍卸甲——手写简易版Spring框架(终):使用三级缓存解决循环依赖问题

    什么是三级缓存 按照目前我们实现的 Spring 框架,是可以满足一个基本需求的,但如果你配置了A.B两个Bean对象互相依赖,那么立马会抛出 java.lang.StackOverflowError ...

最新文章

  1. 【SpringCloud】高可用Eureka
  2. suse mysql 5.5_suse 11 mysql 如何从5.1升级到5.5
  3. Python踩坑:类与类对象类型参数传递与使用
  4. 在shell脚本中调用sqlplus
  5. 【POJ - 3177】Redundant Paths(边双连通分量,去重边)
  6. Unicode : RLO
  7. LeetCode:999. 车的可用捕获量
  8. revit对齐命令选不中_如何利用无人机+Photoscan+Revit完成土方计算
  9. JVM笔记(一)数字在JVM中的表示
  10. php gdb strace抓包,Linux上进程追踪与调试(strace和gdb)
  11. bzoj1010: [HNOI2008]玩具装箱toy
  12. 战旗html5播放器为什么卡顿,视频站启用html5播放器
  13. rootkit模拟木马病毒
  14. 前端页面加蒙版的几种方法
  15. 怎么把wav文件改成mp3?
  16. SQL Server+SSMS下载安装
  17. PreScan 教程:0. PreScan与Matlab连接
  18. mysql介绍索引类型的章节_mysql索引总结--mysql索引类型以及创建的详细介绍
  19. mysql学习第二天
  20. wince挂起和唤醒_relayon

热门文章

  1. 剑侠录java_新蜀山剑侠录-刀剑痕
  2. python中不论类的名字是什么意思_Python中的名实关系——名字、命名空间、作用域...
  3. [Python Challenge通关]第5关 peak hell
  4. HPB芯链 -- 共识算法选举机制描述
  5. 爬虫 — 大众点评商户信息的爬取和文字反爬
  6. 微信小程序--功能区
  7. Leetcode力扣 MySQL数据库 574 当选者
  8. C语言笔记一:二进制补码
  9. 如何在浏览器中打开项目及点击注册按钮或者其他按钮无效问题解决
  10. 虚拟机文件上传至dsm服务器,ESXi虚拟机搭建私有云NAS-黑群晖DSM