当前位置: 首页 > article >正文

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

文章目录

  • 1. 三级缓存解决的问题场景
  • 2. 三级缓存的差异性
  • 3. 循环依赖时的处理流程
  • 4. 源码验证

1. 三级缓存解决的问题场景

循环依赖指的是在对象之间存在相互依赖关系,形成一个闭环,导致无法准确地完成对象的创建和初始化;当两个或多个对象彼此之间相互引用,而这种相互引用形成一个循环时,就可能出现循环依赖问题。

在早期的 Spring 版本中是可以自动解决的循环依赖的问题的,

public class A {
	@Autowired
    private B b;
}

public class B {
	@Autowired
    private A a;
}

img

但要注意,Spring 解决循环依赖是有前提条件的,

🍂第一,要求互相依赖的 Bean 必须要是单例的 Bean。

  • 这是因为对于原型范围的 Bean(prototype scope),每次请求都会创建一个新的 Bean 实例,这样每次尝试解析循环依赖时,都会产生新的 Bean 实例,导致无限循环,由于没有全局的、持续的单例实例的缓存来引用,因此循环依赖无法得到解决。

🍂第二,依赖注入的方式不能都是构造函数注入的方式。

  • 当使用构造函数注入时,一个 Bean 的实例在构造函数被完全调用之前是不会被创建的;如果 Bean A 的构造函数依赖于 Bean B,而 Bean B 的构造函数又依赖于 Bean A,那么就会产生一个死锁的情况,因为两者都不能在对方初始化之前完成初始化。
public class C {
    private D d;
    @Autowired
    public C(D d) {
        this.dService = dService;
    }
}

public class D {
    private C c;
    @Autowired
    public D(C c) {
        this.c = c;
    }
}

Spring 源码中关于三级缓存的定义如下:

// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

所以说,所谓的“三级缓存”就是是指三个 Map 数据结构,分别用于存储不同状态的 Bean。

2. 三级缓存的差异性

img

🎯一级缓存:

一级缓存保存的是已经完全初始化和实例化的 Bean 对象,在程序中使用的 Bean 通常就是从这个缓存中获取的;这个缓存的目的是确保 Bean 只初始化一次(是单例的),避免多次实例化相同的Bean对象,提高性能。

🎯二级缓存:

二级缓存用来解决 Bean 创建过程中的循环依赖问题,它存储的是尚未完成属性注入和初始化的“半成品”Bean 对象;当 Spring容器发现两个或多个 Bean 之间存在循环依赖时,也就是当一个 Bean 创建过程中需要引用另一个正在创建的 Bean,Spring 将创建需要的这些未完全初始化的对象提前暴露在二级缓存中,以便其他 Bean 进行引用,确保它们之间的依赖能够被满足。

🎯三级缓存:

三级缓存中存储的是 ObjectFactory<?> 类型的代理工厂对象,主要用于处理存在 AOP 时的循环依赖问题;每个 Bean 都对应一个 ObjectFactory 对象,通过调用该对象的 getObject 方法,可以获取到早期暴露出去的 Bean;在该 Bean 要被其他 Bean 引用时,Spring 就会用工厂对象创建出该 Bean 的实例对象,最终当该 Bean 完成构造的所有步骤后就会将该 Bean 放入到一级缓存中。

3. 循环依赖时的处理流程

当 Spring 发生循环依赖时(以最开始介绍的场景为例,A B 两个 Bean 相互依赖),以下是完善的执行流程:

  1. 遍历待创建的所有 beanName:在容器启动时,Spring 会遍历所有需要创建的 Bean 名称,在第一次遍历到 A 时,就开始获取 Bean A;如果 Bean A 的实例不在一级、二级缓存中(缓存中没有值),Spring 会开始正常的 Bean 创建流程。
  2. Bean A 的创建:Bean A 的创建过程开始,然后 Spring 会检查是否 A 是单例(Singleton),同时 A 是否已经创建完成;如果 A 是单例且尚未创建完成,将 A 的 BeanFactory 存入三级缓存。
  3. 处理依赖注入:A 开始处理 @Autowired 注解,尝试注入 B 属性;Spring 会在一级、二级缓存中来查找 Bean B,如果找不到,则开始正常创建 Bean B 的流程。
  4. Bean B 的创建:Bean B 的创建过程类似于 A,首先判断B是否是单例,且是否已创建完成,如果否,B 的BeanFactory 也会被存入三级缓存。
  5. B 注入 A 属性:B 开始注入 A 属性,尝试从一级、二级缓存中查找 A;如果在缓存中找不到 A,B 会尝试从三级缓存获取 A 的 BeanFactory,并通过 BeanFactory的getObject()方法获取 A 的属性,此时,A 被存入二级缓存,同时清除三级缓存;因此,B 能够成功注入 A 属性,B 接着执行初始化,处于实例化和初始化都已完成的完全状态。
  6. B 存入一级缓存:B执行addSingleton(),将完全状态的 B 存入一级缓存,并清空二级,三级缓存。
  7. A 继续注入 B 属性:A 继续注入 B 属性,调用beanFactory.getBean()方法获取 B,由于第六步已经将 B 存入一级缓存,A 可以直接获取 B,成功注入 B 属性, A 接着执行初始化,得到一个完全状态的 A。
  8. A 存入一级缓存:A 执行addSingleton(),将完全状态的 A 存入一级缓存,并清空二级缓存。

