目录

解决Spring循环依赖和三级缓存问题

目录

Spring 循环依赖

什么是循环依赖问题?

  • 说白了就是死循环问题,A 对象创建依赖 B,B 对象创建依赖 A /cycledependency.png
1
new A(new B(new A(new B(……)))) //有参
  • 创建对象时做了两件事:
    • 在堆中开辟一块空间
    • 给对象进行赋值
  • 解决循环依赖需要人为的把实例化和初始化的过程给分开
1
new A(new B()) //无参
  • Spring

    • 给属性进行赋值操作的时候产生问题 S

      • 构造器:无法解决循环依赖
      • setter 方法:可以解决循环依赖问题
        • 三级缓存 -> 提前暴露对象
    • bean 的生命周期

      • 实例化:在堆中开辟空间,对象中的属性值都是默认值

      • 初始化

        1. 填充属性 -> populateBean:给默认属性(字段)赋值
        2. 调用 Aware 接口中的方法
        3. 执行 before 方法 -> BeanPostProcessor
        4. 执行 init 方法
        5. 执行 after 方法 -> BeanPostProcessor
        6. 获取完整对象
        7. 销毁流程

        对象的创建包含实例化和初始化两部分

    • AB 循环依赖:

      创建 A 对象 -> 实例化 A 对象 -> 填充 A 对象的 b 属性 -> 从容器中查找 B 对象 -> 如果找到 B 直接赋值,如果没有找到就创建 B 对象 -> 实例化 B 对象 -> 填充 B 对象的 a 属性 -> 从容器中查找 A 对象 -> 找到 A 直接赋值,没找到 A 又要重新创造 A 对象

    • 在循环过程中,对象存在两种状态:

      • 完成实例化但是未完成初始化(半成品)
        • 只要持有了该对象的引用,能否在后续过程中进行赋值操作
      • 完成实例化和初始化(成品)
    • 把 AB 循环依赖中的从容器中查找改成从缓存中查找

      • map 结构 key-value 模型
      • 实例化对象后把对象放入 map 中,从而解决循环创建对象问题
      • 结论:在容器中时需要缓存对象的,使用 map 的存储结构对象存在两种状态,要把半成品对象和成品对象分开存储,避免直接获取到半成品对象,此时可以给出一个描述,一级缓存(singletonObjects)和二级缓存(earlySingletonObjects),可以做如下人为规定:一级缓存存放成品,二级缓存存放半成品
    • DEBUG 整个对象的创建过程,一二三级缓存如何存储

    • 创建对象的流程

    /bean.png

为什么要移除二三级缓存?

在三级缓存中,我们在查找对象的时候,先找一级,再找二级,再找三级,如果一级之中已经有对象,不会去二级缓存中查找,以此类推,二三级都要销毁掉。

如果只有一级缓存,能否解决循环依赖问题?

不能

在一级缓存中存放的是成品对象,二级缓存中存放的是半成品对象,如果只有一级的话,那么成品和半成品回放在一起,那么就有可能获取到半成品对象,此时会有空指针问题,所以必须分开存储。

如果只有二级缓存,能否解决循环依赖问题?为什么非要有三级缓存 (singletonFactories)

改源码验证:哪些地方用到了三级缓存?

  • 创建完对象之后

    addSingletonFactory.getSingleton()

直接使用二级缓存也可以解决循环依赖问题,那么为什么非要用三级缓存?

为什么要使用三级缓存?

三级缓存存在的本质目的是为了解决 aop 过程中存在的动态代理的时候如何处理。

如果一个对象需要被代理,那么是否需要创建当前对象的普通对象(直接通过反射创建出来的对象)?

当添加了三级缓存之后,多了一个什么样的处理过程?

在 getEarlyBeanReferenc() 方法中可能会把原来的普通对象给替换成代理对象。

为什么加了三级缓存之后就解决了这个问题?

不管是普通对象,还是代理对象,对应的 bean 名称是否一致?

一样

在整个容器中可能存在同名不同对象吗?

不会,因为是 Singleton

在程序运行过程中,能确认代理对象什么时候被使用吗?

不能

怎么保证普通对象和代理对象只对外暴露一个?

通过回调机制来保证,lambda 传递到三级缓存之后并不是直接存储对象,而是在需要对象的时候,通过回调方法返回一个唯一的对象。

同过 lambda 表达式的回调确认了一件事,不能将普通对象和代理对象直接暴露出去,而只能选择一个对外暴露,也就是说只有一个最终版本,不会存在多个版本对外暴露