Spring 循环依赖?

2025年 阅读约 15 分钟 面试指南 · Spring Boot

深入解析Spring循环依赖:三级缓存原理、singletonObjects/earlySingletonObjects/singletonFactories、为什么需要三级缓存、构造器注入无法解决的原因,附面试模拟问答。

一句话总结

Spring 通过三级缓存解决单例 Bean 的 Setter/字段注入循环依赖:一级缓存 singletonObjects(完全初始化好的 Bean)、二级缓存 earlySingletonObjects(早期引用,未完成属性填充)、三级缓存 singletonFactories(ObjectFactory,可生成代理对象)。核心流程:A 创建 → 放入三级缓存 → 发现依赖 B → 创建 B → B 发现依赖 A → 从三级缓存获取 A 的早期引用 → B 完成 → A 完成。构造器注入无法解决(因为对象还没创建出来)。

初级理解

什么是循环依赖?

# 循环依赖示例 @Service public class A { @Autowired private B b; // A 依赖 B } @Service public class B { @Autowired private A a; // B 依赖 A } # 创建流程 # 1. 创建 A → 发现需要 B → 去创建 B # 2. 创建 B → 发现需要 A → 去创建 A # 3. 死循环!

哪些循环依赖可以解决?

# ✅ 可以解决:Setter/字段注入的单例 Bean @Service public class A { @Autowired private B b; // ✅ 字段注入 } # ❌ 不能解决:构造器注入 @Service public class A { private final B b; public A(B b) { // ❌ 构造器注入 this.b = b; } } # 原因:构造器调用时对象还没创建,无法放入缓存 # ❌ 不能解决:原型(prototype)Bean # 原因:原型 Bean 不放入缓存,每次都是新对象

中级深入

三级缓存详解

# 三级缓存定义(DefaultSingletonBeanRegistry) # 一级缓存:完全初始化好的 Bean Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); # 二级缓存:早期引用(已实例化但未填充属性) Map<String, Object> earlySingletonObjects = new HashMap<>(16); # 三级缓存:Bean 工厂(可生成代理对象) Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); # 获取 Bean 的流程(getSingleton) protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 1. 从一级缓存获取 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 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; }

高级拓展

为什么需要三级缓存?二级不行吗?

# 核心原因:AOP 代理 # 如果 A 需要 AOP 代理,B 注入的必须是代理后的 A # 只用二级缓存的问题: # 二级缓存存的是原始对象 # 如果 A 需要代理,B 注入的是原始 A(错误!) # 三级缓存的优势: # 三级缓存存的是 ObjectFactory # ObjectFactory.getObject() 可以返回代理对象 # 在 getObject() 中调用 BeanPostProcessor 生成代理 # 流程: # 1. A 实例化后 → 放入三级缓存(ObjectFactory) # 2. B 需要 A → 调用 ObjectFactory.getObject() # 3. getObject() 中执行 BeanPostProcessor → 生成 A 的代理 # 4. 代理 A 放入二级缓存 # 5. B 注入代理 A ✅
总结:如果没有 AOP,二级缓存就够了。三级缓存是为了解决 AOP 代理场景下的循环依赖。

实战场景

场景:如何避免循环依赖?

# 1. 重新设计,消除循环依赖 # A → B → A 改为 A → B → C → A(引入中间层) # 2. 使用 @Lazy 延迟注入 @Service public class A { @Lazy @Autowired private B b; // 延迟注入,先创建 A,用到 B 时才注入 } # 3. 使用 Setter 注入代替构造器注入 @Service public class A { private B b; @Autowired public void setB(B b) { this.b = b; } } # 4. 使用 ApplicationContext.getBean() @Service public class A { @Autowired private ApplicationContext context; public void doSomething() { B b = context.getBean(B.class); // 延迟获取 } }

面试模拟

面试官:Spring 如何解决循环依赖?

你:通过三级缓存。一级存完全初始化好的 Bean,二级存早期引用,三级存 ObjectFactory。A 创建时先放入三级缓存,发现依赖 B 就去创建 B,B 发现依赖 A 就从三级缓存获取 A 的早期引用,B 完成后再完成 A。三级缓存是为了处理 AOP 代理场景。

面试官:为什么构造器注入无法解决循环依赖?

你:因为构造器调用时对象还没创建出来,无法放入缓存。而 Setter/字段注入时对象已经实例化,可以先放入缓存暴露早期引用。