三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Spring三级缓存机制解析与循环依赖处理

Spring三级缓存机制解析与循环依赖处理

1. Spring 三级缓存机制深度解析

在Spring框架的核心设计中,循环依赖处理一直是个让开发者又爱又恨的话题。当Bean A依赖Bean B,而Bean B又反过来依赖Bean A时,传统依赖注入模式会陷入死循环。Spring通过独创的三级缓存机制优雅地解决了这个问题,其设计之精妙堪称框架设计的典范。

我曾在多个大型项目中处理过因循环依赖导致的启动异常,发现理解三级缓存的工作原理不仅能帮助排查问题,更能深入掌握Spring容器的运作机制。本文将结合Spring 5.3源码,拆解三级缓存如何像交通协管员一样指挥Bean的创建流程,避免"创建死锁"的发生。

2. 循环依赖的本质与破解思路

2.1 循环依赖的典型场景

考虑以下代码片段:

@Service public class ServiceA { @Autowired private ServiceB serviceB; } @Service public class ServiceB { @Autowired private ServiceA serviceA; }

当Spring容器启动时,它会尝试:

  1. 创建ServiceA实例 → 发现需要注入ServiceB
  2. 转去创建ServiceB实例 → 发现需要注入ServiceA
  3. 又回到ServiceA的创建... 形成无限递归

2.2 三级缓存的破局之道

Spring的解决方案是引入三个层次的缓存:

  • 一级缓存(singletonObjects):存放完全初始化好的Bean
  • 二级缓存(earlySingletonObjects):存放原始Bean对象(已实例化但未填充属性)
  • 三级缓存(singletonFactories):存放Bean工厂对象

这种分层设计允许Spring在Bean未完全初始化时,就能提供它的引用给其他Bean使用,打破循环链条。就像建筑工地中,虽然大楼还未完工,但可以先提供设计图纸供其他施工方参考。

3. 三级缓存运作全流程剖析

3.1 Bean创建的关键步骤

以ServiceA和ServiceB的循环依赖为例,详细流程如下:

  1. 开始创建ServiceA

    • 实例化ServiceA(调用构造函数)
    • 将ServiceA的ObjectFactory放入三级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  2. 填充ServiceA属性

    • 发现需要注入ServiceB
    • 检查一级缓存 → 无
    • 检查二级缓存 → 无
    • 检查三级缓存 → 无(因为ServiceB还未开始创建)
  3. 开始创建ServiceB

    • 实例化ServiceB
    • 将ServiceB的ObjectFactory放入三级缓存
  4. 填充ServiceB属性

    • 发现需要注入ServiceA
    • 检查一级缓存 → 无
    • 检查二级缓存 → 无
    • 检查三级缓存 → 找到ServiceA的ObjectFactory
    • 通过工厂获取ServiceA的早期引用(可能经过AOP代理)
    • 将ServiceA的代理对象放入二级缓存,并从三级缓存移除
  5. 完成ServiceB初始化

    • 属性注入完成(此时ServiceB持有ServiceA的代理)
    • 初始化后置处理器
    • 将完整ServiceB放入一级缓存
  6. 回到ServiceA的属性注入

    • 现在可以获取到完整的ServiceB实例
    • 完成ServiceA的属性注入
    • 执行初始化方法
    • 将完整ServiceA放入一级缓存

3.2 缓存状态变化图示

操作阶段一级缓存二级缓存三级缓存
初始状态
创建ServiceA实例后ServiceA的ObjectFactory
创建ServiceB实例后ServiceA+ServiceB的工厂
ServiceB注入ServiceA时ServiceA的早期引用ServiceB的工厂
ServiceB创建完成后ServiceBServiceA的早期引用
ServiceA创建完成后ServiceA+ServiceB

4. 源码级关键实现解析

4.1 DefaultSingletonBeanRegistry

这是三级缓存的核心实现类,关键字段包括:

// 一级缓存(完全初始化好的单例Bean) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存(提前曝光的原始Bean) private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存(单例工厂对象) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

4.2 getSingleton() 方法逻辑

获取Bean的核心逻辑如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 1. 检查一级缓存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 2. 检查二级缓存 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 3. 检查三级缓存 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 通过工厂获取早期引用 singletonObject = singletonFactory.getObject(); // 升级到二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }

