题目
Spring框架中如何解决构造器注入的循环依赖问题?
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
Bean生命周期,循环依赖处理机制,构造器注入限制,代理对象处理
快速回答
Spring无法直接解决构造器注入的循环依赖问题,因为它在Bean实例化阶段就会抛出BeanCurrentlyInCreationException。解决方案包括:
- 改用Setter/Field注入:利用三级缓存机制解决
- 使用@Lazy延迟加载:在构造参数上添加@Lazy注解
- 重构代码设计:通过ApplicationContextAware或方法注入
- 调整Bean初始化顺序:结合@DependsOn注解
1. 问题本质与错误场景
当两个Bean通过构造器相互依赖时:
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}启动时将抛出异常:BeanCurrentlyInCreationException: Requested bean is currently in creation
2. Spring解决循环依赖的原理
Spring通过三级缓存解决Setter/Field注入的循环依赖:
- 一级缓存:存放完整初始化的单例Bean
- 二级缓存:存放早期暴露的对象(未填充属性)
- 三级缓存:存放Bean工厂(用于生成代理对象)
但构造器注入在实例化阶段就需要完整依赖,此时依赖对象尚未创建,导致流程中断。
3. 解决方案与代码示例
方案1:改用Setter/Field注入(推荐)
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired // 或使用@Resource
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}方案2:使用@Lazy延迟加载
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB; // 实际注入的是代理对象
}
}方案3:ApplicationContextAware手动注入
@Service
public class ServiceA implements ApplicationContextAware {
private ServiceB serviceB;
@Override
public void setApplicationContext(ApplicationContext ctx) {
// 在Bean初始化后手动获取依赖
this.serviceB = ctx.getBean(ServiceB.class);
}
}方案4:结合@DependsOn调整初始化顺序
@Service
@DependsOn("serviceB") // 强制ServiceB先初始化
public class ServiceA {
// 字段注入
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}4. 最佳实践与注意事项
- 优先选择Setter注入:平衡灵活性和可测试性
- 避免强制循环依赖:重构设计(如引入第三方类)是根本解决方案
- 代理对象的特殊处理:AOP代理可能使@Lazy方案更复杂
- 原型Bean的限制:Spring不管理原型Bean的循环依赖
5. 常见错误
- 尝试在构造器中调用依赖Bean的方法
- 混合使用构造器注入和字段注入导致意外行为
- 忽视@Lazy代理对象的方法调用限制
6. 扩展知识
- Spring容器的启动阶段:Bean定义加载→实例化→属性填充→初始化
- 循环依赖检测算法:基于Bean创建栈的依赖跟踪
- 非单例Bean的处理:原型Bean每次创建新实例,无法解决循环依赖
- Spring Boot 2.6+的变更:默认禁止循环依赖(可通过spring.main.allow-circular-references调整)