此时,A 和 B 都被实例化和初始化完成,解决了循环依赖的问题;这个流程确保了每个Bean在需要时都能够获取到已完全初始化的依赖项。

🚀常见疑问解答

问题一:为什么在 Bean B 被注入 Bean A 之前,需要将 Bean A 存入二级缓存?

  • 主要原因是,如果存在其他循环依赖需要用到 A,从二级缓存中直接取出早期的 Bean A 对象会更加高效。

问题二:为什么创建完 Bean 后要清空二、三级缓存?

  • 清空是为了节省存储空间,一旦 Bean 完全初始化并存储在一级缓存中,其在二、三级缓存中的记录就不再需要了。

问题三:三级缓存为什么不能解决构造器引起的循环依赖?

  • 这是因为构造器引起的循环依赖发生在 Bean 的实例化阶段,这个阶段比二、三级缓存处理的阶段还要早,无法创建出早期的半成品对象。

问题四:如果不使用三级缓存,只使用二级缓存,能否解决循环依赖?

肯定是不能的,二级缓存存储的 Bean 可能是两种类型,一种是实例化阶段创建出来的对象,另一种是实例化阶段创建出来的对象的代理对象;是否需要代理对象取决于你的配置需要,如是否添加了事务注解或自定义 AOP 切面;如果放弃使用三级缓存,即没有 ObjectFactory,那么就需要将早期的 Bean 放入二级缓存;但问题是,应该将未被代理的 Bean 还是代理的 Bean 放入二级缓存,这只能在属性注入阶段,处理注解时才能分辨。

  • 如果直接往二级缓存添加没有被代理的 Bean,那么此时注入给其它对象的 Bean 可能跟最后完全生成的 Bean 是不一样的,因为最后生成使用的是可能代理对象,此时注入的是原始对象,这这种情况是不允许发生的。
  • 如果直接往二级缓存添加一个代理 Bean,在不确定是否要使用代理对象的情况下,就有提前暴露代理对象的可能;正常的代理的对象都是初始化后期调用生成的,是基于后置处理器的,若提早的代理就违背了 Bean 定义的生命周期。

Spring 在一个三级缓存放置一个工厂,如果产生循环依赖 ,这个工厂的作用就是判断这个对象是否需要代理,如果否则直接返回,如果是则返回代理对像。

4. 源码验证

在项目中双击 Shift,全局查找文件:AbstractAutowireCapableBeanFactory,找到 550 行左右的 doCreateBean 方法,重点看一下 580 行到 600 行这20行代码就行,包含了三级缓存、属性注入、初始化,精华都在这20行,下面在源码中给出了关键注释。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    // 通过BeanDefinition实例化对象
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    // 对象是否单例、是否未创建完成
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        // 将对象的工厂加入到三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // 属性注入 (在这里解析@Autowired注解时,触发循环依赖)
        populateBean(beanName, mbd, instanceWrapper);
        // 初始化
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        else {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }

    if (earlySingletonExposure) {
        // 从缓存中获取 Bean
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName,
                            "Bean with name '" + beanName + "' has been injected into other beans [" +
                            StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                            "] in its raw version as part of a circular reference, but has eventually been " +
                            "wrapped. This means that said other beans do not use the final version of the " +
                            "bean. This is often the result of over-eager type matching - consider using " +
                            "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    // Register bean as disposable.
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

从缓存中获取 Bean 的源码

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    // 从一级缓存中获取
    // 如果一级缓存里没有,且 Bean 正在创建中
    // 就从二级缓存里获取
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        // 二级缓存没有,就从三级缓存获取一个工厂
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full sin
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 能获取到工厂则创建 Bean
                            singletonObject = singletonFactory.getObject();
                            // 把实例存入二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 把工厂从三级缓存移除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

总之,Spring 的三级缓存机制是一个巧妙的设计,它解决了在 Bean 初始化过程中可能出现的循环依赖问题;对于 Spring 的用户来说,这意味着更加稳定和可靠的 Bean 管理和依赖注入机制。


http://www.kler.cn/a/106074.html

相关文章:

  • 《基于 PySpark 的电影推荐系统分析及问题解决》
  • 无人机在森林中的应用!
  • 第二十一周学习周报
  • 编写一个生成凯撒密码的程序
  • 蓝牙 HFP 协议详解及 Android 实现
  • 层归一化和批归一化
  • 【EI会议征稿通知】第二届材料科学与智能制造国际学术会议(MSIM 2024)
  • python yield用法
  • java-- 静态数组
  • 多测师肖sir_高级金牌讲师__接口测试之练习题(6.1)
  • 数据结构与算法——分治法
  • tinymce输入框怎么限制只输入空格或者回车时不能提交
  • 微信JSAPI支付对接
  • 安卓开发实例:随机数
  • BI是什么?想要了解BI需要从哪些方面入手?
  • kali搭建docker
  • 硬件安全与机器学习的结合
  • 在进行自动化测试,遇到验证码的问题,怎么办?
  • FFmpeg编译安装(windows环境)以及在vs2022中调用
  • structs2 重构成SpringBoot架构
  • 解决Visual studio 未能正确加载...包问题
  • 一图读懂融云出海 全球化通信方案
  • 手把手教你安装配置『Oracle Database 19c』
  • AD20~PCB的板层设计和布线
  • Java实现电子元器件管理系统
  • python 获取本机ip