详解Spring的循环依赖问题、三级缓存解决方案源码分析

news2024/9/23 17:19:21

0、基础:Bean的生命周期

        在Spring中,由于IOC的控制反转,创建对象不再是简单的new出来,而是交给Spring去创建,会经历一系列Bean的生命周期才创建出相应的对象。而循环依赖问题也是由Bean的生命周期过程导致的问题,因此我们首先需要了解Bean的生命周期。

Bean的生命周期可以概括为4步:

实例化----属性注入----初始化----销毁

详细的讲,步骤如下:

        ******************************************实例化123*******************************************

  1. 定位:Spring容器会根据配置文件(如XML、注解等)或编程式配置来确定需要创建的Bean。
  2. 加载:Spring容器会加载配置文件并解析其中的Bean定义,将其转换为内部数据结构,例如BeanDefinition。

  3. 实例化:在实例化阶段,Spring容器会根据Bean定义中的信息创建Bean的实例。这个过程可以通过构造函数实例化、工厂方法实例化或者通过反射机制来实现。*****************************************属性注入4*********************************************

  4. 属性注入:在实例化Bean之后,Spring容器会对Bean进行属性注入。这可以通过setter方法注入、构造函数参数注入或字段注入等方式来完成。
    ****************************************初始化5~9********************************************

  5. Aware接口回调:如果Bean实现了Spring的Aware接口,容器会通过回调方式将一些特殊的资源注入到Bean中。例如,如果Bean实现了BeanFactoryAware接口,容器会将当前的BeanFactory实例注入到Bean中。

  6. 初始化前回调:如果Bean实现了InitializingBean接口,容器会在初始化之前调用它的afterPropertiesSet()方法,给Bean一个机会执行一些初始化操作。同时,Spring容器还支持使用自定义的初始化方法。

  7. 初始化后回调:如果Bean配置了初始化回调方法,容器会调用该方法进行一些自定义的初始化逻辑处理。

  8. 后置处理器(BeanPostProcessor):Spring容器会调用注册的Bean后置处理器对Bean进行加工和增强。例如,可以通过AOP技术在这个阶段为Bean动态生成代理对象。

  9. 完成:至此,Bean已经成功创建,并且已经完成了所有的初始化过程。此时可以将Bean提供给其他对象使用。
    ****************************************销毁10、11******************************************

  10. 销毁前回调(PreDestroy):在容器关闭之前,调用Bean的销毁前回调方法,执行一些清理操作和释放资源的任务。

  11. 销毁:容器关闭时,销毁所有Bean实例,包括调用相应Bean的销毁方法,进行最终的清理和资源释放。

 1、循环依赖问题

        例如下面的代码,A和B类就构成了循环依赖,原因如下:

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

   @Autowired
   private A a;
}

创建Bean的步骤: 

  1. Spring 扫描 class 得到 BeanDefinition;
  2. 根据得到的 BeanDefinition 去生成 bean;
  3. 首先根据 class 推断构造方法;
  4. 根据推断出来的构造方法,反射,得到一个对象(我们称为原始对象);
  5. 填充原始对象中的属性(依赖注入);
  6. 如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象;
  7. 把最终生成的代理对象放入单例池(源码中叫做 singletonObjects)中,下次 getBean 时就直接从单例池拿即可;

        对于上述步骤的第4步, 得到原始对象后需要注入属性,A 类中存在一个 B 类的 b 属性,此时就会根据 b 属性的类型和属性名去 BeanFactory 中去获取 B 类所对应的单例bean
        如果此时 B 类在 BeanFactory 中还没有生成对应的 Bean,那么就需要去生成,就会经过 B 的 Bean 的生命周期,也就会同样的,需要A类的Bean,就发生了循环依赖,导致A和B的bean都创建不出来。

 

        概括而言:
        A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)

        然而实际上,Spring通过三级缓存的方式自动解决了这个问题 。

2、三级缓存的引入

2.1 非AOP情况下的解决方案

         根据上文的分析我们发现,出现循环依赖的根本原因,是B的Bean需要注入A属性的时候,Bean A还没有创建出来,导致的。那么相应的,只要:
        在进行依赖注入之前,先把 A 的原始 Bean 放入缓存提早暴露,只要放到缓存了,其他 Bean 需要时就可以从缓存中拿了,这个缓存就应该是earlySingletonObjects),放入缓存后,再进行依赖注入。
        由于提前暴露,在创建B的Bean过程中,当需要注入A的属性时,就可以从缓存中拿到A提前暴露的原始对象(还不是最终Bean),就解决了问题。关键在于全程只有一个A的原始对象,其后续的生命周期没有变化。
        如下图所示:

   2.2 三级缓存具体     

        因此,对于不同时期的Bean,如原始Bean、完整周期的Bean,需要不同的缓存来存放,底层源码中有三级缓存: 

/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);

/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);

/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
  • 一级缓存:singletonObjects;缓存的是已经经历了完整生命周期的bean对象。
  • 二级缓存:earlySingletonObjects;比 singletonObjects 多了一个 early ,表示缓存的是早期的 bean对象(原始对象)。早期指的是 Bean 的生命周期还没走完就把这个 Bean 放入了 earlySingletonObjects
  • 三级缓存:singletonFactories;缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象的。

3、有AOP情况下使用singletonFactories 

3.1 引入三级缓存

        看似我们只需要1、2级缓存就能够解决问题了,为什么需要三级缓存呢?
        这就需要考虑到AOP代理对象的问题了:

        上文的红字提到,之所以能够提前暴露,是因为假定的A的原始对象始终是同一个对象,但如果有AOP的情况下呢?我们考虑这样的场景:

         按照上文的分析,假设创建B的bean过程中,注入了A的原始对象属性。然后,A的原始对象采用AOP产生了一个代理对象,即,A的Bean变成了 AOP 之后的代理对象。而B中的 属性a对应的并不是 AOP 之后的代理对象,而仍然是原始对象。

        也就是说,这种情况下,B 依赖的 A 和最终的 A 不是同一个对象!

        而解决这个问题的方法,就是引入三级缓存的 singletonFactories 

 3.2 三级缓存具体解析

        实际上,在有AOP的情况下,Spring并没有像第2节所说,直接将示例缓存到二级缓存,而是生成完原始对象之后”多此一举“地将实例先封装到objectFactory中,在需要引用的时候再通过singletonFactory.getObject()获取。

        跟进getObject()方法,其实执行了getEarlyBeanReference这个关键方法。

    this.addSingletonFactory(beanName, () -> {
		return this.getEarlyBeanReference(beanName, mbd, bean);
	});

         也就是说,Spring将当前bean缓存到earlyProxyReferences中,标识提前曝光的bean。而wrapIfNecessary是用于Spring AOP自动代理的,也就是说在被提前引用前,进行了AOP代理,并得到了代理对象。
        此时earlySingletonObjects 缓存中的对象就是代理对象了!

        因此,假设此时有其他对象依赖了A,就可以从earlySingletonObjects中获取到A原始对象的代理对象了,并且和A是同一个对象,实现了目标。

3.3 后续依赖问题

        当 B 创建完了之后,A 继续进行生命周期,而 A 在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道 A 原始对象已经经历过了 AOP ,所以对于 A 本身而言,不会再去进行 AOP了,那么怎么判断一个对象是否经历过了 AOP 呢?

         注意postProcessAfterInitialization方法,会当前beanName是否在earlyProxyReferences中,如果在就AOP过了,不在则执行AOP方法。

         此时对于Bean A对象而言已经完成创建了,可以把它放入缓存singletonObjects中了,因此从earlySingletonObjects 中得到代理对象,然后入 singletonObjects 中。

        至此,整个循环依赖解决完毕。

4、总结 

        这里引用看到的一篇写的很清晰的博客http://t.csdn.cn/GeHkA的图来说明具体流程:

Spring AOP循环依赖 

         对于三级缓存的singletonFactories,总结而言

        缓存的是一个 ObjectFactory ,主要用来去生成原始对象进行了 AOP之后得到的「代理对象」。
        在每个 Bean 的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到:
(1)如果没有出现循环依赖依赖本 bean,那么这个工厂无用,本 bean 按照自己的生命周期执行,执行完后直接把本 bean 放入 singletonObjects 中即可(对应本文章的第1节)
(2)如果出现了循环依赖依赖了本 bean,则:
        <2.1>如果有 AOP 的话,另外那个 bean 执行 ObjectFactory 提交得到一个 AOP 之后的代理对象。(对应本文章第3节)
        <2.2>如果无需 AOP ,则直接得到一个原始对象。(对应本文章第2节)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/908515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wustojc3010快速求和

#include <stdio.h> int main() {int n;double s;s0;scanf("%d",&n);for(int i1;i<n;i){ss(double)1.0/(i*(i1.0));//强转一下类型}printf("%.5lf",s);return 0;}

Docker私有仓库创建

1.Docker私有仓库搭建 拉取私有仓库镜像并启动私有仓库容器。 访问私有仓库容器&#xff0c;表明私有仓库搭建成功。 此时私有仓库就已经搭建完成了。 2.将本机的镜像传到私有仓库 3.将私有仓库镜像拉取到本地

论文导读|European Journal of Operational Research近期文章精选:旅行商问题专题

推文作者&#xff1a;王松阁 编者按 在“European Journal of Operational Research近期论文精选”中&#xff0c;我们有主题、有针对性地选择了European Journal of Operational Research中一些有趣的文章&#xff0c;不仅对文章的内容进行了概括与点评&#xff0c;而且也对文…

DHCP协议原理与应用

DHCP协议原理与应用 一、DHCP协议概述1.1、场景描述1.1.1、场景描述11.1.2、场景描述21.1.3、场景描述3 二、DHCP协议工作原理2.1、DHCP简介2.2、DHCP协议名词解释2.3、DHCP服务器配置2.4、PC的DHCP设置2.5、DHCP协议工作过程2.6、DHCP协议报文及用途2.7、DHCP报文介绍2.7.1、D…

面试之快速学习STL-迭代适配器

先放一张大图 参考&#xff1a;http://c.biancheng.net/view/7255.html 1. 反向迭代器 例子&#xff1a; std::list<int> values{1,2,3,4,5};auto start_it values.rbegin();const auto end_it values.rend();//start_it end_it std::reverse_iterator<std::lis…

HCIP 三层架构实验

三层架构实验 拓扑和思路拓扑思路LSW配置LSW1LSW2LSW3 DHCPLSW2LSW1 ACL外网冗余 拓扑和思路 拓扑 思路 首先划分网段&#xff0c;然后LSW1和LSW2和R1可以用ospf宣告就行&#xff0c;然后R1写条缺省指向R2 然后可以将LSW1和LSW2三合一&#xff0c;给交换机配置换分组&#x…

用电脑软件0代码设计WS2812显示效果(含软件下载地址)

用电脑软件设计WS2812显示效果 ws2812显示效果设计软件和单片机程序文件 单片机型号为8脚的STC8G1K08A或STC8G1K17A或者16脚的STC8G1K08或STC8G1K17 烧录时晶振选择22.1184M 百度网盘下载地址&#xff1a;链接: https://pan.baidu.com/s/1cVvA604IKtZ-cIqTX8Jgzw?pwd1234 提取…

数学分析:体形式

确实&#xff0c;面积应该是没有正负的&#xff0c;或者说和曲面的定向应该是无关的。我们用微分形式的积分定义了具有参数形式的曲面的面积。所以这个意思就是说&#xff0c;对于不同的曲面的定向&#xff0c;微分形式应该也不同。 这就是体形式的具体样子&#xff0c;得到每…

中科大 Epc 综合英语经验贴

