开发者

Spring如何使用三级缓存解决循环依赖

开发者 https://www.devze.com 2024-11-08 10:34 出处:网络 作者: 夏诗曼CharmaineXia
目录前言1. 什么是循环依赖2. Sping中循环依赖有什么问题?3. 什么是三级缓存4. Spring 可以解决哪些情况的循环依赖?二级缓存作用——普通循环依赖实操环节1. 实例化类A对象2. 实例化类B对象3. B对象完成
目录
  • 前言
    • 1. 什么是循环依赖
    • 2. Sping中循环依赖有什么问题?
    • 3. 什么是三级缓存
    • 4. Spring 可以解决哪些情况的循环依赖?
  • 二级缓存作用——普通循环依赖
    • 实操环节
      • 1. 实例化类A对象
      • 2. 实例化类B对象
      • 3. B对象完成创建
      • 4.继续创建A对象
  • 三级缓存作用——aop循环依赖
    • 1. AOP代理问题
      • 2. 何时生成代理对象
        • 实操环节
          • 1.实例化类A对象
          • 2. 实例化类B对象
          • 3.继续创建A对象
      • 总结

        前言

        1. 什么是循环依赖

        类A需要类B,我们就叫做类A依赖类B。简单说就是依赖,或者和别的类相互依赖

        1.1 互相依赖

        Spring如何使用三级缓存解决循环依赖

        1.2 递归依赖

        Spring如何使用三级缓存解决循环依赖

        2. Sping中循环依赖有什么问题?

        在Spring中,循环依赖指的是两个或多个Bean之间相互依赖形成的循环引用关系。具体来说,当Bean A依赖于Bean B,而Bean B又依赖于Bean A时,就形成了循环依赖。

        只有单例的 Bean 才存在循环依赖的情况,原型(Prototype)情况下,Spring 会直接抛出异常。

        循环依赖可能导致以下问题:

        • 无法完成Bean的初始化:当存在循环依赖时,Spring容器无法确定先初始化哪个Bean,因为它们相互依赖,而且都需要对方完成初始化才能继续。这可能导致Bean的初始化过程无法完成,从而引发异常。
        • 无限递归调用:当存在循环依赖时,Spring容器可能会陷入无限递归的调用中,导致系统堆栈溢出。这是因为每次获取Bean时,Spring容器需要检测循环依赖并创建实例,但由于循环依赖的存在,无法正常创建实例,从而导致无限递归调用。

        Spring如何使用三级缓存解决循环依赖

        为了解决循环依赖问题,Spring使用了三级缓存和"提前暴露"的策略

        3. 什么是三级缓存

        对于创建单例Bean,Spring创建了三个容器来存储不同时期的对象:

        1. ⼀级缓存 : Map<String,Object> singletonObjects,单例池,⽤于保存实例化、属性赋值(注⼊)、初始化完成的 bean 实例
        2. ⼆级缓存 : Map<String,Object> earlySingletonObjects,早期曝光对象,⽤于保存实例化完成的 bean 实例
        3. 三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象⼯⼚,⽤于保存 bean 创建⼯⼚,以便于后⾯扩展有机会创建代理对象

        Spring如何使用三级缓存解决循环依赖

        4. Spring 可以解决哪些情况的循环依赖?

        Spring 不⽀持基于构造php器注⼊的循环依赖,假如 AB 循环依赖,其中一方使用构造器注入,也是不支持的。

        Spring如何使用三级缓存解决循环依赖

        为什么呢?下面二级缓存会说明白。

        二级缓存作用——普通循环依赖

        只用一级缓存和二级缓存就能解决普通bean的循环依赖。

        先回顾Bean对象创建的步骤:

        Spring如何使用三级缓存解决循环依赖

        二级缓存:又称 半成品池 存放的是实例化,js但未属性赋值和初始化的Bean对象。一级缓存:又称 单例池 存放的是完成属性赋值和初始化的成品Bean,可以直接使用了。

        那么二级缓存是如何解决普通Bean的循环依赖的?

        实操环节

        类A依赖类B,类B依赖类A。

        Spring如何使用三级缓存解决循环依赖

        1. 实例化类A对象

        对象a被实例化出来,php会被放到半成品池中,当进行下一步属性赋值时,发现依赖了类B,所以开始创建对象b。

        Spring如何使用三级缓存解决循环依赖

        如果对象b是在a的构造函数中注入的,那就完了,a无法实例化,得先去实例化b,若是b也是构造函数中注入的ahttp://www.devze.com,那就无解了。

        public class A {
            private B b;
        
            @Inject
            public A(B b) {
                this.b = b;
            }
        }

        2. 实例化类B对象

        对象b被实例化出来,也被放到半成品池http://www.devze.com。下一步是属性赋值,发现依赖了类A,会依次从⼀级到三级缓存查询类A对象,最终会在半成品池中找到对象a,成功将它赋值到自己的属性中。

        Spring如何使用三级缓存解决循环依赖

        3. B对象完成创建

        对象b在经过填充属性、初始化后会从半成品池里挪到单例池中,可以直接使用了。

        Spring如何使用三级缓存解决循环依赖

        4.继续创建A对象

        这时候回过头来继续对象a的属性注入,把对象b赋值给自己的属性后再经过初始化,对象a也从半成品池挪到单例池,对象a创建完成,对象b也跟着创建完成。

        Spring如何使用三级缓存解决循环依赖

        三级缓存作用——aop循环依赖

        二级缓存仍然存在问题,它无法解决AOP代理问题。

        1. AOP代理问题

        AOP(面向切面)简单的说,在不改源码的情况下在原始方法前后加一些代码。

        它的底层是靠动态代理实现的,即生成一个代理类,重新原始方法,真正使用的时候其实用的是代理类对象,而非原始类对象。

        既然用的是代理类对象,单例池中应该存放的就该是代理类对象。

        二级缓存无法解决生成代理对象的问题,因为创建对象的过程很复杂,每个代理类都需要一个工厂来专门生成代理类对象。

        Spring如何使用三级缓存解决循环依赖

        三级缓存又叫工厂池,就是用来存放生成代理类对象工厂的。

        Spring如何使用三级缓存解决循环依赖

        2. 何时生成代理对象

        AOP是靠AOP处理器实现的,处理器有两个生成代理对象的方式。

        前置处理:在Bean对象初始化后后置处理:再Bean对象实例化前

        Spring如何使用三级缓存解决循环依赖

        Spring为了解决使用AOP的对象循环依赖的问题,使用了这两种处理方式。

        实操环节

        类A依赖类B,类B依赖类A,类A使用了AOP。

        Spring如何使用三级缓存解决循环依赖

        1.实例化类A对象

        首先把创建类A代理对象的工厂对象放到工厂池中。

        Spring如何使用三级缓存解决循环依赖

        类A实例化对象时发现依赖了类B,使用前置处理器生成A的代理对象,放在半成品池子中。

        Spring如何使用三级缓存解决循环依赖

        2. 实例化类B对象

        对象b找到对象a,成功属性赋值,再经过初始化成功创建,挪到单例池中待用。

        如果类B也使用了AOP,那么对象b在初始化后,会通过后置处理器生成动态代理对象,放到单例池中。

        Spring如何使用三级缓存解决循环依赖

        3.继续创建A对象

        对象b创建完,接着回头创建对象a,这个过程就很顺利了。

        当对象a完成初始化以后,因为已经是代理对象,就不会在走后置处理。

        对象a、b都创建完,会清空在二级、三级池中的相关数据,最终只在单例池中保留一份对象。

        Spring如何使用三级缓存解决循环依赖

        总结

        尽管Spring提供了解决循环依赖的机制,但循环依赖本身是一个设计上的问题,可能导致代码的可读性和可维护性下降。因此,在编写代码时,应尽量避免出现循环依赖的情况。

        以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

        0

        精彩评论

        暂无评论...
        验证码 换一张
        取 消