【Spring Boot 源码学习】深入 BootstrapContext 及其默认实现

news2024/12/25 2:29:34

《Spring Boot 源码学习系列》

在这里插入图片描述

深入 BootstrapContext 及其默认实现

  • 一、引言
  • 二、往期内容
  • 三、主要内容
    • 3.1 BootstrapContext
      • 3.1.1 源码初识
      • 3.1.2 get 方法
      • 3.1.3 getOrElse 方法
      • 3.1.4 getOrElseSupply 方法
      • 3.1.5 getOrElseThrow 方法
      • 3.1.6 isRegistered 方法
    • 3.2 ConfigurableBootstrapContext
    • 3.3 DefaultBootstrapContext
      • 3.3.1 源码初识
      • 3.3.2 实现 BootstrapRegistry 接口中的方法
      • 3.3.3 实现 BootstrapContext 接口中的方法
      • 3.3.4 close 方法
  • 四、总结

一、引言

书接前文《BootstrapRegistry 详解》,在介绍 BootstrapRegistry 的内部类 InstanceSupplierget 方法时,看到了它的唯一参数 BootstrapContext 接口【即引导上下文】。而这个接口及其默认实现就是本篇要重点介绍的对象,且听我娓娓道来。

在这里插入图片描述

二、往期内容

在开始本篇的内容介绍之前,我们先来看看往期的系列文章【有需要的朋友,欢迎关注系列专栏】:

Spring Boot 源码学习
Spring Boot 项目介绍
Spring Boot 核心运行原理介绍
【Spring Boot 源码学习】@EnableAutoConfiguration 注解
【Spring Boot 源码学习】@SpringBootApplication 注解
【Spring Boot 源码学习】走近 AutoConfigurationImportSelector
【Spring Boot 源码学习】自动装配流程源码解析(上)
【Spring Boot 源码学习】自动装配流程源码解析(下)
【Spring Boot 源码学习】深入 FilteringSpringBootCondition
【Spring Boot 源码学习】OnClassCondition 详解
【Spring Boot 源码学习】OnBeanCondition 详解
【Spring Boot 源码学习】OnWebApplicationCondition 详解
【Spring Boot 源码学习】@Conditional 条件注解
【Spring Boot 源码学习】HttpEncodingAutoConfiguration 详解
【Spring Boot 源码学习】RedisAutoConfiguration 详解
【Spring Boot 源码学习】JedisConnectionConfiguration 详解
【Spring Boot 源码学习】初识 SpringApplication
【Spring Boot 源码学习】Banner 信息打印流程
【Spring Boot 源码学习】自定义 Banner 信息打印
【Spring Boot 源码学习】BootstrapRegistryInitializer 详解
【Spring Boot 源码学习】ApplicationContextInitializer 详解
【Spring Boot 源码学习】ApplicationListener 详解
【Spring Boot 源码学习】SpringApplication 的定制化介绍
【Spring Boot 源码学习】BootstrapRegistry 详解

三、主要内容

注意: 以下涉及 Spring Boot 源码 均来自版本 2.7.9,其他版本有所出入,可自行查看源码。

3.1 BootstrapContext

3.1.1 源码初识

public interface BootstrapContext {
	<T> T get(Class<T> type) throws IllegalStateException;
	<T> T getOrElse(Class<T> type, T other);
	<T> T getOrElseSupply(Class<T> type, Supplier<T> other);
	<T, X extends Throwable> T getOrElseThrow(Class<T> type, Supplier<? extends X> exceptionSupplier) throws X;
	<T> boolean isRegistered(Class<T> type);
}

BootstrapContext 是一个简单的引导上下文,它在启动期间以及环境后处理过程中可用,直到应用上下文 ApplicationContext 准备就绪。

它提供了对可能创建成本高昂的单例的延迟访问,或者在 ApplicationContext 可用之前需要共享的单例。

它一共包含 5 个方法,下面分别来介绍下:

3.1.2 get 方法

get 方法,只有一个参数:

  • Class<T> type :实例类型

该方法用于返回一个指定类型的实例对象。如果类型已在上下文中注册,则从上下文中返回一个实例。如果之前未访问过该实例,则会创建它。

有关其具体实现,可查看 3.3 小节【BootstrapContext 的默认实现】

3.1.3 getOrElse 方法

getOrElse 方法,包含两个参数:

  • Class<T> type :实例类型
  • T other :如果上述类型还未注册,则使用该实例进行返回

