SpringBoot扩展篇:循环依赖源码链路

SpringBoot使用TraceId日志链路追踪 用途:每一次链路,线程维度,添加最终的链路ID traceId。MDC(Mapped Diagnostic Context)诊断上下文映射,是@Slf4j提供的一个支持动态打印日志信息的工具。/*** 日志拦截器*/@Override//可以考虑让客户端传入链路ID,但需保证一定的复杂度唯一性;如果没使用默认UUID自动生成if (!@Override// 请求处理完成后,清除MDC中的traceId,以免造成内存泄漏/*** 声明一个线程池。 阅读详情

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 {

    @Autowired
    private ObjectB objectB;

}
@Component
public class ObjectB {

    @Autowired
    private 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方法
在这里插入图片描述

  1. 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);
}
  1. 当创建完成单例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 最终流程

  1. 第三次调用getBean方法获取A,但是此时的A已经缓存在singletonFactory中,在调用getSingleton的时候,会将A从singletonFactory中取出来,放入到二级缓存earlySingletonObjects中。
  2. 第三次getBean方法就会返回A,对B对象的A属性赋值。
  3. 当B对象的所有属性赋值完毕之后,会调用addSingleton将B放入到一级缓存中。此时的B是最终成品。
  4. 第二次getBean方法返回B对象,对A对象的B属性赋值。
  5. 当A对象的所有属性赋值完毕之后,会调用addSingleton将A放入到一级缓存中。此时的A是最终成品。
  6. 第一次调用getBean方法返回A对象。流程结束!
手把手教学|用飞书+扣子工作流实现自动化批量图片生成,效率提升99%!体验完整操作流程 在先前的文章中,我们已经深入探讨了扣子智能体的多种应用场景,以及如何创建和使用工作流进行高效管理。本文将引领大家通过一个具体案例,将扣子的工作流功能嵌入飞书应用中,实现操作的自动化与高效化。 阅读详情

相关推荐

STM32F103C8T6板子使用说明

pcb版,把众多元器件集中到一个板子上面管理。

LYC_462857656的博客 2180

【Spring Boot】Spring Boot解决循环依赖

环依赖是指两个或者多个bean互相依赖对方,从而形成一个闭环。例如:Bean A依赖于Bean B,而Bean B又依赖于Bean A。可能会导致Spring在尝试创建这些bean实例时出现问题,因为他们互相等待对方被创建,最终导致应用程序无法启动。Spring是如何发现这种循环依赖的问题的呢?通过依赖图来检测和发现循环依赖问题。

smile_sundays的博客 7845

告别报错!Win10下Autodock Vina 1.2.3保姆级安装与避坑指南

本文提供了Win10系统下Autodock Vina 1.2.3的完整安装与实战指南,涵盖环境配置、分子文件预处理、对接参数优化及结果分析等关键步骤。通过详细的避坑技巧和优化方案,帮助用户高效完成分子对接任务,特别适合生物信息学研究人员和药物发现领域的初学者。

weixin_29969209的博客 400

【源码解析】SpringBoot循环依赖源码解析II

【源码解析】SpringBoot循环依赖源码解析II

秋装什么的博客 820

华北理工大学以升实验班蓝桥杯选拔赛题解

文章目录华北理工大学以升实验班蓝桥杯选拔赛题解A:期末考试题目描述题目讲解代码B:月份题目描述题目讲解代码C:打饭题目描述题目讲解代码D : 正弦波信号题目描述题目讲解代码E : 亲爱的长方形题目描述题目讲解代码F:挖金子题目描述题目讲解代码G : 金金的游戏题目描述题目讲解代码H : 区间和题目描述题目讲解代码详细视频讲解请关注华北理工大学b站账号。 华北理工大学以升实验班蓝桥杯选拔赛题解 A:期末考试 题目描述 Description 期末考试过去许多天了,如今成绩都出来了,那么现在让我们对成绩我分个类

Qq1739206233的博客 1126

UFS 功能描述:UFS boot

UFS 启动过程介绍:启动配置,启动代码下载过程和安全性

m0_51720747的博客 1600

Springboot项目访问路径

springboot项目的访问路径查询 创建的springboot项目,启动后,默认的访问路径是主机ip+默认的端口号是8080。如果没有配置默认的index.jsp,则访问显示Whitelabel Error Page。 如果在controller接口用requestMapping配置了请求路径,那么访问路径就是主机+默认端口号+方法的请求路径。如果在controller类上方定义了requestMapping,那么就是主机地址+默认端口号+类上的地址+方法地址去具体访问。 如果修改了端口,则访问路径是i

