SpringBoot扩展篇:循环依赖源码链路
SpringBoot扩展篇:循环依赖源码链路
- 1. 相关文章
- 2. 一个简单的Demo
- 3. 流程图
- 3.1 BeanDefinition的注册
- 3.2 开始创建Bean
- 3.3 从三级缓存获取Bean
- 3.4 创建Bean
- 3.5 实例化Bean
- 3.6 添加三级缓存
- 3.7 属性初始化
- 3.8 B的创建过程
- 3.9 最终流程
1. 相关文章
SpringBoot 源码解析全集
SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和@ComponentScan源码分析
SpringBoot 源码解析6:Bean的创建① AbstractBeanFactory#doGetBean
SpringBoot 源码解析7:Bean的创建② AbstractAutowireCapableBeanFactory#createBean
SpringBoot扩展篇:Spring注入 @Autowired & @Resource
在Spring中,注册BeanDefinition和实例化bean的流程是分开的。
在bean实例化之前,Spring已经将所有要实例化的Bean的信息封装成BeanDefinition,
并且注册到DefaultListableBeanFactory#beanDefinitionMap。
本文只是循环依赖原理总结和相关代码链路,想要真正读懂循环依赖源码,需要一定的内功心法,详细的源码解析在上面链接中。
2. 一个简单的Demo
@Component
public class ObjectA {@Autowiredprivate ObjectB objectB;}
@Component
public class ObjectB {@Autowiredprivate ObjectA objectA;}
这是一个简单的循环依赖Demo,后续的讲解以Demo为例。ObjectA、ObjectB 简称A,B。
3. 流程图

这是作者照着源码一步一步Debug画出的流程图,下面的Step序号与流程图中序号一致,挑重点讲。
3.1 BeanDefinition的注册
Step1:在实例化之前,Spring将要创建的Bean所对应的BeanDefinition都注册到BeanFactory。
参考:SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和@ComponentScan源码分析
3.2 开始创建Bean
Step2:在此之前,所有的BeanDifinition全部注册到bean工厂。

@Override
public void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof FactoryBean) {final FactoryBean<?> factory = (FactoryBean<?>) bean;boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)((SmartFactoryBean<?>) factory)::isEagerInit,getAccessControlContext());}else {isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean<?>) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}}else {getBean(beanName);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());}else {smartSingleton.afterSingletonsInstantiated();}}}
}
在refresh方法中,会调用DefaultListableBeanFactory#preInstantiateSingletons,遍历所有的beanName,调用getBean方法获取Bean。
3.3 从三级缓存获取Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}
singletonObjects:一级缓存
earlySingletonObjects: 二级缓存
singletonFactory:三级缓存,从三级缓存中获取Bean时,会调用singletonFactory.getObject()。
从缓存中逐级获取,如果缓存中找到了对应的Bean,那么就会返回bean,getBean方法调用结束。否则,就会创建Bean,放入缓存,然后返回bean。
3.4 创建Bean
doCreateBean方法

- singletonFactory是一个函数式接口,会回调CreateBean方法创建Bean。
//AbstractBeanFactory#doGetBean
if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
- 当创建完成单例Bean,最终会放入到一级缓存中。对应Step21和Step24,先放入的B,在放入的A。
protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}
3.5 实例化Bean
Step7:createBeanInstance
Spring中兼容了自定义Supplier创建、@Configuration注解的工厂创建、构造器创建等多种创建方式。
3.6 添加三级缓存
Step8:放入到三级缓存singletonObjects中,此时的A只是对象创建成功,属性还未开始赋值。
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");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}
一个bean只会缓存在一个缓存中,在加入到一个缓存的时候,会移除其他两个缓存。
@FunctionalInterface
public interface ObjectFactory<T> {/*** Return an instance (possibly shared or independent)* of the object managed by this factory.* @return the resulting instance* @throws BeansException in case of creation errors*/T getObject() throws BeansException;}
可以看到,三级缓存中存放的是ObjectFactory,函数式接口。

