什么是Spring循环依赖?如何用三级缓存解决Bean依赖问题?

 更新时间:2026年08月04日 10:15:19   作者:陵易居士  
还在为Spring Bean初始化时的循环依赖头疼吗?本文深入浅出地讲解Spring如何通过三级缓存机制解决循环依赖,尤其是AOP代理下的处理方式,帮你彻底理解单例池、早期对象和工厂方法的核心原理

什么是循环依赖?

循环依赖就是Spring在初始化Bean时两个不同的Bean你依赖我,我依赖你的情况

例如A依赖B,B依赖A,当IoC容器初始化A时,发现它依赖于B,然后去创建B,创建B的时候又发现B依赖于A,而容器中不存在A,如果不想办法解决,这就会陷入死循环

例如下面这个代码,就是典型的循环依赖

// Spring 配置类,启用组件扫描
@Configuration
@ComponentScan
class AppConfig {
}

// Author 服务类,依赖 BookService
@Service
class AuthorService {
    @Autowired
    BookService bookService;
}

// Book 服务类,依赖 AuthorService
@Service
class BookService {
    @Autowired
    AuthorService authorService;
}

// 程序入口类,测试循环依赖注入
public class SpringCircularDependencySingleClass {
    public static void main(String[] args) {
        ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        BookService bookService = (BookService) annotationConfigApplicationContext.getBean("bookService");
        System.out.println(bookService.authorService);

        AuthorService authorService = (AuthorService) annotationConfigApplicationContext.getBean("authorService");
        System.out.println(authorService.bookService);
    }
}    

运行这个项目,会发现它是正常运行的,这就表示Spring在背后做了一些工作解决了循环依赖的问题

如何解决?

Spring是使用三级缓存的模式来解决循环依赖问题的,其实两级缓存也是能够解决一般的循环依赖问题的,三级缓存主要是为了更好的解决带有AOP的Bean的循环依赖情况

这里先用两级缓存结构来初步阐述思想

采用两级缓存解决

Spring解决循环依赖的关键在于巧妙利用缓存机制。在Spring的实现中,主要涉及两个重要的Map:

  • singletonObjects:这是一个单例池,存放的是经历了完整Spring生命周期的Bean实例。这些Bean的所有依赖都已成功填充,处于完全可用状态。
  • earlySingletonObjects:该Map用于存放提前暴露出来的Bean对象。这些Bean刚刚创建完成,但尚未经历完整的Spring生命周期,其依赖尚未填充完毕

假设此时有两个Bean分别叫A、B,它们互相依赖

解决循环依赖的具体步骤如下:

  • 1.先创建A的实例对象,在A的依赖注入之前,将A的早期对象放入earlySingletonObjects中去,然后开始给A注入依赖,发现依赖于B(此时两级缓存中都没有B),于是转去创建B
  • 2.创建B的实例对象,将B的早期对象放入earlySingletonObjects,然后给B注入依赖,发现B依赖于A,于是去缓存中查找符合条件的Bean
  • 3.先从singletonObjects中查找,发现没有,然后去earlySingletonObjects查找,发现存在A的早期对象,于是返回这个早期对象
  • 4.将A注入到B中,然后将B放入singletonObjects中去
  • 5.这个时候A可以从singletonObjects中获取B的实例,然后将完整的A放入singletonObjects中,循环依赖问题得以解决

需要AOP的Bean的循环依赖问题?

当一个Bean需要使用增强时,我们需要的是它的代理对象而不是它的原型对象,这个时候简单的两级缓存结构并不能很好的解决这个问题,所以Spring选择了三级缓存结构来解决

(两级缓存的解决思路是,在将早期对象放入earlySingletonObjects中前,先判断一下该对象是否需要AOP,如果需要的话生成该对象的代理对象放入earlySingletonObjects

但是!Spring的理念是尽量的把AOP的部分放到构造Bean的后期解决,而不是上来就直接生成一个代理对象,而且过早的生成代理对象也会额外造成空间和时间的消耗)

三级缓存解决

Spring引入了一个新的Map singletonFactories 来解决这个问题。

singletonFactories 存放的是 ObjectFactory 类型的工厂方法。

当创建完对象后,并不立即进行AOP增强,而是将获取该对象的工厂方法放入 singletonFactories 。

当发生循环依赖需要获取对象时,如果从 earlySingletonObjects 中无法获取到合适的对象,就从 singletonFactories 中取出工厂方法并执行,从而获取经过AOP增强后的对象。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 解决mybatis-plus3.1.1版本使用lambda表达式查询报错的方法

    解决mybatis-plus3.1.1版本使用lambda表达式查询报错的方法

    这篇文章主要介绍了解决mybatis-plus3.1.1版本使用lambda表达式查询报错的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • MyBatis-Plus实现逻辑删除的示例代码

    MyBatis-Plus实现逻辑删除的示例代码

    本文主要介绍了MyBatis-Plus实现逻辑删除的示例代码,就是通过逻辑判断的手段表示该条数据已删除,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • SpringBoot整合Redis使用RedisTemplate和StringRedisTemplate

    SpringBoot整合Redis使用RedisTemplate和StringRedisTemplate

    Spring Boot Data(数据) Redis 中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个方法基本一致。本文介绍了SpringBoot整合Redis使用RedisTemplate和StringRedisTemplate的方法,需要的可以参考一下
    2022-12-12
  • SpringBoot跨域Access-Control-Allow-Origin实现解析

    SpringBoot跨域Access-Control-Allow-Origin实现解析

    这篇文章主要介绍了SpringBoot跨域Access-Control-Allow-Origin实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • IDEA运行SpringBoot项目的超详细步骤截图

    IDEA运行SpringBoot项目的超详细步骤截图

    在当前的开发中Spring Boot开发框架已经成为主流,下面这篇文章主要给大家介绍了关于IDEA运行SpringBoot项目的超详细步骤截图,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-11-11
  • MybatisPlus自定义Sql实现多表查询的示例

    MybatisPlus自定义Sql实现多表查询的示例

    这篇文章主要介绍了MybatisPlus自定义Sql实现多表查询的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 详解JVM的内存对象介绍[创建和访问]

    详解JVM的内存对象介绍[创建和访问]

    这篇文章主要介绍了JVM的内存对象介绍[创建和访问],文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Prometheus pushgateway的使用详解

    Prometheus pushgateway的使用详解

    为了防止 pushgateway 重启或意外挂掉,导致数据丢失,我们可以通过 -persistence.file 和 -persistence.interval 参数将数据持久化下来,接下来通过本文给大家介绍下Prometheus pushgateway的使用,感兴趣的朋友一起看看吧
    2021-11-11
  • 基于SpringBoot服务端表单数据校验的实现方式

    基于SpringBoot服务端表单数据校验的实现方式

    这篇文章主要介绍了基于SpringBoot服务端表单数据校验的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Java从入门到起飞之变量与运算符详解

    Java从入门到起飞之变量与运算符详解

    这篇文章主要介绍了Java编程语言中的关键字、标识符、变量、基本数据类型以及运算符等基本概念和用法,它涵盖了变量声明、赋值、类型转换、字符串操作以及运算符优先级等内容,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-03-03

最新评论