该方法用于返回一个指定类型的实例对象。如果类型已在上下文中注册,则从上下文中返回一个实例。如果之前未注册过该实例,则直接用第二个参数 other 进行返回【这里跟 get 方法有所区别】。

有关其具体实现,可查看 3.3 小节【BootstrapContext 的默认实现】

3.1.4 getOrElseSupply 方法

getOrElseSupply 方法,也包含两个参数:

  • Class<T> type :实例类型
  • Supplier<T> other :如果上述类型还未注册,则使用该提供者返回指定实例对象

该方法用于返回一个指定类型的实例对象。如果类型已在上下文中注册,则从上下文中返回一个实例。如果之前未注册过该实例,则用 other.get() 进行返回【这里类似 getOrElse 方法,其实默认实现中 getOrElse 就是调用 getOrElseSupply 进行返回的】。

有关其具体实现,可查看 3.3 小节【BootstrapContext 的默认实现】

3.1.5 getOrElseThrow 方法

getOrElseThrow 方法,同样也包含两个参数:

  • Class<T> type :实例类型
  • Supplier<? extends X> exceptionSupplier :如果上述类型还未注册,则使用该提供者抛出指定的异常

XThrowable 的子类,如果上述类型还未注册过,则将抛出 X 或者 X 的子类。

该方法用于返回一个指定类型的实例对象。如果类型已在上下文中注册,则从上下文中返回一个实例。如果之前未注册过该实例,则通过 throw exceptionSupplier.get() 将指定异常抛出【这个在 默认实现 DefaultBootstrapContext 中即可看到】。

3.1.6 isRegistered 方法

isRegistered 方法,只有一个参数:

  • Class<T> type :实例类型

该方法用于判断指定的类型是否已经被注册过。如果已经在上下文中注册过了,则返回 true;否则,返回false。

有关其具体实现,可查看 3.3 小节【BootstrapContext 的默认实现】

3.2 ConfigurableBootstrapContext

public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext {

}

通过阅读 ConfigurableBootstrapContext 源码,我们可以看到它继承了 BootstrapRegistryBootstrapContext 接口。这也就意味着 ConfigurableBootstrapContext 接口同时拥有了这两者的所有功能,即它是一个可配置的引导上下文。

对于开发人员来讲,只需要实现这个接口,并编写相应实现代码,就可以来配置和管理应用程序的引导过程。当然 Spring Boot 显然已经帮我们考虑了,这也就是下面 Huazie 将要介绍的引导上下文的默认实现 DefaultBootstrapContext

3.3 DefaultBootstrapContext

在 《BootstrapRegistryInitializer 详解》 的 3.1 小节,我们提到了 BootstrapRegistry 的一个默认实现 DefaultBootstrapContext ,下面我们就来深入分析一下。

3.3.1 源码初识

话不多说,直接翻看对应的源码:

public class DefaultBootstrapContext implements ConfigurableBootstrapContext {

	private final Map<Class<?>, InstanceSupplier<?>> instanceSuppliers = new HashMap<>();

	private final Map<Class<?>, Object> instances = new HashMap<>();

	private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster();

	// 实现 BootstrapRegistry 接口中的方法

	// 实现 BootstrapContext 接口中的方法

	public void close(ConfigurableApplicationContext applicationContext) {
		this.events.multicastEvent(new BootstrapContextClosedEvent(this, applicationContext));
	}
}

上述源码中,Huazie 省略了有关实现 BootstrapRegistryBootstrapContext 接口中的方法,这些内容将会在下面的小节详细深入分析。

我们从类开头,一下子就能看到三个私有的,不可变的成员变量:

  • Map<Class<?>, InstanceSupplier<?>> instanceSuppliers :这是个 HashMap,它的键是 Class 对象,值是 InstanceSupplier 对象【它是 BootstrapRegistry 中一个内部接口类,用于提供实际的实例,具体内容可以查看 Huazie 的上一篇博文 】
  • Map<Class<?>, Object> instances :这同样也是个 HashMap,它的键是 Class 对象,值是 Object 对象【即对应的实际实例对象】
  • ApplicationEventMulticaster events :它是 Spring 框架中的一个组件,用于管理和广播应用程序事件。SimpleApplicationEventMulticaster 是其一个简单的实现。

注意: SimpleApplicationEventMulticaster 会将所有的事件广播给所有已注册的监听器,而由监听器自行决定忽略它们不感兴趣的事件。监听器通常会在传入的事件对象上进行相应的 instanceof 检查。
默认情况下,所有的监听器都在调用线程中被调用。这允许存在一个恶意的监听器阻塞整个应用程序的风险,但增加了最小的开销。如果指定了替代的任务执行器,可以让监听器在不同的线程中执行,例如来自一个线程池。

