java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring使用三级缓存解决循环依赖

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

作者:夏诗曼CharmaineXia

在Spring框架中,循环依赖是指两个或多个Bean相互依赖,形成闭环,导致无法完成初始化,此问题仅存在于单例Bean中,而原型Bean会抛出异常,Spring通过三级缓存及提前暴露策略解决循环依赖:一级缓存存放完全初始化的Bean

前言

1. 什么是循环依赖

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

1.1 互相依赖

1.2 递归依赖

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

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

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

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

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

3. 什么是三级缓存

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

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

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

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

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

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

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

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

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

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

实操环节

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

1. 实例化类A对象

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

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

public class A {
    private B b;

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

2. 实例化类B对象

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

3. B对象完成创建

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

4.继续创建A对象

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

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

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

1. AOP代理问题

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

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

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

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

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

2. 何时生成代理对象

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

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

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

实操环节

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

1.实例化类A对象

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

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

2. 实例化类B对象

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

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

3.继续创建A对象

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

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

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

总结

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文