首页 > 其他分享 >一篇文章讲清楚Spring如何解决循环依赖,以及为什么需要三级缓存

一篇文章讲清楚Spring如何解决循环依赖,以及为什么需要三级缓存

时间:2024-10-10 11:18:17浏览次数:3  
标签:讲清楚 初始化 缓存 对象 Spring 代理 Bean

这是笔者从两道面试题出发的思考,如果有不对的地方,还请指正,仅供参考

Q:讲一讲spring 的循环依赖

循环依赖(Circular Dependency)指的是在对象之间互相依赖的情况。例如,Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A,形成了一个循环。Spring 框架中主要处理的是单例(singleton)作用域的循环依赖问题,而不能处理原型(prototype)作用域的循环依赖,也无法解决构造器注入的循环依赖。原因后面分析。

Spring 解决循环依赖的方法:三级缓存

  1. 一级缓存(singletonObjects):存放最终形态的 Bean(已经实例化、属性填充、初始化)。单例池,为“Spring 的单例属性”⽽⽣。一般情况我们获取 Bean 都是从这里获取的,但是并不是所有的 Bean 都在单例池里面,例如原型 Bean 就不在里面。
  2. 二级缓存(earlySingletonObjects):存放过渡 Bean(半成品,尚未属性填充),也就是三级缓存中ObjectFactory产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用ObjectFactory#getObject()都是会产生新的代理对象的。
  3. 三级缓存(singletonFactories):存放ObjectFactoryObjectFactorygetObject()方法(最终调用的是getEarlyBeanReference()方法)可以生成原始 Bean 对象或者代理对象(如果 Bean 被 AOP 切面代理)。三级缓存只会对单例 Bean 生效。

三级缓存如何解决循环依赖?

首先要清楚 Spring 创建 Bean 的流程:

  1. 先去 一级缓存 singletonObjects 中获取,存在就返回;

  2. 如果不存在或者对象正在创建中,于是去 二级缓存 earlySingletonObjects 中获取;

  3. 如果还没有获取到,就去 三级缓存 singletonFactories 中获取,通过执行 ObjectFacotrygetObject() 就可以获取该对象,并在获取成功之后将该对象加入到二级缓存中。

然后来看如何解决循环依赖:

当 Spring 创建 A 之后,发现 A 依赖了 B ,又去创建 B,B 依赖了 A ,又去创建 A;

在 B 创建 A 的时候,那么此时 A 就发生了循环依赖,由于 A 此时还没有初始化完成,因此在 一二级缓存 中肯定没有 A;

那么此时就去三级缓存中调用 getObject() 方法去获取 A 的 前期暴露的对象 ,也就是调用上边加入的 getEarlyBeanReference() 方法,生成一个 A 的 前期暴露对象

然后就将这个 ObjectFactory 从三级缓存中移除,并且将前期暴露对象放入到二级缓存中,那么 B 就将这个前期引用对象(虽然还没初始化完成,但是可以拿到该对象在堆中的存储地址)注入到依赖,来支持循环依赖。

然后B(或者,其代理对象)完成初始化后,进入一级缓存。

A再获取B,按照上述流程就会从一级缓存中获取初始化好的B,注入,完成A的初始化,进入一级缓存。

Q:为什么要有三级依赖?两级行不行?

三级缓存的引入主要用于处理AOP 动态代理对象的创建问题,即某些 Bean 在实例化之后可能需要生成一个代理对象。如果仅仅使用两级缓存,Spring 无法在实例化 Bean 后,动态地将代理对象暴露给其他依赖它的 Bean。

要理解这个问题,我们需要先明确两点:

  1. 代理对象的创建时机:在 Spring 中,某些 Bean 在创建后(即,实例化并初始化后)需要生成代理对象,比如通过 AOP 增强功能(如事务管理、方法拦截等)。

  2. Spring 创建(或者说,获取) Bean 的流程

  • 先去 一级缓存 singletonObjects 中获取,存在就返回;

  • 如果不存在或者对象正在创建中,于是去 二级缓存 earlySingletonObjects 中获取;

  • 如果还没有获取到,就去 三级缓存 singletonFactories 中获取,通过执行 ObjectFacotrygetObject() 就可以获取该对象,并在获取成功之后将该对象加入到二级缓存中。

下面是分析:

如果 A 和 B 只是普通的 Bean,没有涉及代理的问题(即不需要 AOP 之类的功能),那么只有两级缓存(即,第一和第三级缓存)是够用的:

  1. 由于最开始一级缓存没有A,所以创建A时候,会到第三级缓存拿到创建A的工厂,将A实例化,放入一级缓存
  2. 实例化A之后,Spring尝试初始化A。Spring在初始化阶段为A注入属性时,需要B,于是Spring开始创建B,同样是从三级缓存调用创建B的工厂方法,实例化B,放入一级缓存。然后Spring尝试初始化B,B也依赖于A,那么Spring会在初始化B的过程中去获取A。此时,会在一级缓存获取到A的未初始化的实例,完成B的初始化。
  3. B初始化完成,Spring继续完成A的初始化,最终一级缓存里的A和B都正常地完成了初始化。

两级缓存(保留第一、第三级缓存)就能解决循环依赖的问题。

但假设 B 是一个需要 AOP 代理的对象,比如带有事务功能的 @Transactional 注解。那么 Spring 还需要在 B 被创建之后生成它的代理对象(这个代理对象负责增强:拦截方法调用、实现事务等功能)。

那么,问题出现了:如果没有三级缓存机制(也就是,没有第二级缓存),当 A 依赖 B 时,Spring 可能在初始化过程中只能获取到 B 的原始对象,而不是代理对象,导致 A 没有正确拿到 B 的最终版本(即代理对象)。这时,就可能导致 AOP 功能失效。

这里二级缓存和三级缓存的核心区别在于:有三级缓存(第二级缓存)时,相当于多了一个中间层来处理代理对象,Spring 可以判断完是否需要创建代理对象再将实例(或该实例的代理对象)放入一级缓存。

有了三级缓存之后,需要代理的场景才能得到很好的解决:

  1. 由于最开始一二级缓存都没有A,所以创建A时候,会到第三级缓存拿到创建A的工厂,将A实例化,放入二级缓存。
  2. 实例化A之后,Spring尝试初始化A。Spring在初始化阶段为A注入属性时,需要B,于是Spring开始创建B,同样是从三级缓存调用创建B的工厂方法,实例化B,放入二级缓存。然后Spring尝试初始化B,B也依赖于A,那么Spring会在初始化B的过程中去获取A。此时,会在二级缓存获取到A的未初始化的实例,完成B的初始化。
  3. 到这里完成了B的实例化和初始化,接下来查看是否需要生成代理对象。
    • 如果B被标记为需要代理(例如,有@Transactional注解),Spring就会为B创建代理对象。原始的初始化好的B会被包装在代理对象中。这样,就完成了B的代理对象的实例化,初始化,然后B的代理对象被放入一级缓存;
    • 如果B没有被标记为需要代理,那么将初始化好的B放入一级缓存。
  4. 现在,初始化A所需的B(或者,B的代理对象)就已经被创建好了,A也初始化完成。检查A是否需要被代理,并将初始化好的A(或A的代理对象放入一级缓存)

标签:讲清楚,初始化,缓存,对象,Spring,代理,Bean
From: https://blog.csdn.net/weixin_74308571/article/details/142742778

相关文章

  • 洗衣店数字化转型:Spring Boot订单管理
    3系统分析3.1可行性分析通过对本洗衣店订单管理系统实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。3.1.1技术可行性本洗衣店订单管理系统采用JAVA作为开发语言,SpringBoot框架,是基于W......
  • Spring Boot 集成 RabbitMQ 消息事务(生产者)
    1.SpringBoot集成RabbitMQ消息事务(生产者)1.1.版本说明1.2.概览1.2.1.最大努力单阶段提交模式1.2.2.成功的业务流程1.2.3.失败的业务流程1.3.新建数据库表1.4.Spring配置1.5.定义常量1.6.配置交换机和队列1.7.定义RabbitMQ消息事务管理器1.8.定......
  • Spring Boot洗衣店订单系统:业务流程优化
    2相关技术2.1MYSQL数据库MySQL是一个真正的多用户、多线程SQL数据库服务器。是基于SQL的客户/服务器模式的关系数据库管理系统,它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等,非常适用于Web站点或者其他......
  • 洗衣店管理新思路:Spring Boot订单管理系统
    2相关技术2.1MYSQL数据库MySQL是一个真正的多用户、多线程SQL数据库服务器。是基于SQL的客户/服务器模式的关系数据库管理系统,它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等,非常适用于Web站点或者其他......
  • 【开题报告+论文+源码】基于Spring Boot+Vue的考研互助交流平台的设计与实现
    项目背景与意义考研作为许多大学毕业生进一步提升学术能力的重要途径,其过程往往伴随着复杂而严峻的挑战。随着信息时代的到来,虽然考研资源逐渐丰富,但信息不对称、缺乏有效交流平台等问题仍然普遍存在,这严重影响了考生的备考效率和信心。在这样的背景下,设计一个集信息共享、交......
  • 【开题报告+论文+源码】基于SpringBoot及Vue的宿舍软装租赁平台
    项目背景与意义随着科技的飞速发展和人们生活水平的不断提升,大学生对于宿舍环境的个性化需求也日益增长。宿舍作为大学生日常生活的重要场所,其软装的舒适度和美观度直接影响到学生的居住体验。因此,宿舍软装租售市场逐渐兴起,并呈现出蓬勃的发展态势。然而,传统的宿舍软装租售方......
  • 【开题报告+论文+源码】基于SpringBoot+Vue的个人博客系统设计与实现
    项目背景与意义当前,个人博客系统作为一种自由、开放的网络平台,已经成为个人展示、交流和分享的重要途径。然而,传统的个人博客系统在功能性和安全性方面存在一些问题。许多传统的个人博客系统功能单一,用户体验不够友好,同时在安全性方面也存在一定隐患,例如容易受到SQL注入、XSS......
  • Spring Boot洗衣店订单处理:高效管理之道
    1系统概述1.1研究背景如今互联网高速发展,网络遍布全球,通过互联网发布的消息能快而方便的传播到世界每个角落,并且互联网上能传播的信息也很广,比如文字、图片、声音、视频等。从而,这种种好处使得互联网成了信息传播的主要途径,社会上各种各样的信息都想尽办法通过互联网进行......
  • 基于SpringBoot+MySQL+SSM+Vue.js的电影票信息管理系统(附论文)
    获取见最下方名片获取见最下方名片获取见最下方名片演示视频基于SpringBoot+MySQL+SSM+Vue.js的电影票信息管理系统(附论文)技术描述开发工具:Idea/Eclipse数据库:MySQLJar包仓库:Maven前端框架:Vue/ElementUI后端框架:Spring+SpringMVC+Mybatis+SpringBoot......
  • 基于SpringBoot+MySQL+SSM+Vue.js的二手家电管理系统(附论文)
    获取见最下方名片获取见最下方名片获取见最下方名片演示视频基于SpringBoot+MySQL+SSM+Vue.js的二手家电管理系统(附论文)技术描述开发工具:Idea/Eclipse数据库:MySQLJar包仓库:Maven前端框架:Vue/ElementUI后端框架:Spring+SpringMVC+Mybatis+SpringBoot文......