Spring 三级缓存解决循环依赖原理分析

一、Spring容器的三级缓存说明

三级缓存产生的原因:

  1. 解决循环依赖可能导致的死循环问题
  2. 循环依赖下的AOP代理问题

Spring解决循环依赖的核心就是提前暴露对象,而提前暴露对象就是放置于第二级缓存中。缓存的底层都是Map,至于它们属于第几层是由Spring回去数据顺序及其作用来表现的。

名称作用
singletonObjects一级缓存:存放完整的Bean
earlySingletonObjects二级缓存:存放提前暴露的Bean,Bean不是完整的,为完成属性注入和执行初始化方法
singletonFactories三级缓存:存放的是Bean工厂,主要生产Bean,存放到二级缓存中

DefaultSingletonBeanRegistry类中:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

二、Spring创建\管理Bean的基本流程重点

获取bean时通过bean工厂先从单例池中获取,如果没有则创建并添加至单例池,最终返回需要的bean对象

getBean --》 实例化 --》 填充属性 --》 初始化  --》放入单例池 

首先需要了解是Spring它创建Bean的流程,我把它的大致调用栈绘图如下:

在这里插入图片描述

 对Bean的创建最为核心三个方法解释如下:

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  • populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
  • initializeBean:初始化,回到一些形如initMethod、InitializingBean等方法

1、getBean:先去单例池中寻找 A对象 是否存在,存在则直接返回,不存在则进入后续创建流程

2、例化:通过反射实例化该对象 A对象