3.3.2 实现 BootstrapRegistry 接口中的方法

在 《BootstrapRegistry 详解》中,我们已经了解相关的 5 个方法,下面直接看 DefaultBootstrapContext 中的实现:

	@Override
	public <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier) {
		register(type, instanceSupplier, true);
	}

	@Override
	public <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier) {
		register(type, instanceSupplier, false);
	}

	private <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier, boolean replaceExisting) {
		Assert.notNull(type, "Type must not be null");
		Assert.notNull(instanceSupplier, "InstanceSupplier must not be null");
		synchronized (this.instanceSuppliers) {
			boolean alreadyRegistered = this.instanceSuppliers.containsKey(type);
			if (replaceExisting || !alreadyRegistered) {
				Assert.state(!this.instances.containsKey(type), () -> type.getName() + " has already been created");
				this.instanceSuppliers.put(type, instanceSupplier);
			}
		}
	}

	@Override
	public <T> boolean isRegistered(Class<T> type) {
		synchronized (this.instanceSuppliers) {
			return this.instanceSuppliers.containsKey(type);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type) {
		synchronized (this.instanceSuppliers) {
			return (InstanceSupplier<T>) this.instanceSuppliers.get(type);
		}
	}

	@Override
	public void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener) {
		this.events.addApplicationListener(listener);
	}

翻看上述源码,我们可以看到除了 addCloseListener 方法,其他方法中都使用 synchronized 关键字了,而这里同步的对象就是上面提到的 instanceSuppliers。因为 instanceSuppliersHashMap,它并不是线程安全的,为了防止多个线程同时修改 instanceSuppliers 对象,导致数据不一致的问题,这里就需要对该对象进行同步,保证在同一时刻只有一个线程可以访问该代码块。

从源码中,我们可以看出 registerregisterIfAbsent 方法的区别:

registerIfAbsent 只会在该类型尚未注册过时,才注册该类型。而 register 即使该类型已经注册过了,也会重新注册该类型。

isRegistered 方法比较特殊,它在 BootstrapRegistryBootstrapContext 接口中均可以看到。其实现也不难理解,通过 MapcontainsKey 方法,判断给定类型是否已注册。如果给定类型已经注册,则返回 true,否则,返回 false

getRegisteredInstanceSupplier 方法也比较简单,直接从 instanceSuppliers 中获取指定类型的供应者。

addCloseListener 方法,用于添加一个监听器,该监听器用于监听 BootstrapContextClosedEvent,这块后续 Huazie 会带大家实践一下。

3.3.3 实现 BootstrapContext 接口中的方法

	@Override
	public <T> T get(Class<T> type) throws IllegalStateException {
		return getOrElseThrow(type, () -> new IllegalStateException(type.getName() + " has not been registered"));
	}

	@Override
	public <T> T getOrElse(Class<T> type, T other) {
		return getOrElseSupply(type, () -> other);
	}

	@Override
	public <T> T getOrElseSupply(Class<T> type, Supplier<T> other) {
		synchronized (this.instanceSuppliers) {
			InstanceSupplier<?> instanceSupplier = this.instanceSuppliers.get(type);
			return (instanceSupplier != null) ? getInstance(type, instanceSupplier) : other.get();
		}
	}

	@Override
	public <T, X extends Throwable> T getOrElseThrow(Class<T> type, Supplier<? extends X> exceptionSupplier) throws X {
		synchronized (this.instanceSuppliers) {
			InstanceSupplier<?> instanceSupplier = this.instanceSuppliers.get(type);
			if (instanceSupplier == null) {
				throw exceptionSupplier.get();
			}
			return getInstance(type, instanceSupplier);
		}
	}

	@SuppressWarnings("unchecked")
	private <T> T getInstance(Class<T> type, InstanceSupplier<?> instanceSupplier) {
		// 省略 。。。
	}
	

3.1 小节,我们了解了 BootstrapContext4 个获取方法。通过查看上述的源码,我们看到这里只需要分析 getOrElseSupplygetOrElseThrow 的实现即可。

同样在 getOrElseSupplygetOrElseThrow 方法中,我们看到了 synchronized (this.instanceSuppliers),这里同 3.3.2 中讲解的一样,都是为了防止多个线程同时修改 instanceSuppliers 对象,导致数据不一致的问题。

