Spring循环依赖​​的解决方式详解

 更新时间:2025年05月10日 11:48:48   作者:二进制11  
这篇文章主要介绍了Spring循环依赖​​的解决方式,​​循环依赖​​是指两个或多个Bean互相依赖,形成闭环,导致Spring无法正常完成依赖注入,需要的朋友可以参考下

什么是循环依赖

循环依赖是指两个或多个Bean相互依赖,形成一个闭环。例如:

@Component
public class A {
    @Autowired
    private B b;
}
@Component
public class B {
    @Autowired
    private A a;
}

在这个例子中,A依赖B,B又依赖A,形成了一个循环依赖链。

Spring解决循环依赖的机制

Spring通过三级缓存机制来解决循环依赖问题,具体实现如下:

三级缓存结构

  • 一级缓存(singletonObjects):存储完全初始化好的Bean
  • 二级缓存(earlySingletonObjects):存储提前暴露的原始Bean(尚未填充属性)
  • 三级缓存(singletonFactories):存储Bean工厂,用于生成原始Bean的早期引用

解决流程

Spring解决循环依赖的核心流程如下:

详细步骤解析

创建Bean A:

  • Spring容器开始创建Bean A
  • 实例化A(调用构造方法)
  • 将A的ObjectFactory放入三级缓存(singletonFactories)
  • 准备填充A的属性

发现依赖B:

  • 在填充A的属性时,发现需要注入Bean B
  • 容器开始创建Bean B

创建Bean B:

  • 实例化B(调用构造方法)
  • 将B的ObjectFactory放入三级缓存(singletonFactories)
  • 准备填充B的属性

发现依赖A:

  • 在填充B的属性时,发现需要注入Bean A
  • 容器尝试从一级缓存获取A(未找到)
  • 从二级缓存获取A(未找到)
  • 从三级缓存获取A的ObjectFactory并调用getObject()获取早期引用
  • 将A的早期引用放入二级缓存,并从三级缓存移除

完成B的创建:

  • 将B的属性填充完整
  • 执行B的初始化后处理器
  • 将完整的B放入一级缓存
  • 从二级和三级缓存中移除B的相关条目

完成A的创建:

  • 现在A可以获取到完整的B实例
  • 将A的属性填充完整
  • 执行A的初始化后处理器
  • 将完整的A放入一级缓存
  • 从二级缓存中移除A

代码层面的实现

Spring解决循环依赖的核心代码在DefaultSingletonBeanRegistry类中:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 首先检查一级缓存
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 检查二级缓存
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 检查三级缓存
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 从三级缓存移到二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

循环依赖的限制

Spring并非能解决所有类型的循环依赖,有以下限制:

构造器注入:无法解决构造器注入的循环依赖

@Component
public class A {
    private B b;
    @Autowired
    public A(B b) { this.b = b; }
}
@Component
public class B {
    private A a;
    @Autowired
    public B(A a) { this.a = a; }
}

这种情况会抛出BeanCurrentlyInCreationException

原型(prototype)作用域的Bean:Spring不支持原型Bean的循环依赖

@Async方法:如果循环依赖中包含@Async方法,也可能出现问题

最佳实践

  • 尽量避免循环依赖,设计时应考虑解耦
  • 如果必须使用循环依赖,优先使用setter注入而非构造器注入
  • 考虑使用@Lazy注解延迟加载其中一个Bean
@Component
public class A {
    @Lazy
    @Autowired
    private B b;
}

总结

Spring通过三级缓存机制巧妙地解决了setter/field注入方式的循环依赖问题。理解这一机制不仅有助于我们避免开发中的循环依赖陷阱,也能更深入地理解Spring容器的Bean生命周期管理。在实际开发中,我们应当合理设计Bean之间的依赖关系,尽量避免循环依赖,当确实需要时,也要了解其背后的原理和限制。

以上就是Spring循环依赖​​的解决方式详解的详细内容,更多关于Spring循环依赖的资料请关注脚本之家其它相关文章!

相关文章

最新评论