fglxhlw的博客 1万+

数据行小于列索引,内部模式表示可能存在问题

通过检查数据源的完整性和一致性,检查Debezium配置,检查列映射以及启用调试模式和日志记录,您应该能够解决这个错误。请注意,具体的解决方法可能因您的具体情况而异,因此您可能需要根据问题的根本原因进行适当的调整和修复。如果问题仍然存在,您可能需要进一步检查您的数据和配置,并根据具体情况进行调整和修复。在Debezium中,您可以使用列重命名和列映射来定义源和目标之间的列映射关系。通过检查日志,您可以查看详细的错误信息和堆栈跟踪,从而更好地理解问题的根本原因。首先,我们应该检查数据源的完整性和一致性。

XbtoMemory的博客 729

SpringBoot中循环依赖问题由浅入深看源码

Spring在启动类启动的时候,就会自动扫描所有加了对应注解的类,然后把它们通过反射,调用默认的构造方法以单例模式创建出来,然后存放在Map容器中。这个是传说中的IOC。 但是假设现在有这种情况: @Component public class A { public B b = new B(); public A(){ System.out.println("Bean A 的实例化依赖了 B:"+b); } } @Component public class B

whiteBearClimb的博客 707

Claude Code 100个真实案例 - 用AI批量清洗Excel脏数据(10万行3分钟搞定)

本案例展示如何使用 Claude Code 搭建一个强大的 Excel 数据清洗工具。支持自动检测和处理空值、重复行、格式错误、异常值等常见脏数据问题,批量处理10万行数据只需3分钟。

yueyeguzhuo的专栏 37

2020计算机专业保研夏令营面经:中科院计算所网数机试题目

计算所网络数据科学与技术重点实验室的第一阶段是机试。机试不限程序语言,要求提交源代码。

乔卿的博客 2957

springboot bean的循环依赖实现 源码分析

springboot bean的循环依赖实现 源码分析 本文基于springboot版本2.5.1 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version>

wbo 599

SAP特殊库存全解析:供应商寄售、客户寄售、分包与项目库存说明

客户寄售库存(Customer Consignment)是企业将物料存放于客户处,客户实际领用后才确认销售收入。这种模式能缩短交货周期、提高客户满意度,但需严格跟踪寄售库存的数量与状态。

我小时候很黑的博客 816

SpringBoot 扩展篇:ConfigFileApplicationListener源码解析

SpringBoot的配置文件加载由ConfigFileApplicationListener完成的,它会加载application.properties、application.yml等配置文件,还支持用户配置和扩展。本文从源码的角度分析它的原理。加载完毕的配置信息最终都会放入到Environment中。ConfigFileApplicationListener定义在spring.factories中。SpringBoot 源码解析3:事件监听器。

java_xiaoba1的博客 1361

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Spring源码 - Spring IOC 如何解决循环依赖

我们回到我们的例子,我们先处理的是 1对象(为了直观,之后将 CircleDependent1 简化描述为 1对象) 的逻辑不存在于三级缓存,所以通过 singletonFactory.getObject() 回调到匿名函数(回到前文可看到该匿名函数),调用 createBean 去创建 bean(此时 create ,而不是 get,注意这个差别,意味着这时候才是真正的创建逻辑,毕竟从缓存中没有拿到现有的只能去创建,命名十分合理)。我们知道,递归调用就会有递归调用的算法思路。

GDUT_xin的博客 680

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

SpringBoot 源码解析全集

本文只是纯源码分析文章,阅读者需要有Spring或者SpringBoot使用经验。

java_xiaoba1的博客 1327

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

【SpringBoot篇】SpringBoot WebFlux响应式编程WebClient深度解析:源码、原理、优缺点、扩展点与落地实战

WebClient是 Spring 5 及 SpringBoot 2.0+ 全新推出的响应式非阻塞HTTP客户端,隶属于 Spring WebFlux 响应式编程模块,是 Spring 官方为替代废弃的 RestTemplate 推出的现代化服务通信组件,全面适配响应式编程模型,适配微服务高并发、大流量、流式数据传输场景。区别于传统 Spring MVC 同步阻塞的 RestTemplate,WebClient 基于实现,遵循 Reactive Streams 响应式规范,支持。

