spring仅能解决单例bean的setter/字段注入循环依赖,依赖三级缓存提前暴露半成品对象;构造器注入、原型bean及含@postconstruct的场景均无法解决。

Spring 中的循环依赖问题,本质是两个或多个 Bean 相互依赖对方,在初始化过程中形成闭环。Setter 注入之所以能规避部分报错,是因为它将依赖注入推迟到对象实例化之后、初始化之前(即通过 populateBean 阶段完成),配合 Spring 的三级缓存机制(尤其是二级缓存 earlySingletonObjects)实现提前暴露未完全初始化的对象引用。
理解为什么 Setter 注入能“绕过”部分循环依赖报错
Spring 默认使用构造器注入时,必须在创建 A 的同时注入 B,而 B 又依赖 A,此时 A 还没构造完,B 无法获取 A 实例 → 报 BeanCurrentlyInCreationException。Setter 注入则不同:
- A 先调用无参构造器完成实例化(此时 A 是个空壳)
- A 被提前放入二级缓存(earlySingletonObjects),供 B 获取引用
- B 创建时从缓存中拿到 A 的半成品实例,完成自身实例化和属性注入
- B 初始化完成后,再回过头给 A 的 setter 方法注入 B
这个过程依赖 Spring 容器的“提前曝光”能力,仅对单例 Bean 有效,且要求至少一个 Bean 使用非构造器注入(Setter 或字段注入)。
正确使用 Setter 注入解决循环依赖的写法
必须确保两个 Bean 都是单例(默认),且至少一方不使用构造器注入依赖。例如:
@Service
public class ServiceA {
private ServiceB serviceB;
// 必须提供 public setter 方法
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
// 构造器注入也可,但不能两边都用
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
注意:若两个类都用 @Autowired 在字段上(即字段注入),Spring 同样会走 setter 逻辑(本质是反射调用生成的 setter),也能生效;但显式写出 setter 更清晰、更可控。
哪些情况 Setter 注入也救不了?
不是所有循环依赖都能靠 Setter 注入解决:
- 原型(prototype)作用域 Bean:不加入三级缓存,每次创建新实例,无法提前曝光 → 必然报错
- 构造器注入 + 构造器注入:双方都在 new 实例时就要对方,无缓存介入机会 → 立即失败
- 依赖中有 @PostConstruct 或 InitializingBean.afterPropertiesSet:若 A 的初始化方法里调用了 B 的方法,而 B 尚未初始化完成,可能引发 NPE 或逻辑错误(虽不报循环依赖异常,但运行时出问题)
比 Setter 更推荐的长期解法
Setter 注入只是“技术性绕过”,不代表设计合理。更健康的方案包括:
- 重构依赖关系:提取公共逻辑到第三方服务,让 A 和 B 都依赖 C,打破闭环
-
使用 @Lazy 延迟加载:在依赖方声明
@Lazy ServiceB serviceB,让 B 的创建推迟到第一次调用时,常用于启动阶段解耦 - 引入事件或回调机制:A 初始化完成后发布事件,B 订阅并执行后续操作,避免直接持有引用
Setter 注入是 Spring 提供的兼容性保障,不是鼓励你写循环依赖。发现循环依赖时,优先审视业务模型是否合理。











