面试问题浓缩总结 面试问题浓缩总结
  • Go
  • Java
  • C/C++
  • JavaScript/HTML
  • MySQL
  • Redis
  • MongoDB
  • 操作系统
  • 计算机网络
  • spring全家桶
  • mybatis
  • 中间件
  • 软件相关
  • 系统相关
  • 算法
  • 数据结构
  • 设计模式
  • CMU硕士经典100题
  • 剑指offer
  • 重点手撕代码
  • 程序员面试金典
  • 3月
  • 4月
  • 智力题
  • 业务问题
  • 一些技术
  • 安全相关
APP下载 (opens new window)
GitHub (opens new window)
  • Go
  • Java
  • C/C++
  • JavaScript/HTML
  • MySQL
  • Redis
  • MongoDB
  • 操作系统
  • 计算机网络
  • spring全家桶
  • mybatis
  • 中间件
  • 软件相关
  • 系统相关
  • 算法
  • 数据结构
  • 设计模式
  • CMU硕士经典100题
  • 剑指offer
  • 重点手撕代码
  • 程序员面试金典
  • 3月
  • 4月
  • 智力题
  • 业务问题
  • 一些技术
  • 安全相关
APP下载 (opens new window)
GitHub (opens new window)
  • spring全家桶

    • IOC
    • AOP
    • spring MVC
    • springSecurity
    • 面试问题
    • 循环依赖
      • 创建对象
      • 死循环出现
      • 那么我们如何解决呢?
      • AOP代理
      • 三级缓存
      • Spring如何解决循环依赖的
        • 面试官:为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
    • 事务
    • 启动流程和配置
    • 注解相关
  • mybatis

  • 中间件

  • 框架
  • spring全家桶
小游
2021-03-26

循环依赖

先说一下什么是循环依赖,循环依赖就是A引用了对象B,但是B对象又引用了A,如下图所示

image-20210313153357517

springIOC就是我们不自己创建对象,而是使用springIOC来帮我们创建,所以也叫控制反转。但是IOC在创建bean对象时,就不可避免要遇到两个问题,一个是循环依赖,一个就是AOP的代理。

# IOC创建bean过程

首先我们通过工厂方法来创建一个bean对象(通过工厂方法工厂就会从单例池里面去获取对象,如果没有就自己新建一个)

image-20210313154913582

# 创建对象

那么如何新建一个对象呢,主要包括以下几个步骤

getBean->然后通过反射实例化(createBeanInstance(“a”))->填充属性(populate(“b”))->初始化(执行Bean里面的初始化方法)创建完以后就放到单例池里面,然后再返回出去(如下图)

image-20210313155047091

# 死循环出现

当我们创建的时候出现一个循环引用时,我们的代码执行栈里面就会出现递归操作,造成死循环

image-20210313155547466

# 那么我们如何解决呢?

由两级缓存就可以实现:一个是单例池,一个是半成品池

  1. 先去获取a,但a实例化的时候,就会把a放到半成品池中去
  2. 然后去填充属性b,然后获取b
  3. b在单例池中判断是否有a,如果没有就去半成品池中去获取
  4. 如果半成品中有把a放到b中就进行填充和初始化,然后b初始化成功,b 就可以放到单例池中
  5. 而后a就可以在单例池中获取到b进行属性填充,最后也初始化成功,将a放入到单例池中

image-20210313155846260

# AOP代理

虽然我们前面已经解决了死循环的问题,但是如果我们要使用AOP时,因为我们创建的对象是代理对象不是实际对象,我们就需要想个办法获取到实际的对象

# 三级缓存

三级缓存实现:一个是单例池,一个是半成品池,一个是对象工厂池

image-20210313160329540

代理对象的产生如上图所示:

首先初始化的时候会调用bean处理器,而这个处理器里面包含了一个AOP的处理器

AOP处理器会调用createproxy创建代理对象

调用createproxy有两种途径,一个是后置处理器(正常途径),一个是提前处理器(解决问题的关键)

最后的创建过程如下

  1. 当a实例化的时候就会在工厂池里面创建一个factory(a) a的对象工厂,这个对象工厂可以调用提前处理,创建一个a的代理对象,当然这个factory(a)并不会自动的调用,只有当b需要填充属性的时候才会调用
  2. 当b填充属性调用factory(a), factory(a)会提前引用创建出来了一个代理对象a,会放入半成品池中,然后把半成品池中的a,填充到b中
  3. b就初始化完成,通过后置处理创建出b的代理对象放入单例池中(在b实例化的时候也会创建factory(b),但无人引用就销毁了)
  4. a填充属性,由于单例池中有b了就直接填充进去了,然后执行初始化、后置处理创建动态代理,由于半成品池中有了a的代理对象,所以直接把半成品池中直接拿到单例池,同时再把factory(a)销毁,这样两个代理对象就都创建出来了

image-20210313160548729

3d9f567b-31d3-43ac-a8a4-6b2371e8337d.png

# 回答模板

# Spring如何解决循环依赖的

答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:

第一步:先获取到三级缓存中的工厂;

第二步:调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。

第三步:当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

# 面试官:为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

参考

烂大街的Spring循环依赖该如何回答? - sowhat1412的个人空间 - OSCHINA - 中文开源技术交流社区 (opens new window)

编辑 (opens new window)
上次更新: 2021/03/31, 20:52:00
面试问题
事务

← 面试问题 事务→

Theme by Vdoing | Copyright © 2021-2021 小游
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式