Spring概念:三级缓存

Spring 的三级缓存介绍

在 Spring 中,为了解决单例 Bean 的循环依赖问题,使用了三级缓存机制。这三级缓存本质上是三个 Map,分别如下:

1. 一级缓存(singletonObjects)

  • 含义:也被称作单例池,它存储的是已经完全初始化好的单例 Bean 实例。当需要获取一个单例 Bean 时,Spring 会优先从这个缓存中查找。
  • 数据结构Map<String, Object>,其中键是 Bean 的名称,值是对应的 Bean 实例。

2. 二级缓存(singletonFactories)

  • 含义:存放的是单例 Bean 的工厂对象。当 Bean 实例化完成,但还未完成属性注入和初始化时,会将一个创建该 Bean 代理对象的工厂存入此缓存。
  • 数据结构Map<String, ObjectFactory<?>>,键为 Bean 的名称,值是用于创建 Bean 的工厂对象。

3. 三级缓存(earlySingletonObjects)

  • 含义:存储的是提前暴露的单例 Bean 实例,这些 Bean 虽然还未完成全部的初始化流程,但已经可以被引用。通过这个缓存,其他 Bean 在依赖该 Bean 时可以获取到一个早期的实例。
  • 数据结构Map<String, Object>,键是 Bean 的名称,值是早期暴露的 Bean 实例。

应用场景

主要应用场景就是解决单例 Bean 的循环依赖问题。在 Spring 应用中,当多个单例 Bean 之间存在相互依赖形成循环时,利用三级缓存机制可以保证这些 Bean 能够正常创建和初始化。例如,在一个 Web 应用中,ServiceA 依赖 ServiceB,而 ServiceB 又依赖 ServiceA,这种情况下就会出现循环依赖,三级缓存就可以发挥作用来解决该问题。

缓存解决循环依赖问题的原理

下面通过一个具体的循环依赖场景(BeanA 依赖 BeanBBeanB 依赖 BeanA)来详细解释三级缓存是如何解决循环依赖问题的:

步骤 1:创建 BeanA

  • Spring 容器开始创建 BeanA,首先将 BeanA 的创建标记为正在创建状态。
  • 实例化 BeanA,此时 BeanA 只是一个空的实例,还未进行属性注入和初始化。
  • 将创建 BeanA 的工厂对象存入二级缓存 singletonFactories 中,这个工厂对象可以在需要时返回 BeanA 的早期实例。

步骤 2:BeanA 注入 BeanB

  • 在对 BeanA 进行属性注入时,发现 BeanA 依赖 BeanB,于是 Spring 容器开始创建 BeanB

步骤 3:创建 BeanB

  • 同样,将 BeanB 的创建标记为正在创建状态。
  • 实例化 BeanB,然后将创建 BeanB 的工厂对象存入二级缓存 singletonFactories 中。

步骤 4:BeanB 注入 BeanA

  • 在对 BeanB 进行属性注入时,发现 BeanB 依赖 BeanA。此时,Spring 会先从一级缓存 singletonObjects 中查找 BeanA,发现没有找到。
  • 接着从二级缓存 singletonFactories 中查找 BeanA 的工厂对象,找到后通过该工厂对象获取 BeanA 的早期实例,并将这个早期实例存入三级缓存 earlySingletonObjects 中,同时从二级缓存中移除该工厂对象。
  • 将这个早期的 BeanA 实例注入到 BeanB 中。

步骤 5:完成 BeanB 的创建

  • BeanB 完成属性注入和初始化后,将 BeanB 存入一级缓存 singletonObjects 中,并从正在创建状态标记中移除。

步骤 6:完成 BeanA 的创建

  • 此时 BeanA 可以从一级缓存中获取到已经完全初始化好的 BeanB 实例,完成属性注入和初始化。
  • 最后将 BeanA 存入一级缓存 singletonObjects 中,并从三级缓存 earlySingletonObjects 中移除。

通过以上步骤,利用三级缓存机制,Spring 容器可以在存在循环依赖的情况下,成功创建和初始化所有的单例 Bean。

下面是一个简单的示例代码,展示了 BeanABeanB 的循环依赖:

// BeanA 类
class BeanA {
    private BeanB beanB;

    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

// BeanB 类
class BeanB {
    private BeanA beanA;

    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 配置类
@Configuration
class AppConfig {
    @Bean
    public BeanA beanA() {
        BeanA beanA = new BeanA();
        beanA.setBeanB(beanB());
        return beanA;
    }

