Spring如何巧妙解决循环依赖问题?深入浅出解析三级缓存机制

一、什么是循环依赖?

循环依赖(Circular Dependency)就像两个程序员互相等待对方提交代码的场景:A说"我的代码要调用B的类",B说"但我的类需要A的接口定义"。在Spring中具体表现为:

 

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

这两个服务类就像"先有鸡还是先有蛋"的问题,传统的对象创建方式根本无法解决这种相互依赖关系。但Spring通过巧妙的三级缓存设计,让这个看似无解的问题迎刃而解。

二、Spring Bean生命周期关键节点

要理解解决方案,先要掌握Bean创建的核心步骤:

  1. 实例化(Instantiate):new ServiceA() 创建原始对象

  2. 属性填充(Populate):通过反射注入依赖

  3. 初始化(Initialize):执行@PostConstruct等方法

三、三级缓存机制全景解析

Spring在DefaultSingletonBeanRegistry中维护了三个关键缓存:

缓存名称存储内容级别
singletonObjects完整的单例Bean一级
earlySingletonObjects提前暴露的早期引用二级
singletonFactories创建Bean的ObjectFactory工厂对象三级

处理流程详解(以ServiceA和ServiceB为例)

  1. 创建ServiceA

    • 实例化ServiceA原始对象

    • 将ObjectFactory存入三级缓存(singletonFactories)

    • 开始属性注入,发现需要ServiceB

  2. 创建ServiceB

    • 实例化ServiceB原始对象

    • 将ObjectFactory存入三级缓存

    • 属性注入时发现需要ServiceA

  3. 获取ServiceA早期引用

    • 从三级缓存获取ObjectFactory

    • 执行getObject()得到ServiceA的早期引用(可能生成代理)

    • 将引用存入二级缓存,清除三级缓存记录

  4. 完成ServiceB创建

    • 继续完成ServiceB的属性注入和初始化

    • 将ServiceB存入一级缓存

  5. 回填ServiceA

    • 用已创建的ServiceB完成属性注入

    • 执行初始化方法

    • 将ServiceA存入一级缓存

四、源码级深度剖析

关键源码在AbstractAutowireCapableBeanFactory

protected Object doCreateBean(...) {
    // 1. 实例化Bean
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    // 2. 加入三级缓存(重要!)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    
    // 3. 属性注入
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化
    initializeBean(beanName, exposedObject, mbd);
}

缓存操作核心方法:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

五、不同注入方式的差异

注入方式是否支持解决循环依赖原因分析
Setter注入属性注入在对象实例化之后
字段注入同Setter注入
构造器注入实例化前就需要完成注入

构造器注入失败示例:

@Service
public class ServiceC {
    private final ServiceD serviceD;
    
    @Autowired
    public ServiceC(ServiceD serviceD) {
        this.serviceD = serviceD;
    }
}

@Service
public class ServiceD {
    private final ServiceC serviceC;
    
    @Autowired
    public ServiceD(ServiceC serviceC) {
        this.serviceC = serviceC;
    }
}

这种情况会直接抛出BeanCurrentlyInCreationException

六、应用场景与限制条件

适用场景

  • 单例作用域(Singleton)的Bean

  • 非构造器注入方式

  • 没有同时使用AOP代理的情况

限制条件

  1. 原型(Prototype)作用域的Bean无法解决

  2. 需要开启allowCircularReferences(默认true)

  3. 存在AOP代理时需要特殊处理(通过ObjectFactory延迟生成代理)

七、最佳实践建议

  1. 架构设计层面

    • 尽量避免循环依赖,使用中介者模式解耦

    • 将公共逻辑抽取到第三方的Common模块

  2. 编码实现层面

    • 优先使用构造器注入明确依赖关系

    • 对于必须的循环依赖使用@Lazy延迟加载

    • 复杂场景考虑使用ApplicationContext.getBean()

  3. 调试技巧

    • 开启Spring调试日志:logging.level.org.springframework=DEBUG

    • 分析Bean创建顺序:spring.main.lazy-initialization=true

    • 使用@DependsOn显式控制初始化顺序

八、常见问题解答

Q:为什么需要三级缓存而不是两级?

A:主要是为了处理AOP代理的情况。ObjectFactory可以延迟决定返回原始对象还是代理对象,保证最终注入的Bean类型正确。

Q:Spring如何检测循环依赖?

A:通过DefaultSingletonBeanRegistrysingletonsCurrentlyInCreation集合记录正在创建的Bean,当发现重复创建时抛出异常。

Q:多级循环依赖(如A->B->C->A)能解决吗?

A:可以,只要依赖链是setter/字段注入且都是单例Bean,三级缓存机制可以处理任意长度的循环链。

九、总结

Spring的三级缓存设计充分体现了"空间换时间"的智慧:

  1. 一级缓存:存储完整可用的成品Bean

  2. 二级缓存:临时保存半成品Bean的早期引用

  3. 三级缓存:通过ObjectFactory实现灵活的代理处理

通过这种分层缓存的机制,Spring既保证了单例Bean的唯一性,又巧妙地打破了循环依赖的死锁状态。理解这个机制不仅能帮助我们更好地使用Spring,更能够学习到框架设计中解决复杂问题的思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值