荏苒夕阳的博客 305

【SpringBoot篇】SpringBoot WebFlux 全解:源码解析、实现原理、优劣特性、扩展机制与落地实战方案

SpringBoot WebFlux 是 Spring Framework 5.0 推出的完全响应式、非阻塞、异步Web 开发框架,基于 Reactive Streams 规范与 Reactor 核心实现,是 Spring MVC 的并行替代技术栈,专为高并发、高吞吐、低延迟、流式数据交互场景设计。区别于 Spring MVC 基于 Servlet 阻塞 IO 模型,WebFlux 彻底脱离 Servlet API 依赖,默认基于 Netty 事件循环模型实现请求处理,支持背压机制。

荏苒夕阳的博客 438

【SpringBoot篇】一文吃透SpringBoot自动装配原理:源码详解、实战案例及优缺点分析

SpringBoot 是目前 Java 后端主流开发框架绝大多数 Java 开发者日常开发都在使用SpringBoot自动装配,但多数开发者仅会使用,不了解SpringBoot自动装配底层源码执行,其最核心的优势是零XML配置、开箱即用、快速开发,彻底解决了传统 Spring 框架配置繁琐、Bean 需要手动注册、环境适配复杂、项目配置不统一等行业痛点。支撑 SpringBoot 极简开发能力的核心底层原理,就是SpringBoot自动装配机制,也是开发者学习 SpringBoot 底层源码的核心重点。

荏苒夕阳的博客 318

基于docker部署skywalking并实现SpringBoot全链路监控

基于docker部署skywalking并实现SpringBoot全链路监控一、安装环境部署1.1 Docker中安装Elasticsearch7.6.2(方式一)1.2 安装部署elasticsearch:7.10.1(方式二)修改配置文件启动elasticsearch1.3 安装oap1.4 安装ui1.5 下载源码包,会用到其中的agent二、SpringBoot集成Skywalking2.1 配置文件准备使用方式详细配置参考链接 一、安装环境部署 下载镜像: $ docker pull docker

爱是与世界平行 2689

【SpringBoot 从入门到架构师】第20章:SpringBoot核心源码解析

├─ 创建 BootstrapContext├─ 发布 ApplicationStartingEvent├─ 准备 Environment(加载配置)├─ 发布 ApplicationEnvironmentPreparedEvent├─ 创建 ApplicationContext├─ 准备 Context(加载 Bean 定义)├─ 发布 ApplicationPreparedEvent├─ refreshContext() // Spring 容器核心初始化。

gaochufukanyongren的博客 773

【SpringBoot篇】SpringBoot WebFlux响应式编程Scheduler完全解析:源码实现、优缺点、扩展点与落地实战方案

SpringBoot WebFlux是基于实现的非阻塞响应式Web框架,彻底颠覆了Spring MVC的Servlet阻塞线程模型。而Scheduler(调度器)是WebFlux响应式编程的核心线程调度基础设施,负责定义响应式数据流(Mono/Flux)的执行线程、执行时机、线程池策略与任务排队规则,是WebFlux实现高并发、低资源占用、非阻塞异步处理的核心根基。不同于传统Spring MVC一个请求绑定一个固定线程的模型,WebFlux通过Scheduler实现线程复用、任务解耦、异步调度、背压适配。

荏苒夕阳的博客 387

SpringBoot 从注解到源码:核心知识点总结

Component@Autowired@Component@Autowired其他还有很多种方式,如 A 依赖 B,B 依赖 C,C 依赖 A,或是 A 依赖 A 自己,只要产生了依赖关系的闭环,即造成了循环依赖。

2501_93103214的博客 231

springboot源码学习-画流程图学源码

最近加班比较多,spring源码已经将主线撸了一遍,将学到的通过画图再梳理一遍,非常有效。因此springboot也通过画流程图来学习: 一、Spring基于java-SPI扩展的SPI&spring boot启动和配置加载流程&条件装配&自定义属性类&自己写个中间件 https://www.processon.com/view/link/5f66bb8bf346fb40fb9b4b87 二、@springbootApplication(如何加载自动配置类、加载源码涉及的

qq_42282200的博客 660
上一篇: SpringBoot 源码解析7:Bean的创建② AbstractAutowireCapableBeanFactory#createBean
下一篇: SpringBoot 扩展篇:ConfigFileApplicationListener源码解析
hao9e
博客等级 码龄7年 189粉丝 51原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值