在Java后端面试中,面试题:Spring 循环依赖怎么解决的是考察Spring IoC底层机制、缓存设计逻辑、边界场景认知的核心题目——它不仅能看穿你对Spring容器核心原理的掌握程度,更能判断你是否具备排查Bean创建异常、优化依赖设计的实战能力。鳄鱼java社区的面试跟踪数据显示,能讲清三级缓存的作用、AOP代理一致性、循环依赖边界的求职者,Spring岗位通过率比仅背“三级缓存”的求职者高95%。
一、拆解:面试题背后的3个核心考察点

很多求职者开口就说“Spring用三级缓存解决循环依赖”,但这完全没触及面试官的考察点。这个面试题的本质是要你回答3个关键问题:
1. 缓存设计逻辑:为什么需要三级缓存?二级缓存能不能解决问题?三级缓存的核心价值是什么?
2. 边界场景认知:哪些循环依赖Spring能解决,哪些不能?为什么?
3. 源码级理解:Spring容器是如何通过缓存提前暴露对象的?AOP代理在循环依赖中是如何保证一致性的?
鳄鱼java社区的Spring专家强调:面试中第一个提到“三级缓存是为了解决AOP代理与循环依赖的一致性问题”的求职者,会立刻获得面试官的好感——这证明你不是在背模板,而是理解底层设计的开发者。
二、基础认知:什么是Spring循环依赖?哪些能解决?
要回答面试题:Spring 循环依赖怎么解决的,首先要明确“循环依赖的定义与可解决场景”:
1. 循环依赖的定义:两个或多个Bean之间互相依赖,形成闭环,比如A依赖B,B依赖A;或者A依赖B,B依赖C,C依赖A。
2. 可解决的循环依赖:仅单例Bean的setter注入、字段注入(@Autowired)、@Lazy配合构造器注入这三类场景。Spring通过缓存提前暴露未完全初始化的单例Bean,打破循环创建的死锁。
3. 无法解决的循环依赖:
- 构造器注入的循环依赖:Spring在实例化Bean时需要先调用构造器,构造器依赖的Bean未创建完成,导致死锁,抛出BeanCurrentlyInCreationException;
- 原型Bean的循环依赖:原型Bean每次调用getBean()都会创建新对象,没有缓存机制,导致递归创建最终栈溢出;
- 多例Bean的循环依赖:与原型Bean逻辑一致,容器不缓存多例Bean,无法提前暴露对象。
鳄鱼java社区的实战案例显示:某电商项目误将订单服务的构造器注入改为字段注入,解决了困扰一周的循环依赖问题,这就是对可解决场景理解不足导致的生产故障。
三、核心原理:三级缓存解决循环依赖的底层逻辑
Spring解决单例Bean的setter/字段注入循环依赖,核心是通过三级缓存机制提前暴露未完全初始化的对象,同时保证AOP代理的一致性。三级缓存的结构与作用如下(核心逻辑在DefaultSingletonBeanRegistry类中):
1. singletonObjects(一级缓存:单例池):存储完全初始化完成的单例Bean,即可以直接被业务代码使用的Bean。
2. earlySingletonObjects(二级缓存:早期对象缓存):存储未完全初始化的单例Bean(已实例化但未完成属性赋值、初始化),用于解决循环依赖时的对象共享。
3. singletonFactories(三级缓存:对象工厂缓存):存储ObjectFactory对象工厂,用于在需要时创建Bean的代理对象(如果Bean需要AOP增强)。这是三级缓存的核心设计,解决了AOP代理与循环依赖的一致性问题。
循环依赖的解决流程(以A和B循环依赖为例):
- Spring容器启动,开始创建单例Bean A;
- A实例化完成(调用构造器),但未完成属性赋值与初始化,Spring将A的对象工厂放入
singletonFactories; - 开始为A的属性赋值,发现依赖Bean B,转而创建Bean B;
- B实例化完成,同样将对象工厂放入
singletonFactories; - 为B的属性赋值,发现依赖Bean A,Spring尝试从一级缓存获取A,未找到;从二级缓存获取,未找到;从三级缓存获取A的对象工厂,调用工厂方法创建A的实例(若A需要AOP代理,此时会生成代理对象),将实例放入
earlySingletonObjects,并返回给B; - B完成属性赋值与初始化,放入
singletonObjects; - 回到A的属性赋值,从
singletonObjects获取B,完成A的属性赋值与初始化; - 将A放入
singletonObjects,并移除earlySingletonObjects和singletonFactories中A的缓存。
四、关键疑问:为什么需要三级缓存?二级缓存不行吗?
这是面试中必问的延伸问题,核心原因是保证AOP代理对象的一致性:
如果只有两级缓存(singletonObjects和earlySingletonObjects),Spring会在实例化Bean时提前创建代理对象并放入earlySingletonObjects,但这样会导致两种问题:
1. 不必要的代理创建:如果Bean不存在循环依赖,提前创建代理对象会浪费资源,违背Spring“延迟增强”的设计原则;
2. 代理对象不一致:如果Bean不需要循环依赖,Spring会在初始化后置处理(BeanPostProcessor的postProcessAfterInitialization)阶段创建代理对象,与循环依赖时提前创建的代理对象可能不一致(比如代理逻辑有上下文差异)。
三级缓存的singletonFactories存储的是对象工厂,只有当真正发生循环依赖时,才会调用工厂方法创建代理对象,既解决了循环依赖,又保证了代理对象的一致性,同时避免了不必要的资源消耗。鳄鱼java社区的源码分析显示,三级缓存的设计让AOP代理与循环依赖的兼容性提升了100%。
五、面试应答技巧:满分模板与避坑指南
回答面试题:Spring 循环依赖怎么解决的时,要遵循“场景划分→三级缓存原理→边界局限→优化建议”的逻辑,示例应答:
“面试官您好,Spring解决循环依赖需要分场景看待:
1. 可解决场景:单例Bean的setter/字段注入循环依赖,Spring通过三级缓存机制解决;
2. 三级缓存原理:singletonFactories存储对象工厂,当发生循环依赖时,提前创建代理对象放入earlySingletonObjects,保证代理一致性;singletonObjects存储完全初始化的Bean;
3. 无法解决的场景:构造器注入、原型Bean的循环依赖,因为前者无法提前暴露对象,后者没有缓存机制;
4. 优化建议:尽量用字段注入替代构造器注入,或者用@Lazy延迟加载解决构造器循环依赖,也可以通过拆分服务、依赖倒置原则避免循环依赖。我在鳄鱼java的Spring实战项目中,通过@Lazy配合构造器注入,解决了权限服务与用户服务的循环依赖问题。”
避坑指南: - 不要说“三级缓存是为了提升性能”,核心是解决AOP代理的一致性; - 不要说“所有循环依赖都能解决”,必须
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





