Spring循环依赖三级缓存--白话版

搜索引擎上面各路大神讲解的Spring三级缓存都是自源码级别,逐行逐句的解释,非常的详细周到,遥拜各路大神。

But !------------------大神们的解释有点儿不那么容易理解,并且解释的有点儿机械。

首先一个Java对象的创建过程往往包括 类初始化 和 类实例化 两个阶段:
使用new关键字创建对象,这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。比如:

AClass aClass = new AClass();
  1. 当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值数字为 0,字符为 null,布尔为 false,而所有引用被设置成 null)。
  2. 在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照不同的构造方法进行初始化。

给实例对象属性赋值不是必须通过构造方法,还可以通过set方法和直接赋值属性的方式。对应的Spring Bean的注入方式有三种:

  1. 构造器注入;
  2. 设值注入(setter方式注入);
  3. Feild方式注入(注解方式注入);

其中的构造器注入的方式对于循环依赖的情况目前无解(构造方法里面的参数需要是已经被正确地初始化的),虽然构造器注入是官方推荐的,有诸多好处,但是我不用。。。。。。。。

 Spring核心里面的AOP和IOC是两个不同的部分,不能混为一谈:

               AOP:增强功能的@Async/@Transactional等

               IOC:控制对象注入的 @Autowired/@Service等

Spring中有三级缓存,分别如下

  1. singletonObjects:完成初始化的单例对象的cache(一级缓存)
  2. earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)
  3. singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)

 

  • 首先假设有两个类 AClass和BClass,其中A依赖B,B依赖A:

理论上Spring创建A的时候依赖了B,然后Spring就会去加载B,但是这个时候B又依赖了A,Spring又去加载A,就会陷入一个死循环,如果不做干预的话,会一直循环下去,直至内存耗尽而OOM。而针对这种情况Spring提前将异常抛出了。

  • 总结各路大神源码层面的解析,整理了自己的理解:

一般的循环依赖:

如果存在循环依赖Spring内部如何解决? 

  • A创建过程中需要B,于是A将自己放在三级缓存中,去实例化B

B在实例化的时候发现需要A,于是B先去查询一级缓存,方法没有A,再取查询二级缓存发现还是没有A,再查询三级缓存找到了A,然后将三级缓存A注入到自己的属性中,然后从三级缓存中将A删除,加入到二级缓存,并将B自己放入一级缓存中,此时B初始化完毕,然后回来接着创建A,此时B已经在一级缓存了,直接从一级缓存中拿到B,然后完成A的初始化,最后再将A自己放入一级缓存
接着在解释Spring通过三级缓存解决循环依赖的过程:

  • A创建过程中需要B,于是A将自己放在三级缓存中,去实例化B。

      PS:A已经被分配了存储空间,只是此时属性都是默认值,还处于第一阶段(有了固定的内存地址,就是一个完整的实例对象了,只是属性都是空值而已,可以粗略的理解为已经执行过无参构造方法,是一个完整的实例了,不耽误用),在进行第二阶段通过有参构造方法给属性赋值的时候才发现的需要B,于是将A已经确定下来的内存地址存到了三级缓存的Map中,紧接着开始执行B的实例化流程了。

  • B在实例化的时候发现需要A,于是B先去查询一级缓存,方法没有A,再取查询二级缓存发现还是没有A,再查询三级缓存找到了A,然后将三级缓存A注入到自己的属性中,然后从三级缓存中将A删除,加入到二级缓存,并将B自己放入一级缓存中,此时B初始化完毕。

      PS:B发现需要A的时候也是完成第一阶段,已经被分配了存储空间,在进行第二阶段通过有参构造方法给属性赋值的才发现需要A,这时候B先去查询一级缓存,方法没有A,再取查询二级缓存发现还是没有A,再查询三级缓存找到了A(这时候的A已经有了固定内存地址,只是属性还没有赋值,但是能用),然后将三级缓存A注入到自己的属性中(在后面的流程中A完成了属性赋值时,因为A对象是单例对象,A属性值的变化,在B实例中引用的A属性也能体现出来变化),然后从三级缓存中将A删除,加入到二级缓存,并将B自己放入一级缓存中,此时B初始化完毕。

  • 然后回来接着创建A,此时B已经在一级缓存了,直接从一级缓存中拿到B,然后完成A的初始化,最后再将A自己放入一级缓存,同时从二级缓存中将A删除。

有AOP增强的循环依赖:

包含了@Async 方法的类与@Transactional的类相似,会被替换成一个新的代理类,三级缓存里提前暴露出来的是原始对象,完成创建后在一级缓存里的是代理对象,这时候的原始对象和代理对象指向的不是同一个内存地址,很明显这样的结果是不合理的,所以Spring将创建代理的逻辑延迟到了initializeBean步骤当中。

简单来说,spring认为如果一个bean在initializeBean前后不一致,并且一个已经完全初始化的beanA注入了这个未完全初始化的beanB,在spring的流程中beanA就再也没有机会改变注入的依赖了,所以会抛异常。

  • 为什么@Lazy可以解决这个问题?

被@Lazy修饰的属性并不是用到的时候在加载,而是正常加载,只是在用到的时候在给属性赋值。


文章参考:

https://blog.csdn.net/woshiwjma956/article/details/113245190

https://blog.51cto.com/15081050/2593337

https://blog.csdn.net/qq32933432/article/details/107561984

https://blog.csdn.net/justloveyou_/article/details/72466416

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值