三级缓存无法解决的@Async的循环依赖异常以及@Lazy解决方案

在 Spring 中,@Async 注解用于将方法标记为异步执行,其底层依赖 动态代理机制 实现。当 Bean 之间存在循环依赖且使用了 @Async 时,会因 代理对象的生成时机与循环依赖的解决机制冲突 而抛出异常。以下是详细分析:


1. 代理的生成时机与循环依赖的冲突
(1) 正常循环依赖的解决流程

Spring 通过 三级缓存 解决单例 Bean 的循环依赖问题:

  1. 实例化阶段:创建 Bean 的原始对象,存入三级缓存(singletonFactories)。
  2. 属性填充阶段:从三级缓存获取依赖的 Bean 的早期引用(原始对象)。
  3. 初始化阶段:完成 Bean 的初始化,存入一级缓存(singletonObjects)。

对于大多数代理(如 @Transactional),Spring 会在 属性填充之前 生成代理对象,并提前暴露到三级缓存中,确保依赖链中的其他 Bean 注入的是代理对象。例如:

// A 依赖 B,B 依赖 A,且 A 使用 @Transactional
A实例化 → 生成代理对象 A' → 存入三级缓存 → B 注入 A' → B初始化 → A注入 B → A初始化完成

此时,A 和 B 依赖的都是代理对象,版本一致,循环依赖正常解决。

(2) @Async 的特殊性

@Async 的代理对象生成时机 晚于其他代理

  • 代理生成阶段@Async 的代理在 Bean 初始化之后postProcessAfterInitialization)生成。
  • 流程冲突
    如果 A 和 B 存在循环依赖,且 A 使用了 @Async
    1. A 实例化 → 存入三级缓存(原始对象)。
    2. B 创建时从三级缓存获取 A 的原始对象 → 完成 B 的初始化。
    3. A 初始化完成后生成代理对象 A' → 存入一级缓存。
    4. 问题:B 中注入的是 A 的原始对象,而容器中最终存在的是代理对象 A',导致 对象版本不一致

此时,Spring 检测到 A 的原始对象和代理对象同时存在,抛出异常:

BeanCurrentlyInCreationException: 
Error creating bean with name 'a': 
Bean with name 'a' has been injected into other beans [...] in their raw version as part of a circular reference.

2. 核心矛盾:代理对象的版本一致性
(1) 无代理场景

若 Bean 无代理(如普通 Bean):

  • 所有依赖注入的都是原始对象,版本一致,循环依赖正常解决。
(2) 有代理但生成时机正确(如 @Transactional
  • 代理对象在 属性填充前 生成并暴露,依赖链中所有 Bean 注入的是同一代理对象。
(3) @Async 的代理生成时机错误
  • 依赖链中的 Bean 注入了原始对象,而 最终 Bean 是代理对象
  • Spring 强制要求单例 Bean 的唯一性,因此抛出异常。

3. 为什么 @Lazy 可以解决此问题?

@Lazy 通过 延迟初始化 和 代理占位符 打破循环依赖的强触发条件:

  1. 延迟初始化:被 @Lazy 标记的依赖不会在启动时立即初始化。
  2. 代理占位符:注入一个轻量级代理,仅在首次调用时触发实际 Bean 的初始化。
示例流程
// A 依赖 B,B 依赖 A,且 A 使用 @Async
@Lazy 注入 B → A 完成初始化生成代理 → B 实际调用时触发初始化 → B 注入 A 的代理对象
  • 效果:A 和 B 的初始化顺序解耦,依赖链中所有对象版本一致。

4. 其他解决方案
(1) 重构代码结构
  • 将 @Async 方法移至独立的 Bean,避免参与循环依赖。
@Service
public class AsyncService {
    @Async
    public void asyncMethod() { /*...*/ }
}

@Service
public class AService {
    @Autowired
    private AsyncService asyncService; // 无循环依赖
}
(2) 避免 @Async 与循环依赖叠加
  • 确保 @Async 方法不涉及核心依赖链。

5. 总结
核心问题 解决方案 原理
@Async 代理生成在初始化后,导致版本不一致 使用 @Lazy 注解 延迟依赖初始化,通过代理占位符保证版本一致性
循环依赖强触发条件 重构代码,分离异步逻辑 消除循环依赖
代理对象与原始对象冲突 避免 @Async 参与核心依赖链 隔离异步方法与非循环依赖组件

关键结论
@Async 的循环依赖问题本质是 代理生成时机与依赖注入顺序的冲突,通过调整代理生成时机(如 @Lazy)或消除循环依赖,可彻底解决此问题。

你可能感兴趣的:(spring,java,后端)