getOrElseSupply 方法的实现也比较简单。如果指定类型的供应者存在,则通过 getInstance 方法从这个供应者中获取对应类型的实例对象;否则,直接从提供者 other 参数中获取。

getOrElseThrow 方法的实现也好理解。如果指定类型的供应者不存在,则直接从异常供应者中获取一个异常类,并将该异常抛出去即可;否则,通过 getInstance 方法从这个供应者中获取对应类型的实例对象。

很显然,上述方法最终都需要使用 getInstance 方法,从供应者中获取对应类型的实例对象。我们来看看相关的源码:

	T instance = (T) this.instances.get(type);
	if (instance == null) {
		instance = (T) instanceSupplier.get(this);
		if (instanceSupplier.getScope() == Scope.SINGLETON) {
			this.instances.put(type, instance);
		}
	}
	return instance;

简单总结如下:

  • 首先,从 instances 中获取指定类型的实例对象 instance
  • 接着,如果 instance 不存在,则继续。
    • 从实例供应者 instanceSupplier 中获取一个实例对象,并赋值给 instance
    • 如果实例供应者 instanceSupplier 指定的作用域是单例,则将获取的实例对象添加到 instances 中,方便后续直接获取。
  • 最后,直接返回指定类型的实例对象 instance

3.3.4 close 方法

BootstrapContext 被关闭且 ApplicationContext 已准备好时,该方法将被调用【后续笔者讲解 Spring Boot 的启动引导过程会涉及到】。

通过阅读源码,我们可以看到这里触发了一个名为 BootstrapContextClosedEvent 的事件,该事件会多播给所有注册了该事件的监听器,而这些监听器就是通过 3.3.2 小节中提到的 addCloseListener 方法添加的【后续 Huazie 会带大家实操一下】。

四、总结

本篇 Huazie 带大家深入了解了 BootstrapContext 及其默认实现,这些内容对我们理解 Spring Boot 的启动引导过程至关重要。下篇 Huazie 将通过自定义 BootstrapRegistry 初始化器实现,来看看引导上下文在 Spring Boot 的启动引导过程中的作用。

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

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

相关文章

搜维尔科技:用于运动科学的 OptiTrack,范围标记、步态捕捉!

OptiTrack 系统提供世界领先的测量精度和简单易用的工作流程&#xff0c;为研究人员和生物力学师的研究提供理想的 3D 跟踪数据。 对所有主要数字测力台、EMG 和模拟设备的本机即插即用支持为研究人员提供了在 Visual3D、MotionMonitor、MATLAB 和其他第三方生物力学软件包中进…

leetcode有效的括号-中等

题目描述 解题思路 不要被事例骗到&#xff0c;没有事例中写的那么简单&#xff0c;其中还包括&#xff08;[ ]&#xff09;这种例子。如果是计算机本专业的同学来说&#xff0c;一眼看出来&#xff0c;需要使用栈的数据结构。在python中需要使用列表进行。如果匹配的是左括号…

这几个Python内置函数你都知道吗

divmod() divmod() 是一个 Python 内置函数&#xff0c;用于同时返回商和余数。它接受两个参数&#xff0c;第一个参数是被除数&#xff0c;第二个参数是除数&#xff0c;返回一个包含两个值的元组&#xff0c;第一个值是商&#xff0c;第二个值是余数。 示例用法如下&#…

Linux使用Docker部署Nacos容器并结合内网穿透实现公网访问本地服务

文章目录 推荐1. Docker 运行Nacos2. 本地访问Nacos3. Linux安装Cpolar4. 配置Nacos UI界面公网地址5. 远程访问 Nacos UI界面6. 固定Nacos UI界面公网地址7. 固定地址访问Plik 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff…

计算机组成原理 — 存储器(2)

高速缓冲存储器 大家好呀&#xff01;我是小笙&#xff0c;由于存储器这部分章节内容较多&#xff0c;我分成二部分进行总结&#xff0c;以下是第二部分&#xff0c;希望内容对你有所帮助&#xff01; 概述 目的&#xff1a;避免CPU空等现象 原理&#xff1a;程序访问的局部…

基于yolov2深度学习网络的火焰烟雾检测系统matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 .................................................................. load yolov2.mat% 加载…

【Docker】03 容器操作

