1. 循环依赖问题本质剖析
在Spring框架中,当两个或多个Bean相互依赖时就会形成循环依赖。比如Bean A依赖Bean B,同时Bean B又依赖Bean A。这种场景下,Spring的默认依赖注入机制会陷入死循环,最终抛出BeanCurrentlyInCreationException异常。
循环依赖的典型表现是控制台出现类似这样的错误日志:
code复制Requested bean is currently in creation: Is there an unresolvable circular reference?
从底层实现来看,Spring通过三级缓存机制解决循环依赖:
- 一级缓存:存放完全初始化好的单例Bean
- 二级缓存:存放早期暴露的Bean对象(已实例化但未完成属性注入)
- 三级缓存:存放Bean工厂对象(用于生成早期引用)
重要提示:Spring只能解决单例作用域下的Setter注入循环依赖。原型(prototype)作用域的Bean或构造器注入方式的循环依赖无法自动解决,需要特殊处理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. AOP代理与循环依赖的冲突根源
当AOP与循环依赖同时存在时,问题会变得更加复杂。这是因为Spring AOP通过动态代理实现,而代理对象的生成时机与普通Bean不同:
-
普通Bean的生命周期:
- 实例化 → 属性注入 → 初始化 → 加入容器
-
AOP代理Bean的生命周期:
- 实例化 → 属性注入 → 生成代理对象 → 初始化 → 加入容器
关键矛盾点在于:当Bean A需要注入Bean B时,如果Bean B需要被AOP代理,Spring必须在属性注入阶段就决定是否返回原始对象还是代理对象。这个决策过程会导致循环依赖处理出现异常。
3. 解决方案实战
3.1 使用@Lazy延迟加载
@Lazy注解是最简单的解决方案,它通过延迟依赖解析来打破循环:
java复制@Service
public class ServiceA {
@Autowired
@Lazy // 关键注解
private ServiceB serviceB;
}
实现原理:
- 注入的不是实际Bean,而是一个代理对象
- 只有当第一次调用方法时才会真正触发依赖解析
- 此时所有Bean都已初始化完成,可以正常生成AOP代理
优点:
- 实现简单,只需添加一个注解
- 对代码侵入性小
缺点:
- 可能掩盖设计问题,建议仅作为临时方案
- 调试时堆栈信息会更复杂
3.2 调整Bean加载顺序
通过@DependsOn显式指定Bean初始化顺序:
java复制@Service
@DependsOn("serviceB") // 明确声明依赖顺序
public class ServiceA {
@Autowired
private ServiceB serviceB;