我们可以看到,如果当前bean被代理了,那么就会在AbstractAutoProxyCreator中缓存了当前bean对应的代理bean,那么三级缓存会返回代理bean,否则就会返回当前bean。
3.7 属性初始化
初始化逻辑可参考:SpringBoot扩展篇:Spring注入 @Autowired & @Resource
最终会回调getBean方法,参数为A对象依赖bean的名称。此时,第二次调用getBean方法
3.8 B的创建过程
B的创建会重复3.3-3.7流程,发现B也依赖A。就会第三次调用getBean方法。
3.9 最终流程
- 第三次调用getBean方法获取A,但是此时的A已经缓存在singletonFactory中,在调用getSingleton的时候,会将A从singletonFactory中取出来,放入到二级缓存earlySingletonObjects中。
- 第三次getBean方法就会返回A,对B对象的A属性赋值。
- 当B对象的所有属性赋值完毕之后,会调用addSingleton将B放入到一级缓存中。此时的B是最终成品。
- 第二次getBean方法返回B对象,对A对象的B属性赋值。
- 当A对象的所有属性赋值完毕之后,会调用addSingleton将A放入到一级缓存中。此时的A是最终成品。
- 第一次调用getBean方法返回A对象。流程结束!
相关文章:
SpringBoot扩展篇:循环依赖源码链路
SpringBoot扩展篇:循环依赖源码链路 1. 相关文章2. 一个简单的Demo3. 流程图3.1 BeanDefinition的注册3.2 开始创建Bean3.3 从三级缓存获取Bean3.4 创建Bean3.5 实例化Bean3.6 添加三级缓存3.7 属性初始化3.8 B的创建过程3.9 最终流程 1. 相关文章 SpringBoot 源码…...
服务消费微服务
文章目录 1.示意图2.环境搭建1.创建会员消费微服务模块2.删除不必要的两个文件3.检查父子模块的pom.xml文件1.子模块2.父模块 4.pom.xml 添加依赖(刷新)5.application.yml 配置监听端口和服务名6.com/sun/springcloud/MemberConsumerApplication.java 创…...
uni-app纵向步骤条
分享一下项目中自封装的步骤条,存个档~ 1. 话不多说,先看效果 2. 话还不多说,上代码 <template><!-- 获取一个数组,结构为[{nodeName:"流程发起"isAudit:falsetime:"2024-02-04 14:27:35"otherDat…...
【JavaEE -- 文件操作IO有关面试题】
文件操作IO有关面试题 1.查找硬盘上的文件位置1.1 思路1.2 执行代码 2. 实现文件复制2.1 思路2.2 代码执行 3. 打印搜索的词的文件路径3.1 思路3.2 代码执行 1.查找硬盘上的文件位置 给定一个文件名,去指定的目录中进行搜索,找到文件名匹配的结果&#…...
Open WebUI大模型对话平台-适配Ollama
什么是Open WebUI Open WebUI是一种可扩展、功能丰富、用户友好的大模型对话平台,旨在完全离线运行。它支持各种LLM运行程序,包括与Ollama和Openai兼容的API。 功能 直观的界面:我们的聊天界面灵感来自ChatGPT,确保了用户友好的体验。响应…...
[2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
今天在漏洞扫描的时候蹦出来一个zookeeper的漏洞问题,即使是非zookeeper的节点,或者是非集群内部节点,也可以通过nc扫描2181端口,获取极多的zk信息。关于漏洞的详细描述参考apache zookeeper官方概述:CVE-2018-8012: A…...
vscode添加gitee
1.创建仓库 2.Git 全局设置 3.初始化仓库 2.1 打开vscode打开需要上传到给git的代码文件 2.2.点击左边菜单第三个的源代码管理->初始化仓库 4.点击加号暂存所有更改 5.添加远程仓库 5.1 添加地址,回车 5.2 填写库名,回车 6.提交和推送 6.1 点击✔提交…...
数据库底层原理
本文将介绍数据库在储存和通讯时的原理 数据库储存 首先,数据库的作用持久化存储数据,数据库的存储形式就是文件,每一张表就是一个文件,其他数据也是文件形式,比如索引文件。 比如像mysql数据库,其中的数…...
JVM虚拟机-实战篇
专属小彩蛋:前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站(前言 - 床长人工智能教程) 目录 一、内存溢出和内存泄漏 什么是内存泄漏? 二、解决内存泄漏 解决内存泄漏的思路 top命令 发现问题 VisualVM 发现问…...
上岸跨考生的备考经验,送给零基础跨考计算机的你!
九个月的时间绝对是够用的,就算是跨考也够用! 一般来说,专业课要复习三轮,九个月的时间,复习三轮完全够用 复习资料:王道四本书王道真题 打基础阶段:3-6月: 学习目标:…...
js改变图片曝光度(高亮度)
方法一: 原理: 使用canvas进行滤镜操作,通过改变图片数据每个像素点的RGB值来提高图片亮度。 缺点 当前项目使用的是svg,而不是canvas 调整出来的效果不是很好,图片不是高亮,而是有些发白 效果 代码 …...
【NLP笔记】大模型prompt推理(提问)技巧
文章目录 prompt概述推理(提问)技巧基础prompt构造技巧进阶优化技巧prompt自动优化 参考链接: Pre-train, Prompt, and Predict: A Systematic Survey of Prompting Methods in Natural Language Processing预训练、提示和预测:NL…...
【目标检测】西红柿成熟度数据集三类标签原始数据集280张
文末有分享链接 标签名称names: - unripe - semi-ripe - fully-ripe D00399-西红柿成熟度数据集三类标签原始数据集280张...
Java File类(文件操作类)
背景: 在Java编程语言中,操作文件和目录是一项常见的任务。而File类,则是java.io包中的重要类,它是唯一代表磁盘文件本身的对象。通过File类提供的方法,我们可以轻松地创建、删除、重命名文件和目录等操作。 构造方法&…...
正则表达式 vs. 字符串处理:解析优势与劣势
title: 正则表达式 vs. 字符串处理:解析优势与劣势 date: 2024/3/27 15:58:40 updated: 2024/3/27 15:58:40 tags: 正则起源正则原理模式匹配优劣分析文本处理性能比较编程应用 1. 正则表达式起源与演变 正则表达式(Regular Expression)最早…...
1、goreplay流量回放
目的 在实际项目中,会有大量的回归测试工作,通常会使用自动化代码的手段来实现回归,但是对于一个庞大的系统来说,通过自动化脚本的方式来实现回归测试,又显得很费时费力。并且如果有定期将线上数据同步到测试环境的需求…...
Transformer的前世今生 day06(Self-Attention和RNN、LSTM的区别)
Self-Attention和RNN、LSTM的区别 RNN的缺点:无法做长序列,当输入很长时,最后面的输出很难参考前面的输入,即长序列会缺失上文信息,如下: 可能一段话超过50个字,输出效果就会很差了 LSTM通过忘…...
UDP send 出现大量“Resource temporarily unavailable”
背景 最近排查用户现场环境,查看日志出现大量的“send: Resource temporarily unavailable”错误,UDP设置NO_BLOCK模式,send又发生在进程上下文,并且还设置了SO_SNDBUF 为8M,在此情况下为什么还会出现发送队列满的情况…...
怎么拆解台式电脑风扇CPU风扇的拆卸步骤-怎么挑
今天我就跟大家分享一下如何选购电脑风扇的知识。 我也会解释一下机箱散热风扇一般用多少转。 如果它恰好解决了您现在面临的问题,请不要忘记关注本站并立即开始! 文章目录列表:大家一般机箱散热风扇都用多少转? 机箱散热风扇选择…...
Windows安装Odoo结合内网穿透实现公网访问本地企业管理系统
文章目录 前言1. 下载安装Odoo:2. 实现公网访问Odoo本地系统:3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件,是一个一站式全功能ERP及电商平台。 开源性质:Odoo是一个开源的ERP软件,这意味着企…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