3、填充属性 A对象 填充属性(其中一个属性为 B对象 :需要 从单例池中获取 B对象 进行填充,这里等待第4步完成再能填充

4、由于填充的其中一个属性是需要引用另一个对象,则开始对 B对象 进行相同的创建流程

        getBean --》 实例化 --》 填充属性 --》 初始化  --》放入单例池

5、初始化:将完成的对象放入单例池中,再给返回

6、放入单例池将完成的 A对象 放入单例池 

实例化:是从无到有在创造一个对象(把一个不存在的东西实例出来带到现实),只是单纯的把对象 new 一下就行了;例:new Student();在构造一个实例的时候需要在内存中开辟空间

初始化:是在对象实例化的基础上,并且对 对象中的值进行赋一下初始值(这样的好处是避免当你没有赋值的时候,可以用这个初始的值来代替,友好的帮助你的功能)

三、循环引用的场景及解决方案

1、循环引用的死循环场景

从对单例Bean的初始化流程可以看出,循环依赖主要发生在第三步(populateBean),也就是field属性注入的处理。 如果继续遵循一个单例池的原则,会出现下列死循环引用问题。

两对象相互引用一直在填充属性中进行循环(死循环)

因为引用间没有任何一个可以进入下一阶段 “初始化” 并 “放入单例池”

所以单例池中需要的对象永远找不到(getBean)

2、二级缓存解决死循环方案(无法解决AOP代理问题)

增加一个【半成品池】结解决循环依赖创建问题:

创建时:对象一旦实例化完成【createBeanInstance("a")】,会先将对象放入半成品池中

对象引用时:会先找单例池,如果不存在则会去半成品池中寻找

 【重点】代码调用栈大致流程:

1、A对象 执行【第2行实例化完成【createBeanInstance("a")】后,将实例化的 A对象 放入半成品池中

2、【第3行】当为 A对象 填充属性(其中一个属性为 B对象 :需要 单例池 或 半成品池 获取 B对象 进行填充

3、此时 单例池 和 半成品池 中都没有 B对象 因而执行B的创建流程:

        (1)执行到【第7行】为 B对象 填充属性时需要 A对象 

        (2)首先判断 单例池中是否存在【没有】,再判断 半成品池中是否存在【找到了】

        (3)半成品池中的 A对象 填充进 B的属性中

        (4)B对象 属性填充完成 --》初始化完成 --》放入单例池

4、A对象 填充 B属性存在【getBean单例池中找到了】

5、A象 属性填充完成 --》初始化完成 --》放入单例池

6、半成品池中的A对象 清除

3、三级缓存解决死循环+AOP代理问题

我们通常情况下调用的是代理对象,而非对象本身(回忆一下事务处理的场景)

增加一个【工厂池】结解决循环依赖创建+AOP代理问题:

所谓的三级缓存就是在 单例池、半成品池 的基础上,增加了一个工厂池,里面存储的都是每个对象绑定的ObjectFactory(),此时的 单例池、半成品池 中 存储的 已经 不是对象本身,而是代理对象

ObjectFactory()作用特点:

  1. 为了调用提前处理getEarlyBeanReference】来执行创建动态代理createProxy
  2. 对象 实例化完成createBeanInstance】,就会在工厂池中创建一个factory()
  3. factory()不一定会被执行,只有该对象在创建过程中,又被其他对象所引用才会被调用,从而执行createProxy

AOP处理器实现了Bean处理器接口

AOP处理器创建代理对象会通过两个入口来调用创建动态代理createProxy):

  1. postProcessAfterInitialization:【后置处理】在对象 初始化完成 后调用后置处理来创建代理对象
  2. getEarlyBeanReference:【提前处理】在对象 实例化完成 后在工厂池中创建一个工厂,这个工厂会通过调用提前引用来创建代理对象

【重点】代码调用栈大致流程:

1、A对象 执行【第2行实例化完成createBeanInstance("a")后,会在 工厂池 中放入一个【factory(a)

2、【第3行】当为 A对象 填充属性(其中一个属性为 B对象 :需要 单例池 或 半成品池 获取 B对象 进行填充)

3、此时 单例池半成品池 中都没有 B代理对象 因而执行B的创建流程(同样会在工厂池中放入factory(b)):

        (1)执行到【第7行】为 B对象 填充属性时需要 A代理对象 

        (2)首先判断 单例池中是否存在【没有】,再判断 半成品池中是否存在【没有】

        (3)从而进入 工厂池 中执行调用 【factory(a)--》执行调用 提前引用getEarlyBeanReference】--》执行调用 创建动态代理createProxy

        (4)将创建的 A代理对象 放入 半成品池 ,可以被获取到并进行填充

        (5)B对象 属性填充完成 --》初始化完成 --》行调用 后置处理postProcessAfterInitialization】--》执行调用 创建动态代理createProxy

        (6)将创建的 B代理对象 放入 单例池 ,可以被获取到并进行填充

4、A对象 填充 B属性存在【getBean单例池中找到了B代理对象

5、A属性填充完成 --》初始化完成 --》行调用 后置处理postProcessAfterInitialization

6、此时 半成品池 中 已经存在 了 A代理对象 所以不需要再执行 createProxy,会将 A代理对象 从 半成品池中 移入 单例池

7、一旦对象进入单例池,则意味着创建流程已经全部完成,此时会将工厂池的factory()进行清理

问:为什么不在创建实例化后直接调用提前引用,而是要通过工厂方法调用?

答:所谓的提前引用指的是在创建的过程当中被引用。A在创建过程中,如果有人要去引用这个A,那么才会执行这个提前引用流程,否则这个factory(a)是不会被执行的,重点取决于是否被引用决定。

  • 12
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
面试高级开发的期间整理的面试题目,记录我面试遇到过的spring题目以及答案 目录 spring ThreadLocal的底层对象; 为什么@Service和@Repository放到实现类上面而不是接口类上面; spring 三种注入(就是从spring容器中将bean放入对象属性值中) Spring下描述依赖关系@Resource, @Autowired和@Inject的区别与联系 Spring中BeanFactory和ApplicationContext的区别 谈谈Spring IOC的理解,原理与实现? bean的生命周期,详细看上面 SpringBoot自动装配的过程的原理spring的缓存; spring是如何解决循环依赖; BeanFactory和FactoryBean有什么区别; Spring中用到的设计模式; SPI 机制(Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制), 很多地方有用到: AOP Spring的AOP的底层实现原理; 为什么jdk动态代理是必须是接口 两种动态代理的区别 AOP实现方式:aop注解或者xml配置;后来工具jar包aspects; aop的属性 事务 事务编码方式: 事务注意事项; 为什么同一个类A调用b方法事务,A方法一定要有事务(编码式的不用) @transaction多个数据源事务怎么指定数据源 传播特性有几种?7种; 某一个事务嵌套另一个事务的时候怎么办? REQUIRED_NEW和REQUIRED区别 Spring的事务是如何回滚的,实现原理; 抽象类和接口的区别,什么时候用抽象类什么时候用接口; StringBuilder和StringBuffer的区别 java值传递和引用传递

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值