深入剖析Spring如何解决循环依赖

 更新时间:2025年04月04日 08:15:59   作者:北辰alk  
循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环的情况,本文将和大家深入探讨一下Spring如何解决循环依赖,需要的可以参考下

一、什么是循环依赖

循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环的情况。例如:

@Service
public class AService {
    @Autowired
    private BService bService;
}

@Service
public class BService {
    @Autowired
    private AService aService;
}

上述代码中,AService依赖BService,而BService又依赖AService,形成了循环依赖。

二、Spring循环依赖的三种情况

构造器循环依赖:通过构造器注入形成的循环依赖,Spring无法解决,会直接抛出BeanCurrentlyInCreationException

Setter循环依赖(单例模式):通过setter方法注入形成的循环依赖,Spring可以解决
原型模式(prototype)循环依赖:scope为prototype的bean形成的循环依赖,Spring无法解决

三、Spring解决循环依赖的核心思想

Spring通过三级缓存机制来解决单例模式下的Setter循环依赖问题。核心思想是:

提前暴露未完全初始化的Bean实例:在Bean创建过程中,在完成实例化后、初始化前,将Bean的引用提前暴露

分级缓存:使用三级缓存存储不同状态的Bean,确保依赖注入时能获取到正确的引用

四、三级缓存详解

1. 三级缓存结构

/** 一级缓存:存放完全初始化好的Bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 二级缓存:存放原始的Bean对象(尚未填充属性) */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** 三级缓存:存放Bean工厂对象,用于生成原始Bean对象 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2. Bean创建与三级缓存交互流程

1.创建Bean实例:通过反射调用构造器创建Bean实例

2.放入三级缓存:将Bean包装成ObjectFactory并放入三级缓存singletonFactories

3.属性填充:填充Bean的属性,此时如果发现依赖其他Bean

  • 从一级缓存singletonObjects获取
  • 如果不存在,尝试从二级缓存earlySingletonObjects获取
  • 如果还不存在,从三级缓存singletonFactories获取ObjectFactory并生成Bean,然后放入二级缓存

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

5.放入一级缓存:将完全初始化好的Bean放入一级缓存,删除二、三级缓存中的记录

3. 循环依赖解决示例

以AService和BService的循环依赖为例:

1.开始创建AService

  • 实例化AService
  • 将AService的ObjectFactory放入三级缓存

2.填充AService属性时发现需要BService

  • 开始创建BService
  • 实例化BService
  • 将BService的ObjectFactory放入三级缓存

3.填充BService属性时发现需要AService

  • 从一级缓存获取AService → 不存在
  • 从二级缓存获取AService → 不存在
  • 从三级缓存获取AService的ObjectFactory → 存在,生成AService早期引用并放入二级缓存
  • 将AService早期引用注入BService

4.BService继续完成属性填充和初始化

5.BService创建完成,放入一级缓存

6.AService获得完全初始化的BService引用

7.AService继续完成属性填充和初始化

8.AService创建完成,放入一级缓存

五、源码分析

关键源码在DefaultSingletonBeanRegistry类中:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 2. 从二级缓存获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 3. 从三级缓存获取ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 将三级缓存提升到二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

六、为什么构造器循环依赖无法解决

时序问题:构造器注入发生在实例化阶段,此时Bean尚未创建完成,无法提前暴露引用

缓存机制不适用:三级缓存机制依赖于在实例化后、初始化前暴露引用,而构造器注入时连实例都还没完全创建

七、为什么原型模式的循环依赖无法解决

生命周期不同:原型模式每次获取都创建新实例,Spring不缓存原型Bean

无法提前暴露引用:没有缓存机制支持,无法在创建过程中共享未完成初始化的引用

八、Spring解决循环依赖的局限性

只能解决单例模式的Setter注入循环依赖

大量使用循环依赖会导致代码结构混乱,增加维护难度

某些AOP场景下可能出现问题(需要特殊处理)

九、最佳实践

避免循环依赖:通过设计模式(如中介者模式、观察者模式)重构代码

使用Setter注入替代构造器注入:如果必须使用循环依赖

使用@Lazy注解:延迟加载依赖的Bean

@Service
public class AService {
    @Autowired
    @Lazy
    private BService bService;
}

使用ApplicationContextAware接口:手动获取依赖Bean

十、总结

Spring通过三级缓存机制巧妙地解决了单例模式下Setter注入的循环依赖问题,但其本质上是一种妥协方案。良好的系统设计应当尽量避免循环依赖,保持清晰的依赖关系。理解Spring解决循环依赖的机制,有助于我们更好地使用Spring框架,并在遇到相关问题时能够快速定位和解决。

到此这篇关于深入剖析Spring如何解决循环依赖的文章就介绍到这了,更多相关Spring解决循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Java实现的k-means聚类算法

    详解Java实现的k-means聚类算法

    这篇文章主要介绍了详解Java实现的k-means聚类算法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • 深入了解Java行为型设计模式之策略模式

    深入了解Java行为型设计模式之策略模式

    策略模式属于Java-设计模式中行为模式之一,该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。本文将通过示例详细讲解这一模式,需要的可以参考一下
    2022-09-09
  • Java拦截器Interceptor和过滤器Filte的执行顺序和区别

    Java拦截器Interceptor和过滤器Filte的执行顺序和区别

    本文主要介绍了Java拦截器Interceptor和过滤器Filte的执行顺序和区别,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • IntelliJ IDEA使用git初始化仓库的使用方法

    IntelliJ IDEA使用git初始化仓库的使用方法

    这篇文章主要介绍了IntelliJ IDEA使用git初始化仓库的使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • 快速理解Java设计模式中的组合模式

    快速理解Java设计模式中的组合模式

    这篇文章主要介绍了快速理解Java设计模式中的组合模式,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • 利用Java实体bean对象批量数据传输处理方案小结

    利用Java实体bean对象批量数据传输处理方案小结

    javabean是对面向对象思想的一种具体实施的表现,本文重点给大家介绍利用Java实体bean对象批量数据传输处理方案小结,本文通过两种方案给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2021-05-05
  • Java五种方式实现多线程循环打印问题

    Java五种方式实现多线程循环打印问题

    本文主要介绍了Java五种方式实现多线程循环打印问题,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • Mybatis通过拦截器实现单数据源内多数据库切换

    Mybatis通过拦截器实现单数据源内多数据库切换

    这篇文章主要为大家详细介绍了Mybatis如何通过拦截器实现单数据源内多数据库切换,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-12-12
  • springboot json时间格式化处理的方法

    springboot json时间格式化处理的方法

    这篇文章主要介绍了springboot json时间格式化处理的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • 解析Java实现设计模式六大原则之里氏替换原则

    解析Java实现设计模式六大原则之里氏替换原则

    里氏替换原则是用来帮助我们在继承关系中进行父子类的设计。它阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。它是继承复用的基础,反映了基类与子类之间的关系,是对开闭原则的补充,对实现抽象化具体步骤的规范
    2021-06-06

最新评论