当前位置: 面试刷题>> 为什么 Spring 循环依赖需要三级缓存,二级不够吗?


在深入探讨Spring框架中循环依赖问题及其解决方案,特别是为何需要三级缓存而非简单的二级缓存时,我们首先需要理解Spring容器如何管理Bean的生命周期以及循环依赖的基本概念。循环依赖,简单来说,就是两个或多个Bean在实例化过程中互相引用,形成了一个闭环。

理解Spring的Bean生命周期与缓存机制

Spring容器管理Bean的生命周期,从创建、配置到销毁,通过一系列复杂的步骤来确保Bean的依赖注入(DI)和生命周期管理。在这个过程中,Spring利用缓存机制来优化性能,并处理循环依赖的问题。

传统的二级缓存方案通常包括一个早期引用(early reference)缓存和一个完全初始化后的Bean缓存。早期引用缓存存储的是Bean的原始状态(如构造后的实例,但尚未填充依赖),而完全初始化后的Bean缓存则存储已经填充所有依赖并经过所有后置处理器(如AOP增强)处理的Bean实例。

为什么需要三级缓存?

尽管二级缓存能够在一定程度上解决某些类型的循环依赖问题,但它在处理涉及代理对象的循环依赖时显得力不从心。在Spring中,AOP代理是一种常见的增强Bean行为的方式。当一个Bean需要被AOP代理时,其实际注入到其他Bean中的是一个代理对象,而非原始的Bean实例。

问题场景: 假设有两个Bean,A和B,它们互相依赖,并且都被AOP增强。在传统的二级缓存机制下,当Spring尝试创建A时,发现需要B,于是开始创建B。在创建B的过程中,B又需要A,此时由于A尚未完成初始化(包括AOP代理的创建),传统的二级缓存无法直接提供一个可用的A的代理对象给B。这会导致循环依赖无法解决。

三级缓存的引入: 为了解决这个问题,Spring引入了第三级缓存,通常被称为“单例工厂缓存”(Singleton Factory Cache)。这个缓存存储的是能够创建Bean代理对象的工厂(Factory),而不是Bean的实例本身。当Bean A被创建但尚未完成AOP代理的创建时,Spring会将一个能够生成A的代理对象的工厂放入三级缓存。随后,当Bean B需要A的代理对象时,可以从这个工厂中获取,从而解决了循环依赖的问题。

工作流程简述

  1. 一级缓存:存储完全可用的Bean实例。
  2. 二级缓存:存储早期引用,即尚未填充依赖的Bean实例。
  3. 三级缓存:存储能够创建Bean代理对象的工厂。

在创建Bean A时,如果A需要被AOP代理,Spring会先创建一个代理工厂并将其放入三级缓存。然后,Spring会将A的早期引用放入二级缓存,并继续处理A的依赖。当A需要B且B又需要A的代理时,B可以从三级缓存中获取A的代理工厂,从而生成A的代理对象并继续B的创建过程。最终,当A和B都完全初始化后,它们的实例会被放入一级缓存。

示例(概念性)

虽然这里不直接提供完整的Java代码,但我们可以概念性地描述这一过程:

// 伪代码,表示三级缓存的使用
class SingletonObjectFactory implements FactoryBean<Object> {
    private final Object target; // 原始Bean实例

    // 构造时传入原始Bean实例
    public SingletonObjectFactory(Object target) {
        this.target = target;
    }

    // 创建并返回AOP代理对象
    @Override
    public Object getObject() {
        return createProxy(target);
    }

    // 省略其他方法
}

// 在Spring容器创建Bean时
// 1. 创建Bean A的实例并放入三级缓存(作为SingletonObjectFactory)
// 2. 将A的早期引用放入二级缓存
// 3. 创建Bean B时,从三级缓存获取A的SingletonObjectFactory,并调用getObject()获取A的代理
// 4. A和B都完全初始化后,移入一级缓存

通过三级缓存机制,Spring能够优雅地解决涉及AOP代理的循环依赖问题,这是传统二级缓存所无法做到的。这一设计体现了Spring框架在处理复杂依赖关系时的灵活性和强大能力。