文章目录 一、流转图二、基本操作2.1 查看本地容器进程2.2 启动容器2.2.1 交互式启动容器2.2.2 后台启动容器 2.3 进入容器2.4 停止启动重启容器2.5 退出容器2.6 删除容器2.7 提交容器&#xff08;打包成镜像&#xff09;2.8 拷贝文件2.8.1 拷贝容器内文件到宿主机2.8.2 拷贝宿…

机器学习理论知识学习

机器学习介绍 让机器有能力去根据数据学习 不是人类的if和else控制 机器学习分类 监督学习: 最成熟,落地案例最多。我们的视觉处理大多数都是监督学习。 小孩学习,类似监督学习,这是猫,这是狗,这是兔子 非监督学习:找到不同类型的数据,kmeans 聚类算法 帮助梳理不…

软考47-上午题-【数据库】-数据查询语言DQL2

一、聚合函数 聚合函数实现数据统计的功能&#xff0c;返回一个单一的值。聚合函数一般与select语句的group by子句一起使用。 示例&#xff1a; 二、数据分组-group by 聚合函数加上group by子句进行分组。 通常一个聚合函数的作用范围是满足where子句中指定条件的记录&…

【刷题】leetcode 1544.整理字符串

刷题 1544.整理字符串思路一&#xff08;模拟栈速解版&#xff09;思路二 &#xff08;原地算法巧解版&#xff09;思路三&#xff08;C栈版&#xff09; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff…

罗克韦尔AB的PLC实现ModbusTCP和ModbusRTU协议标签方式通讯

本文是通过IGT-DSER智能网关读写AB罗克韦尔Compact、Control系列PLC的标签数据缓存并转为Modbus从站协议&#xff0c;与上位机通讯的案例。 打开智能网关的参数软件(下载地址)&#xff0c;通过功能->数据转发与平台对接&#xff0c;再选择数据转发与缓存’&#xff0c;进入以…

探索Allure Report:提升自动化测试效率的秘密武器!

一.使用 Allure2 运行方式-Python # --alluredir 参数生成测试报告。 # 在测试执行期间收集结果 pytest [测试用例/模块/包] --alluredir./result/ (—alluredir这个选项 用于指定存储测试结果的路径)# 生成在线的测试报告 allure serve ./result二.使用 Allure2 运行方式-Ja…

Elasticsearch 创建index库 timeout

问题概述 使用 python 客户端 代码进行创建,【之前成功创建,但是现在出现报错,报错代码es_connection.client.indices.create】def create_vector_index(dataset_index_name,vector_query_field,query_field):es_connection = get_collention(dataset_index_name,vector_que…

Linux调用可执行程序:system()函数和execl函数

system()函数&#xff1a; system()函数是一个在C/C编程语言中的库函数&#xff0c;用于在操作系统中执行命令。 函数声明如下&#xff1a; int system(const char *command);该函数接受一个指向以空字符结尾的字符串的指针作为参数&#xff0c;该字符串包含要执行的命令。函…

福特锐界2021plus 汽车保养手册

福特锐界2021plus汽车保养手册两页&#xff0c;零部件保养要求&#xff0c;电子版放这里方便查询&#xff1a;

2024.02.23作业

1. 尝试处理普通信号 #include "test.h"#define MAXSIZE 128void handler(int signo) {if (SIGINT signo){printf("用户按下了 ctrl c 键\n");} }int main(int argc, char const *argv[]) {if (signal(SIGINT, SIG_IGN) SIG_ERR){perror("signal …

【C++】---内存管理new和delete详解

一、C/C内存分布 C/C内存被分为6个区域&#xff1a; &#xff08;1&#xff09; 内核空间&#xff1a;存放内核代码和环境变量。 &#xff08;2&#xff09;栈区&#xff1a;向下增长&#xff08;存放非静态局部变量&#xff0c;函数参数&#xff0c;返回值等等&#xff09; …

帧同步原理

帧同步和状态同步区别 状态同步&#xff1a;发操作&#xff0c;收状态 帧同步&#xff1a;发操作&#xff0c;收操作 逻辑严格排序 经常会有需要排序的列表或者数组&#xff0c;比如攻击距离自己最近的敌人&#xff0c;这时候就需要将身边的敌人进行距离排序&#xff0c;一般…

在having、select子句中使用子查询

目录 在having子句中使用子查询 统计出部门平均工资高于公司平均工资的部门编号、平均工资、部门人数 在select子句中使用子查询 查询每个员工的编号、姓名、职位、部门名称 Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 在havin…

2024.2.26

1、实现信号灯集 sem.c #include<myhead.h>union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer …