简单说说 spring 是如何处理循环依赖问题的(源码解析)

ops/2024/10/18 16:38:26/

聊聊源码

spring 中,解决循环依赖的关键是三级缓存,缓存数据在 DefaultSingletonBeanRegistry类中

/** Cache of singleton objects: bean name to bean instance. */
//一级缓存,是最终生成的对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name to ObjectFactory. */
//三级缓存,和aop相关
//ObjectFactory 是一个函数式接口,如果有进行aop,则返回aop对象,否则返回的是原始对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name to bean instance. */
//二级缓存,完成实例化,未完成初始化的对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

1 提前暴露

AbstractAutowireCapableBeanFactory#doCreateBean

在这个方法中,spring会将已经完成实例化的bean放入到三级缓存中去。

java">//AbstractAutowireCapableBeanFactory 类
//创建bean的过程
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper = null;//如果是单例,先从缓存中进行获取,如果获取的到,则直接使用if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}//如果缓存中没有,则创建bean实例if (instanceWrapper == null) {//【核心】创建实例instanceWrapper = createBeanInstance(beanName, mbd, args);}.... 其他处理逻辑/******************************* 1 提前暴露 *******************************/// Eagerly cache singletons to be able to resolve circular references// even when triggered by lifecycle interfaces like BeanFactoryAware.//判断是否支持循环依赖(是单例 && 允许循环引用 && 当前bean正在创建中)boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}//这个方法会将当前的实例化完成,初始化未完的对象,存储到 singletonFactories 缓存中去//之所以存到的是三级缓存,是因为对象可能是经过aop的,在singletonFactories中存储的是ObjectFactory// ObjectFactory是一个函数式接口,如果对象有经过aop会返回aop对象,否则返回原对象//singletonFactories 就是我们所说的三级缓存//同时,会将beanName 存入 registeredSingletons 列表中,作用是提前曝光,是解决循环袭来的关键addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.Object exposedObject = bean;try {//【核心】填充bean的属性//1 完成属性的注入,根据bean定义的时候,设置的属性值。//2 解析依赖 如果bean依赖其他的bean,则其会递归的去创建其他的bean//3 调用BeanPostProcessor,这里主要是执行 InstantiationAwareBeanPostProcessorpopulateBean(beanName, mbd, instanceWrapper);//【核心】初始化beanexposedObject = initializeBean(beanName, exposedObject, mbd);}... 其他处理逻辑return exposedObject;
}

2 从缓存中依次获取bean

在populateBean中,会去循环遍历需要进行依赖注入的bean,会通过getBean方法去进行获取,会走到DefaultSingletonBeanRegistry.getSingleton

过程如下

1 从一级缓存中获取,如果可以获取到,直接返回

2 如果一级缓存中获取不到,从二级缓存中获取,如果获取到,则直接返回

3 如果二级缓存也获取不到,则从三级缓存中获取

java">protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lock//先从一级缓存中获取,一级缓存存放的是成品bean,就是已经完成了实例化和初始化Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {//从二级缓存中获取(早期单例缓存),只完成了实例化的beansingletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton lock//这里进行二重检查singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {// 从三级缓存中获取,三级缓存存放的是ObjectFactory,// ObjectFactory是一个函数式接口,这个方法就是 // AbstractAutowireCapableBeanFactory.getEarlyBeanReferenceObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}

三级缓存中存储的对象是ObjectFactory,这个对象定义的是一个函数式接口,最终会调用回设置的位置,就是

AbstractAutowireCapableBeanFactory的getEarlyBeanReference方法

java">protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {//这是一个BeanPosterProcessor,是一个接口//1 允许获取一个早期的bean//2 一般框架中,比如mybatis中的事务,会实现这个接口的getEarlyBeanReference方法//然后返回一个aop之后的类。SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;
}

总结

从源码中,我们可以推断出,循环依赖的处理有几个基础的点,分别是

1 三级缓存,三级缓存中的第三级缓存是处理循环依赖的关键

2 提前暴露,在bean完成实例化之后,spring就会将还未创建完成的bean暴露到三级缓存中。

3 允许获取仅实例化的bean进行赋值。

需要注意的是:spring中,只能实现set注入的循环依赖,如果在构造器中产生循环依赖,spring无法解决

传送阵

简单说说 spring构造器循环依赖 为什么无法解决(源码解析)-CSDN博客


http://www.ppmy.cn/ops/126519.html

相关文章

web网页---QQ注册页面的实现

代码部分 <title>QQ注册</title> </head> <style>.text-style1 {color: black;font-size: 28px;}.text-style2 {color: rgb(37, 37, 37);font-size: 17px;}.text-style3{color: rgba(189, 185, 185, 0.904);font-size: 9px;}.text-style4 {color: rg…

使用 nrm 管理 npm 镜像源

使用 nrm&#xff08;npm registry manager&#xff09;管理 npm 镜像源可以极大地简化在不同 npm 源之间切换的过程。以下是如何使用 nrm 来管理 npm 镜像源的详细步骤&#xff1a; 一、安装 nrm 首先&#xff0c;你需要全局安装 nrm。打开命令行界面&#xff0c;然后运行以…

永恒之蓝漏洞

MS17-010是微软于2017年3月发布的一个安全补丁&#xff0c;旨在修复Windows操作系统中的一个严重漏洞&#xff0c;该漏洞被称为“永恒之蓝”&#xff08;EternalBlue&#xff09;。这个漏洞影响了Windows的Server Message Block&#xff08;SMB&#xff09;协议&#xff0c;允许…

万界星空科技专门针对数字化改造申报的MES

万界星空科技在制造业管理软件领域&#xff0c;特别是MES&#xff08;制造执行系统&#xff09;方面&#xff0c;展现了强大的技术实力和创新能力&#xff0c;为制造型企业实现数字化转型提供了全方位的支持和保障。针对数字化改造申报的MES系统&#xff0c;万界星空科技提供了…

OSI参考模型与TCP/IP模型

OSI参考模型 物理层 定义电压、接口、线缆标准、传输距离、传输介质等物理参数。数据链路层&#xff08;确定范围里的某一个&#xff09; MAC地址寻址网络层&#xff08;确定一个范围&#xff09; 网络地址层寻址、路由传输层&#xff08;区分不同的程序&#xff09; 数据分段…

《计算机视觉》—— 疲劳检测

文章目录 一、疲劳检测实现的思想二、代码实现 一、疲劳检测实现的思想 了解以下几篇文章有助于了解疲劳检测的方法 基于dlib库的人脸检测 https://blog.csdn.net/weixin_73504499/article/details/142977202?spm1001.2014.3001.5501 基于dlib库的人脸关键点定位 https://blo…

香橙派刷机和开发环境准备(ubuntu20.04版)_随记1

前言&#xff1a; 目录&#xff1a; 3.38.1.3. PC 端安装 Paddle2ONNX 1、常用&#xff1a; 目录 一、香橙派刷ubuntu系统 2.3.2. 使用 RKDevTool 烧录 Linux 镜像到 TF 卡中的方法 a. 首先通过 USB 公对公数据线连接好开发板与 Windows 电脑&#xff0c;开发板 USB b. …

Redis高阶

一、Redis单线程和多线程 1.Redis为什么选择单线程: 主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回(socket 写)等都由一个顺序串行的主线程处理&#xff0c;这就是所谓的“单线程”…