免修规定考试形式1. 听力&#xff08;813131320分&#xff09;2. 单词&#xff08;20120分&#xff09;3. 语法结构&#xff08;10110分&#xff09;4. 阅读&#xff08;5篇&#xff0c;每题两分&#xff0c;52550分&#xff09; 机考答题建议 免修规定 研究生英语课免修规定&…

已解决Gradle错误:“Unable to load class ‘org.gradle.api.plugins.MavenPlugin‘”

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

<指针进阶>指针数组和数组指针傻傻分不清?

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

数学建模大全及优缺点解读

分类模型 1、距离聚类&#xff08;系统聚类&#xff09;&#xff08;常用&#xff0c;需掌握&#xff09; 优点&#xff1a; ①将一批样本数据按照他们在性质上的亲密程度在没有先验知识的情况下自动进行分类 ②是一种探索性的分析方法&#xff0c;分类结果不一定相同 例如&am…

Stable Diffusion训练Lora模型

以下内容参考:https://www.bilibili.com/video/BV1Qk4y1E7nv/?spm_id_from333.337.search-card.all.click&vd_source3969f30b089463e19db0cc5e8fe4583a 1、训练Lora的2个重点步骤 第一步&#xff0c;准备训练要使用的图片&#xff0c;即优质的图片 第二部&#xff0c;为…

6.物联网LWIP之并发服务器编程

一。并发服务器&#xff08;多线程&#xff09;实现 #include "socket_udp_server.h" #include "socket_tcp_server.h" #include "socket_wrap.h" #include "ctype.h"static char ReadBuff[BUFF_SIZE];/*** brief udp 服务器任务* p…

深度学习论文: Learning Transferable Visual Models From Natural Language Supervision

深度学习论文: Learning Transferable Visual Models From Natural Language Supervision Learning Transferable Visual Models From Natural Language Supervision PDF: https://arxiv.org/pdf/2103.00020.pdf 官方代码: https://github.com/OpenAI/CLIP PyTorch代码: https:…

vector(介绍)

目录 1.vector的介绍及使用 1.1 vector的介绍 1.2 vector的使用 1.2.1 vector的定义 1.2.2 vector iterator 的使用 1.2.3 vector 空间增长问题 1.2.4 vector 增删查改 1.2.5 vector 迭代器失效问题。&#xff08;重点&#xff09; 2.vector深度剖析及模拟实现 2.1 使用…

PHP“牵手”淘宝商品评论数据采集方法,淘宝API接口申请指南

淘宝天猫商品评论数据接口 API 是开放平台提供的一种 API 接口&#xff0c;它可以帮助开发者获取商品的详细信息&#xff0c;包括商品的标题、描述、图片等信息。在电商平台的开发中&#xff0c;详情接口API是非常常用的 API&#xff0c;因此本文将详细介绍详情接口 API 的使用…

深入理解Semaphore

Semaphore&#xff08;信号量&#xff09;是操作系统中PV操作的原语在java中的实现&#xff0c;它也是基于AQS实现的。其中PV操作是操作系统中一种实现进程互斥与同步的有效方法。PV操作与信号量&#xff08;S&#xff09;的处理有关&#xff0c;P表示通过&#xff0c;V表示释放…

2023.8 - java - 泛型

泛型问题的引出&#xff1a; jdk 1.5 引出泛型 // package 泛型; public class index {public static void main (String[] args){test t new test();t.setContent("aaa");int a (int) t.getContent();System.out.println(a);} }class test{Object content;publi…

分享图片 | 快速浏览网页资源,批量保存、一键分享图片

前言 小伙伴学习吉他&#xff0c;有时需要在互联网搜索曲谱资源&#xff0c;而多数曲谱均为图片&#xff0c;并且为多页&#xff0c;在电脑上显示练习很不方便&#xff0c;需要停下来点击鼠标进行翻页&#xff0c;影响练习的连贯性。 为了解决上述问题&#xff0c;通常把图片…