首页 >文档 > spring循环依赖解决方法及原理

spring循环依赖解决方法及原理

Spring循环依赖是开发中常见问题,当两个Bean相互依赖时会形成闭环导致启动失败。Spring通过三级缓存机制解决循环依赖:一级缓存存储完整Bean,二级缓存存储半成品Bean,三级缓存存储Bean工厂对象。核心原理是提前暴露半成品Bean,打破依赖死锁。但构造器注入、原型Bean和@Async代理场景无法解决。实际开发建议使用Setter注入、代码重构或@Lazy延迟加载来避免循环依赖。理解三级缓存机制能帮助开发者快速定位Bean创建异常,提升系统稳定性。

Spring循环依赖解决方法及原理

💡 2025年Java面试宝典重磅分享
🔗 百度网盘链接 提取码: 9b3g (建议保存备用)


什么是Spring循环依赖?

当我们说 Spring循环依赖,本质上是指两个或多个Bean相互依赖,形成闭环。比如Bean A依赖Bean B,而Bean B反过来又依赖Bean A。这种场景在项目开发中其实挺常见的,尤其是在业务模块划分不够清晰时。

举个面试常问的例子:

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;  // A依赖B
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;  // B依赖A
}

这时候启动Spring容器,系统直接抛BeanCurrentlyInCreationException错误!那Spring到底怎么破这个局?核心秘密就在三级缓存机制里。


Spring三级缓存如何解决循环依赖?

先看Spring容器的三个核心缓存结构:

  1. singletonObjects(一级缓存):存完全初始化的Bean
  2. earlySingletonObjects(二级缓存):存半成品Bean(已实例化但未初始化)
  3. singletonFactories(三级缓存):存Bean的工厂对象(用于生成半成品Bean)

🎯 解决流程拆解(以A→B→A为例):

  1. 创建A实例(通过反射),此时A还是原始对象(未注入属性)
  2. 把A的工厂对象(ObjectFactory)放入三级缓存(暴露半成品A)
  3. 开始给A注入属性,发现需要B → 触发B的创建
  4. 创建B实例,同样把B工厂放入三级缓存
  5. B注入属性时发现需要A → 去三级缓存找到A工厂 → 调用getObject()得到A的代理对象(半成品)
  6. B完成初始化,放入一级缓存
  7. A拿到完整的B对象,完成自身初始化
  8. A移入一级缓存,清理二三级缓存

Spring三级缓存流程图

🔥 关键点
Spring通过提前暴露半成品Bean(三级缓存),打破了循环依赖的死锁。就像两人打架各退一步,先给对方一个"临时版本"用着。


哪些场景无法解决循环依赖?

三级缓存虽强,但也有硬伤:

  1. 构造器注入循环依赖(直接报错)

    public class A {
        public A(B b) { ... }  // 构造器依赖B
    }
    

    原因:必须拿到B才能实例化A,但B也需要A... 死锁无解!

  2. 原型Bean(prototype)的循环依赖
    每次都要新实例化,无法用缓存机制兜底

  3. @Async代理对象循环依赖
    动态代理机制导致三级缓存拿到的对象和最终对象不一致


开发中如何避免循环依赖?

虽然Spring提供了解决方案,但实际开发中还是建议:

  1. 重构代码结构
    使用事件驱动(ApplicationEvent)或中介者模式解耦

  2. 优先用Setter/Field注入
    避免构造器注入导致的硬死锁

  3. 模块化拆分
    @Lazy延迟加载非核心依赖

    @Autowired @Lazy 
    private ServiceB serviceB;  // 暂时不初始化
    

进阶思考:为什么要用三级缓存?

你可能疑惑:二级缓存不够用吗?来看个特殊场景:

@Service
public class UserService {
    @Autowired
    private RoleService roleService; // 依赖角色服务
}

@Service
public class LogService {
    @Autowired
    private UserService userService; // 依赖用户服务
}

@Service
public class RoleService {
    @Autowired
    private UserService userService; // 依赖用户服务
}

此时存在多个Bean依赖同一个半成品UserService。如果只有二级缓存:

  • 第一次LogService取UserService → 工厂创建代理对象A
  • 第二次RoleService取UserService → 工厂再创建代理对象B
  • 导致同一个Bean产生不同代理对象 → 系统崩溃!

三级缓存通过工厂模式保证:多次获取半成品Bean时,返回的是同一个代理对象,避免对象不一致问题。


💡 面试应答技巧

当面试官问Spring循环依赖解决方案时,分三步回答:

  1. 定性问题:"Spring通过三级缓存机制解决Setter/Field注入的循环依赖"
  2. 画流程图:口述三级缓存交互过程(结合A→B→A例子)
  3. 强调边界:"构造器注入、原型Bean、AOP代理的特殊场景无法解决"

📢 会员福利提示
如果需要开通「面试鸭」会员刷真题,记得通过 面试鸭返利网 下单!返利25元,真实有效(已帮200+程序员省钱):

面试鸭返利网返利活动


总结关键点

| 核心机制 | 作用 | 注意事项 | |----------------|----------------------------|----------------------------| | 三级缓存 | 暴露半成品Bean | 只对单例Bean有效 | | ObjectFactory | 延迟生成代理对象 | 解决AOP导致的对象不一致问题 | | 构造器注入 | 无法解决循环依赖 | 需改用Setter注入 | | @Lazy | 延迟初始化依赖 | 适用于非核心链式依赖 |

理解Spring循环依赖底层原理,不仅为了面试通关,更能帮你在实际开发中快速定位Bean创建异常。毕竟谁没在深夜被BeanCurrentlyInCreationException折磨过呢?🙃

如果你想获取更多关于面试鸭的优惠信息,可以访问面试鸭返利网面试鸭优惠网,了解最新的优惠活动和返利政策。

🎯 立即加入面试鸭会员 →

扫码联系我返利
(当前返利8元,金额随官方实际价格波动,最好提前咨询)

面试鸭返利网客服-面试鸭返利网

面试鸭小程序码

面试鸭小程序码 - 面试鸭返利网

美团大额优惠券,给自己加个鸡腿吧!

美团大额优惠券,给自己加个鸡腿吧!

今日有支付宝大红包赶快领,手慢无

支付宝红包二维码

支付宝扫码领取1-8元无门槛红包

支付宝红包二维码