    @Bean
    public BeanB beanB() {
        BeanB beanB = new BeanB();
        beanB.setBeanA(beanA());
        return beanB;
    }
}

// 主类
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        BeanA beanA = context.getBean(BeanA.class);
        BeanB beanB = context.getBean(BeanB.class);
        System.out.println("BeanA 和 BeanB 成功创建");
    }
}

在这个示例中,BeanABeanB 相互依赖,但由于 Spring 的三级缓存机制,它们能够正常创建和初始化。

为什么还需要第三级缓存?

在 Spring 的三级缓存设计中,第三级缓存(earlySingletonObjects)的存在是为了优化早期实例的获取效率,并支持代理对象的提前暴露。以下是其必要性的详细解释:

1. 为什么需要第三级缓存?

场景背景

在解决循环依赖时,Spring 需要提前暴露未完全初始化的 Bean 实例。此时,二级缓存(singletonFactories)存储的是工厂对象,而三级缓存存储的是工厂对象生成的早期实例。两者的协作可以避免重复生成实例,同时支持 AOP 代理的创建。

具体原因

  • 避免重复调用工厂方法
    二级缓存中的工厂对象(ObjectFactory)每次调用都会生成一个新的早期实例。如果多个 Bean 依赖同一个未初始化的 Bean,直接通过工厂生成实例会导致多次创建,造成性能浪费。
    三级缓存的作用:将工厂生成的早期实例缓存起来,后续依赖方可以直接从缓存中获取,避免重复生成。

  • 支持 AOP 代理的提前暴露
    在 Spring 中,若 Bean 需要被代理(如通过 @Transactional@Async),代理对象的创建必须在属性注入前完成。三级缓存存储的是工厂生成的代理对象,这样其他 Bean 注入的是代理而非原始实例,确保代理逻辑的正确性。

2. 三级缓存与二级缓存的协作流程

BeanABeanB 的循环依赖为例:

  1. 创建 BeanA
    • 实例化 BeanA,将其工厂对象存入二级缓存(singletonFactories)。
  2. BeanA 注入 BeanB
    • 创建 BeanB,同样将其工厂存入二级缓存。
  3. BeanB 注入 BeanA
    • 从二级缓存中取出 BeanA 的工厂,调用工厂生成早期实例(可能是代理对象)。
    • 将该实例存入三级缓存(earlySingletonObjects),并移除二级缓存中的工厂。
    • 将该实例注入到 BeanB
  4. 完成初始化后
    • BeanB 初始化完成,存入一级缓存,BeanA 同样完成初始化并存入一级缓存。
    • 三级缓存中的早期实例被移除。

3. 为什么不能只用二级缓存?

若仅有二级缓存(工厂对象):

  • 每次获取早期实例都需要调用工厂,导致重复创建。
  • 无法处理代理对象的提前暴露。例如,若 BeanA 是代理对象,工厂需在 Bean 实例化后立即生成代理,而三级缓存能确保后续依赖方直接获取代理对象。

4. 总结

缓存层级 作用描述 关键场景
一级缓存 存储完全初始化的单例 Bean 正常获取 Bean 时使用
二级缓存 存储工厂对象,用于生成早期实例 处理循环依赖时生成早期实例的工厂
三级缓存 缓存工厂生成的早期实例(可能是代理) 优化性能,避免重复生成实例;支持代理对象的提前暴露

第三级缓存通过缓存已生成的早期实例,确保循环依赖中的 Bean 能高效获取实例,并正确处理代理逻辑,是 Spring 解决循环依赖的关键设计。

Spring 是如何通过第三级缓存来避免 AOP 问题的?

在 Spring 中,当存在 AOP(面向切面编程)和循环依赖的情况时,第三级缓存(earlySingletonObjects)发挥着至关重要的作用,下面详细解释 Spring 如何利用第三级缓存来避免 AOP 带来的问题。

1. AOP 和循环依赖可能产生的问题

在 AOP 场景下,Spring 会为目标 Bean 创建代理对象,这个代理对象会包含额外的增强逻辑,如事务管理、日志记录等。当存在循环依赖时,如果处理不当,可能会导致以下问题:

  • 代理对象创建时机不当:如果在 Bean 初始化过程中,没有正确处理代理对象的创建和注入,可能会导致一个 Bean 注入的是原始对象而非代理对象,从而使得 AOP 增强逻辑无法生效。
  • 重复创建代理对象:若没有合理的缓存机制,在解决循环依赖的过程中,可能会多次创建代理对象,造成资源浪费。

2. 第三级缓存解决 AOP 问题的原理

2.1 提前暴露代理对象的工厂

在 Spring 容器创建 Bean 时,当 Bean 实例化完成但还未进行属性注入和初始化时,会将一个创建该 Bean 代理对象的工厂存入二级缓存(singletonFactories)中。这个工厂对象会根据 Bean 是否需要进行 AOP 代理来决定返回原始对象还是代理对象。

2.2 利用第三级缓存存储早期代理对象

当另一个 Bean 在属性注入过程中需要依赖这个还未完全初始化的 Bean 时,Spring 会从二级缓存中获取该 Bean 的工厂对象,并调用工厂对象的方法来获取早期实例。如果该 Bean 需要进行 AOP 代理,工厂对象会创建代理对象并返回。这个早期的代理对象会被存入第三级缓存(earlySingletonObjects)中,同时从二级缓存中移除对应的工厂对象。

2.3 保证注入的是代理对象

通过将早期的代理对象存入第三级缓存,其他 Bean 在依赖该 Bean 时,会从第三级缓存中获取到这个代理对象并进行注入。这样就保证了在循环依赖的情况下,每个 Bean 注入的都是正确的代理对象,从而避免了 AOP 增强逻辑失效的问题。

2.4 避免重复创建代理对象

由于第三级缓存存储了早期的代理对象,后续再需要获取该 Bean 的实例时,会直接从第三级缓存中获取,而不会再次调用二级缓存中的工厂对象来创建代理对象,从而避免了重复创建代理对象的问题。

3. 示例代码及流程分析

3.1 示例代码

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

// 切面类
@Aspect
class MyAspect {
    @Before("execution(* com.example.demo.ServiceA.*(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }
}

// ServiceA 类
class ServiceA {
    private ServiceB serviceB;

    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void doSomething() {
        System.out.println("ServiceA is doing something");
    }
}

// ServiceB 类
class ServiceB {
    private ServiceA serviceA;

    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void doSomething() {
        System.out.println("ServiceB is doing something");
    }
}

// 配置类
@Configuration
@EnableAspectJAutoProxy
class AppConfig {
    @Bean
    public ServiceA serviceA() {
        ServiceA serviceA = new ServiceA();
        serviceA.setServiceB(serviceB());
        return serviceA;
    }

    @Bean
    public ServiceB serviceB() {
        ServiceB serviceB = new ServiceB();
        serviceB.setServiceA(serviceA());
        return serviceB;
    }

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }
}

// 主类
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        ServiceA serviceA = context.getBean(ServiceA.class);
        serviceA.doSomething();
    }
}

3.2 流程分析

  • 创建 ServiceA:Spring 容器开始创建 ServiceA,实例化 ServiceA 后,将创建 ServiceA 代理对象的工厂存入二级缓存。
  • ServiceA 注入 ServiceB:在对 ServiceA 进行属性注入时,发现依赖 ServiceB,于是开始创建 ServiceB
  • 创建 ServiceB:实例化 ServiceB 后,将创建 ServiceB 代理对象的工厂存入二级缓存。
  • ServiceB 注入 ServiceA:在对 ServiceB 进行属性注入时,发现依赖 ServiceA。从二级缓存中获取 ServiceA 的工厂对象,调用工厂方法创建 ServiceA 的代理对象,并将该代理对象存入第三级缓存,同时从二级缓存中移除 ServiceA 的工厂对象。将 ServiceA 的代理对象注入到 ServiceB 中。
  • 完成 ServiceB 的创建ServiceB 完成属性注入和初始化后,存入一级缓存。
  • 完成 ServiceA 的创建ServiceA 从一级缓存中获取到已经完全初始化好的 ServiceB 实例,完成属性注入和初始化。最后将 ServiceA 存入一级缓存,同时从第三级缓存中移除 ServiceA 的早期代理对象。

通过以上流程,Spring 利用第三级缓存确保了在循环依赖的情况下,ServiceAServiceB 注入的都是正确的代理对象,避免了 AOP 问题的出现。

posted @ 2025-03-14 18:17  皇问天  阅读(851)  评论(1)    收藏  举报