Spring解决循环依赖的详细解析及代码分析

Spring框架在处理Bean的循环依赖时,主要针对单例作用域(Singleton)的Bean,并且是在通过setter方法(即基于property的依赖注入)进行依赖注入时能够解决循环依赖问题。

对于构造器注入的循环依赖,Spring是无法解决的,会抛出BeanCurrentlyInCreationException异常。

下面是对Spring解决循环依赖的详细解析及代码层面的简要分析。

解决循环依赖的核心机制

Spring解决循环依赖的核心在于它的“三级缓存”机制和提前曝光(Early Reference)的概念。这三级缓存分别是:

  1. singletonObjects:第一级缓存,用于存放完全初始化好的单例Bean对象。
  2. earlySingletonObjects:第二级缓存,用于存放提前曝光的单例Bean的实例(也就是未完成全部初始化过程的Bean实例)。
  3. singletonFactories:第三级缓存,存放的是用于创建单例Bean的工厂对象,这个工厂对象可以在真正需要Bean实例时生产出一个早期的、未完成初始化的Bean实例。

解析过程

  1. Bean创建过程:当Spring容器尝试创建Bean A时,首先会检查singletonObjects(第一级缓存)中是否有已经创建好的A,如果没有则继续。

  2. 进入创建流程:Spring开始创建Bean A,首先进行实例化(调用构造函数),然后将实例化后的Bean A的早期引用(未完成依赖注入的状态)放入singletonFactories(第三级缓存)。

  3. 依赖注入:在为Bean A注入依赖时,发现需要Bean B,于是Spring尝试从singletonObjects获取B。如果B还未创建,同样流程创建B,B的创建过程中发现依赖A,此时Spring会检查singletonFactories

  4. 解决循环依赖:由于之前A的早期引用已经被放入singletonFactories,Spring此时可以从这个缓存中取出A的早期实例,而不是重新创建,然后将这个早期实例注入到正在创建的B中。这个过程实际上是一个“提前曝光”的概念,允许依赖注入继续进行,而不会陷入死循环。

  5. 完成初始化:随着B的依赖注入完成,B被完全初始化并放入singletonObjects。接下来,A可以继续完成其剩余的初始化过程(包括属性注入等),最后也被放入singletonObjects

代码层面分析

在Spring源码中,这个过程涉及到DefaultSingletonBeanRegistry类中的缓存管理和AbstractAutowireCapableBeanFactory类中的Bean创建逻辑。

特别是getSingleton(String beanName, boolean allowEarlyReference)方法和populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw)方法,这些地方体现了如何检查和使用三级缓存以及如何处理循环依赖的细节。

为了更直观地理解Spring如何通过三级缓存解决循环依赖,我们可以参考Spring框架中几个关键类的部分核心代码逻辑。请注意,以下代码是简化版的逻辑描述,旨在帮助理解,实际Spring源码结构复杂得多,涉及众多类与方法的交互。

1. DefaultSingletonBeanRegistry 类中的缓存管理

// 第一级缓存:完全初始化的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(64);

// 第二级缓存:提前曝光的单例Bean实例
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 第三级缓存:用于创建单例Bean的工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2. AbstractAutowireCapableBeanFactory 类中的Bean创建与循环依赖处理

实例化Bean并加入三级缓存
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // ... 省略实例化之前的准备工作 ...
    
    // 实例化Bean
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    
    // 如果是单例且允许提前曝光,则将Bean的早期实例加入到singletonFactories中
    if (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)) {
        if (logger.isDebugEnabled()) {
            logger.debug("Detected circular reference to bean '" + beanName + "' while finishing " +
                    "bean creation for bean '" + currentlyCreatedBeanName + "'");
        }
        // 将beanInstance包装成ObjectFactory并放入singletonFactories
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
    }
    
    // ... 省略后续的属性填充等操作 ...
}
解决循环依赖时获取Bean实例
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = singletonObjects.get(beanName);
    
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 尝试从singletonFactories中获取或创建Bean的早期实例
        singletonObject = getSingletonFromFactoryBean(beanName, allowEarlyReference);
    }
    
    return singletonObject;
}

private Object getSingletonFromFactoryBean(String beanName, boolean allowEarlyReference) {
    ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
    if (singletonFactory != null) {
        // 从singletonFactory创建早期Bean实例,并尝试移入earlySingletonObjects
        Object singletonObject = singletonFactory.getObject();
        // 如果成功创建,从singletonFactories中移除,并可能加入到earlySingletonObjects或完全初始化后加入singletonObjects
        singletonFactories.remove(beanName);
        earlySingletonObjects.put(beanName, singletonObject);
        // ... 后续可能的完全初始化逻辑 ...
        return singletonObject;
    }
    return null;
}

说明

上述代码片段展示了Spring如何在实例化Bean时将其早期引用(未完全初始化)存入singletonFactories,并在依赖注入时通过这个三级缓存来解决循环依赖。

当依赖于某个正在创建中的Bean时,它能够提供一个未完成初始化的实例,从而打破了循环依赖的死锁,之后再逐步完成所有Bean的完整初始化过程。

注意事项

  • 非单例(Prototype)的Bean:对于非单例模式的Bean,Spring不处理循环依赖,因为每次请求都会创建新的实例。
  • 构造器注入的循环依赖:构造器注入模式下,Spring无法解决循环依赖,因为必须先构造完整才能注入依赖。
  • @Lazy注解:使用@Lazy注解可以间接解决循环依赖问题,因为它使得Bean的初始化延迟到第一次实际使用时,但这种方式改变了Bean的初始化时机。

综上,Spring通过其精心设计的三级缓存机制和提前曝光策略,在一定程度上解决了单例Bean之间的循环依赖问题,但仍需注意设计上的最佳实践,避免不必要的循环依赖出现。

  • 33
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值