4.3 AOP代理的特殊处理

当存在AOP代理时,三级缓存中的ObjectFactory会通过SmartInstantiationAwareBeanPostProcessor提前生成代理对象。这是通过AbstractAutoProxyCreator实现的:

public Object getEarlyBeanReference(Object bean, String beanName) { // 如果需要代理,则在此处创建代理对象 return wrapIfNecessary(bean, beanName); }

5. 实践中的典型问题与解决方案

5.1 构造器循环依赖无解

三级缓存只能解决字段注入(setter注入)的循环依赖。如果使用构造器注入:

@Service public class ServiceA { private final ServiceB serviceB; public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; } } @Service public class ServiceB { private final ServiceA serviceA; public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; } }

Spring会直接抛出BeanCurrentlyInCreationException,因为:

  1. 创建ServiceA需要先有ServiceB
  2. 创建ServiceB又需要先有ServiceA
  3. 无法通过提前暴露引用来打破循环

解决方案:重构代码避免构造器循环依赖,或改用@Lazy延迟加载

5.2 prototype作用域的循环依赖

Spring不会缓存prototype作用域的Bean,因此无法解决其循环依赖:

Error: Requested bean is currently in creation: Is there an unresolvable circular reference?

解决方案:改为singleton作用域,或使用@Lazy延迟注入

5.3 异步初始化导致的问题

当使用@Async时,代理对象的创建时机可能打乱三级缓存的正常工作流程:

@Service public class ServiceA { @Autowired private ServiceB serviceB; @Async public void asyncMethod() { ... } }

解决方案:确保异步方法所在的Bean不参与复杂依赖链,或显式配置代理模式

6. 性能优化与最佳实践

6.1 缓存配置调优

对于大型应用,可以调整缓存初始容量减少扩容开销:

// 在自定义BeanFactoryPostProcessor中设置 ((DefaultListableBeanFactory)beanFactory).setSingletonCacheCapacity(1024);

6.2 循环依赖检测工具

使用Spring的BeanCurrentlyInCreationException分析工具快速定位问题:

try { applicationContext.getBean(problematicBean); } catch (BeanCurrentlyInCreationException ex) { System.out.println("循环依赖链:" + ex.getBeanCurrentlyInCreationException()); }

6.3 架构设计建议

  1. 层级清晰化:遵循"上层依赖下层"原则

    • Controller → Service → Repository
    • 避免同层之间相互依赖
  2. 接口隔离:通过接口解耦具体实现

    @Service public class OrderService implements IOrderService { @Autowired private IPaymentService paymentService; } @Service public class PaymentService implements IPaymentService { @Autowired private IInventoryService inventoryService; }
  3. 事件驱动:用ApplicationEvent替代直接调用

    // 订单创建后发布事件 applicationContext.publishEvent(new OrderCreatedEvent(this, order)); // 库存服务监听事件 @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // 减库存逻辑 }

7. 三级缓存的演进与替代方案

7.1 Spring早期版本的实现

在Spring 2.x时代,仅使用二级缓存:

  • 一级缓存:完整Bean
  • 二级缓存:原始Bean 这种设计无法处理AOP代理的情况,导致后续引入了三级缓存。

7.2 其他IoC容器的解决方案

  1. Guice:默认不支持循环依赖,需通过Provider延迟获取

    public class ServiceA { private final Provider<ServiceB> serviceB; @Inject public ServiceA(Provider<ServiceB> serviceB) { this.serviceB = serviceB; } }
  2. Dagger:编译时检测循环依赖,直接报错

  3. Micronaut:通过编译时AOP避免运行时代理,简化依赖处理

7.3 Spring Reactive的挑战

在WebFlux等响应式编程模型中,传统的三级缓存机制面临新挑战:

  • Bean可能是惰性初始化的
  • 依赖关系在订阅时才实际建立
  • 需要新的解决方案来处理响应式组件的循环依赖

理解三级缓存机制的价值不仅在于解决具体问题,更在于领悟框架设计中的权衡艺术。Spring开发者需要明白:三级缓存是手段而非目的,真正的优雅设计应当尽可能避免复杂的循环依赖。当你的Bean关系网变得过于复杂时,这往往是架构需要重构的信号,而不是强行依赖框架特性的理由。

← 返回列表