2025年Java面试宝典最新版(含Spring全家桶源码剖析) 建议保存在自己网盘随时查看
Spring三级缓存如何化解循环依赖这个经典面试题?
大家面试时有没有被问到过这样的场景:BeanA的构造方法里调用了BeanB,而BeanB的构造方法又调用了BeanA,这种「鸡生蛋还是蛋生鸡」的问题在Spring中是怎么解决的?今天我们就来拆解这个高频面试题的解题思路。

一、先搞明白什么是循环依赖
循环依赖本质是对象间的网状引用关系,就像我们日常开发中常遇到的:
- 构造函数循环依赖(最致命)
- setter方法循环依赖
- 字段属性循环依赖
其中构造函数循环依赖是Spring无法解决的硬伤,其他两种循环依赖Spring都给出了优雅的解决方案。
二、三级缓存结构长什么样?
Spring的解决方案核心在于三级缓存机制:
- 一级缓存(成品池):存放完全初始化好的单例对象
- 二级缓存(半成品池):存放刚实例化但未初始化的原始对象
- 三级缓存(工厂池):存放生成代理对象的ObjectFactory
这三层缓存的配合就像流水线作业:
- 实例化对象后立即放入二级缓存
- 属性注入阶段从三级缓存获取代理对象
- 完全初始化后转移到一级缓存
三、解决流程分步拆解
- 首次获取BeanA:三级缓存都没有,开始创建流程
- 实例化BeanA:通过反射创建原始对象,放入二级缓存
- 填充BeanB属性:触发BeanB的创建流程
- 实例化BeanB:同样创建原始对象放入二级缓存
- BeanB填充BeanA:此时二级缓存已有BeanA的原始对象,直接注入
- BeanB初始化完成:移入一级缓存
- BeanA继续初始化:最终完成移入一级缓存
这个过程中如果存在AOP代理,三级缓存的ObjectFactory就会生成代理对象替换原始对象,保证最终注入的是增强后的对象。
四、高频疑问集中答疑
Q:为什么需要三级缓存而不是两级? 当存在AOP代理时,二级缓存直接存储原始对象会导致最终注入的对象与代理对象不一致,三级缓存的ObjectFactory动态生成代理对象保证一致性。
Q:原型Bean为什么不能解决循环依赖? 原型Bean每次都会新建实例,无法通过缓存复用,容易导致无限递归。
Q:构造器注入为何无解? 实例化阶段就需要完成依赖注入,但此时连原始对象都还没创建成功,缓存中没有任何记录可用。

五、实际开发中的避坑指南
- 避免在@PostConstruct方法中调用其他bean的方法
- 慎用@Async等会生成代理的注解
- 使用@Lazy延迟加载打破循环链
- 优先使用setter注入而非字段注入
最近帮粉丝review代码时发现一个典型case:用户服务中同时使用了@Cacheable和@Transactional,导致代理对象生成两次,最终注入的service并不是预期的增强对象。这种隐蔽的问题就需要对三级缓存机制有深入理解才能快速定位。
小提示:需要购买面试鸭会员的同学,通过面试鸭返利网找我下单可以返25元现金,相当于七五折优惠。已帮助上百位小伙伴省下奶茶钱,操作流程很简单:

理解三级缓存机制不仅是为了应付面试,更是排查复杂依赖问题的必备技能。建议大家结合Spring源码中DefaultSingletonBeanRegistry类的getSingleton()方法,对照着流程图多走几遍创建流程,下次面试被问到就能对答如流了。


