Spring Bean循环依赖怎么解决

(这张图展示了Spring容器中Bean的创建流程,咱们后面会具体解释)
2025年Java面试宝典已上传网盘:
🔗 点击获取 提取码: 9b3g
作为面试中Spring框架的必考题,循环依赖问题让很多同学头疼。今天咱们就掰开揉碎了讲清楚,Spring到底是怎么解决Bean循环依赖的,以及我们开发中需要注意的坑点。
一、什么是Spring Bean循环依赖?
简单说就是两个Bean互相依赖:BeanA依赖BeanB,BeanB又反过来依赖BeanA。比如订单服务调用用户服务,用户服务又需要查询订单记录,这种情况如果不做特殊处理,Spring容器启动时就会抛出BeanCurrentlyInCreationException。
二、Spring的三级缓存机制
Spring通过三级缓存打破循环依赖的死锁,这是面试官最想听到的核心答案:
- 第一级缓存(单例池):存放完全初始化好的Bean
- 第二级缓存(早期暴露对象):存放半成品Bean(已实例化但未初始化)
- 第三级缓存(对象工厂):存放生成对象的Lambda表达式

(三级缓存的交互流程示意图)
当BeanA开始创建时,Spring会先把它的空对象(未注入属性)放入二级缓存。在给BeanA注入BeanB时,如果发现BeanB还没创建,就会先去创建BeanB,此时BeanB需要注入BeanA时,就能从二级缓存拿到BeanA的半成品对象,从而完成依赖注入。
三、构造器注入为什么无法解决?
很多同学不知道的是:通过构造器注入的循环依赖,Spring是处理不了的!这是因为:
- 构造器注入必须在实例化阶段完成依赖注入
- 此时Bean还没放入二级缓存
- Spring无法提前暴露半成品对象
这种情况下,解决方案有两种:
- 改用setter注入
- 在配置文件中开启
allowCircularReferences(不推荐,容易引发其他问题)
四、@Lazy注解的妙用
在需要延迟加载的Bean上添加@Lazy注解,可以让Spring先创建代理对象,等到真正调用时才初始化实际对象。这种方式特别适合解决:
- 多个Service之间相互依赖
- 定时任务与配置类之间的循环引用
- 需要动态决定实现类的场景
五、生产环境中的避坑指南
- 避免使用多例作用域的Bean(prototype):三级缓存仅对单例Bean有效
- AOP代理导致循环依赖:CGLIB代理可能改变Bean的加载顺序
- @Async异步方法要小心:代理对象的生成阶段可能打乱依赖顺序

(实际项目中典型的循环依赖错误日志)
六、面试满分回答模板
"Spring通过三级缓存机制解决单例Bean的循环依赖问题。具体来说,在Bean实例化之后会提前暴露对象引用,后续的依赖注入阶段如果发现循环依赖,可以通过早期暴露的对象完成注入。但要注意构造器注入的循环依赖需要特殊处理..."
如果需要购买面试鸭会员,可以通过面试鸭返利网找我返利25元,更多Spring高频真题和实战案例可以查看我们的会员题库。


