spring循环依赖及解决方式

spring循环依赖

Spring中的循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环,导致Spring容器无法正确初始化这些Bean。

循环依赖通常发生在以下场景:

  • 构造函数循环依赖:两个Bean在构造函数中相互依赖。
  • 字段注入循环依赖:两个Bean通过@Autowired或@Resource相互注入。
  • 方法注入循环依赖:两个Bean通过@Bean方法相互依赖。
@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

这里,A依赖B,B又依赖A,形成了循环依赖。

spring循环依赖的解决方式

Spring通过三级缓存机制部分解决了循环依赖问题,但仅限于字段注入和Setter注入,构造函数注入无法解决。

Spring三级缓存具体的实现类是:DefaultListableBeanFactory

Spring的三级缓存机制

  • 一级缓存(Singleton Objects):存放完全初始化好的Bean。
  • 二级缓存(Early Singleton Objects):存放提前暴露的Bean(尚未完成属性注入)。
  • 三级缓存(Singleton Factories):存放Bean的工厂对象,用于生成提前暴露的Bean。
数据结构的源码如下:
// 一级缓存:存放完全初始化好的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存放早期暴露的Bean(未填充属性)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:存放Bean工厂(用于生成早期引用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
解决过程示例:
  1. 当Spring初始化A时,会先将A的工厂对象放入三级缓存。
  2. 在A的属性注入阶段,发现需要注入B,于是开始初始化B。
  3. 在初始化B时,发现需要注入A,此时Spring会从三级缓存中获取A的工厂对象,生成一个提前暴露的A对象(未完成属性注入),并将其放入二级缓存。
  4. B完成初始化后,A继续完成属性注入,最终A和B都初始化完成。
sequenceDiagram
    participant Container
    participant CacheA
    participant CacheB

    Container->>CacheA: 开始创建A
    Container->>CacheA: 实例化A(未设属性)
    Container->>CacheA: 将A的ObjectFactory放入三级缓存
    Container->>CacheA: 开始注入A的属性B
    Container->>CacheB: 开始创建B
    Container->>CacheB: 实例化B(未设属性)
    Container->>CacheB: 将B的ObjectFactory放入三级缓存
    Container->>CacheB: 开始注入B的属性A
    Container->>CacheA: 发现一级缓存里没有,二级缓存里也没有,就取三级缓存里的ObjectFactory,获取早期引用,并放入二级缓存里并返回
    Container->>CacheB: 将A的早期引用注入B
    Container->>CacheB: 完成B的初始化
    Container->>CacheA: 将初始化好的B注入A
    Container->>CacheA: 完成A的初始化
关键的源码解析

获取Bean的核心入口:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 检查一级缓存
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2. 检查二级缓存
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 3. 检查三级缓存
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                singletonObject = singletonFactory.getObject(); // 触发工厂创建早期引用
                this.earlySingletonObjects.put(beanName, singletonObject);
                this.singletonFactories.remove(beanName);
            }
        }
    }
    return singletonObject;
}

暴露早期引用:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 实例化(此时对象还未填充属性)
    Object beanInstance = instantiateBean(beanName, mbd);
    
    // 2. 将ObjectFactory加入三级缓存(关键步骤!)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));

    // 3. 属性填充(可能触发循环依赖)
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    return exposedObject;
}
以下场景的循环依赖无法解决:
  1. 构造器循环依赖:Spring无法通过三级缓存解决构造函数循环依赖,因为在构造函数中需要完整的Bean实例,而提前暴露的Bean尚未完成初始化。
  2. Prototype作用域的循环依赖:Spring不缓存prototype Bean,每次请求都新建实例。
  3. @Async代理的循环依赖:代理对象需要在初始化后生成,与早期引用冲突。

我们需要避免循环依赖

为什么需要三级缓存?二级不行吗?

三级缓存的核心价值在于处理AOP代理。如果只有二级缓存:

  1. 普通Bean可以直接存半成品到二级缓存
  2. 但AOP代理需要在初始化后生成,必须通过ObjectFactory延迟判断是否需要代理
Spring如何检测循环依赖?

通过isSingletonCurrentlyInCreation(beanName)检查当前Bean是否正在创建中:

// DefaultSingletonBeanRegistry
public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.get().contains(beanName);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值