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

《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 中的实现:

	@Overridepublic <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier) {register(type, instanceSupplier, true);}@Overridepublic <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);}}}@Overridepublic <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);}}@Overridepublic 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 接口中的方法

	@Overridepublic <T> T get(Class<T> type) throws IllegalStateException {return getOrElseThrow(type, () -> new IllegalStateException(type.getName() + " has not been registered"));}@Overridepublic <T> T getOrElse(Class<T> type, T other) {return getOrElseSupply(type, () -> other);}@Overridepublic <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();}}@Overridepublic <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.mzph.cn/news/704400.shtml

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

相关文章

构建企业多维模型,助力财务战略规划

财务规划与分析是一个企业需要广泛实践、改革优化的领域。战略规划对于财务管理来说&#xff0c;对企业的未来发展具有长期且不可逆的影响。一般企业在做重要战略决策时面临的主要挑战是对于可能结果复杂性的预测&#xff0c;其干涉因素很多&#xff0c;规划范围也十分广泛&…

【Unity】如何从现有项目中抽取好用的资源

【背景】 在做Unity项目的过程中引入各种各样的Package&#xff0c;有的Package很大&#xff0c;但是觉得非常有用的可能只是几个Prefab或者Material等。如果直接拷贝想要的Prefab和Material&#xff0c;又需要自己确认所有有依赖关系的资源。 如果能将所有日常经受项目中自己…

cgroup底层技术研究一、cgroup简介与cgroup命令行工具

本文参考以下文章&#xff1a; 58 | cgroup技术&#xff1a;内部创业公司应该独立核算成本 特此致谢&#xff01; 一、cgroup简介 1. cgroup是什么 cgroup&#xff08;Control Group&#xff09;是Linux内核提供的一种机制&#xff0c;用于对进程或进程组进行资源限制、优先…

python[6]

类和对象 面向对象编程–说白就是让对象干活 创建类&#xff1a;class 类名&#xff1a; 创建类对象 对象名 类名&#xff08;&#xff09; 构造方法 1、构造方法的名称是__init__ 2、构造方法的作用&#xff1f; 构建类对象的时候会自动运行 构建类对象的传参会传递给构造…

react+canvas实现刮刮乐效果

话不多说&#xff0c;直接看代码吧 import { useEffect } from react; import styles from ./index.less;export default function Canvas() {function init() {let gj document.querySelector(.gj);let jp document.querySelector(#jp) as HTMLElement;let canvas documen…

mac flutter 配置

下载Flutter Sdk 直接访问官网无法下载&#xff0c;需要访问中国镜像下载 Flutter SDK 归档列表 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter Start building Flutter Android apps on macOS - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 下载后解压…

Java+SpringBoot+Vue+MySQL:美食推荐系统的技术革新

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

基于STM32的蓝牙遥控小车:手机APP控制小车运动

这里写目录标题 一、智能双轮小车蓝牙APP控制前后左右调速运动功能描述原理图设计PCB设计源码设计 二、更多功能小车网友推荐后开发... 一、智能双轮小车蓝牙APP控制前后左右调速运动 功能描述 1、小车可以前后左右运动 2、可以调节小车的速度 3、使用两个电机驱动两个轮子&a…

Go语言必知必会100问题-06 生产者端接口

生产者端接口 Go语言必知必会100问题-05 接口污染中介绍了程序中使用接口是有价值的。在编码的时候&#xff0c;接口应该放在哪里呢&#xff1f;这是Go开发人员经常有误解的一个问题&#xff0c;本文将深入分析该问题。 在深入探讨问题之前&#xff0c;先对提及的术语做一个定…

MySQL(2/3)

select和别名的使用 主要是用以查询数据 语法&#xff1a;select 字段 from 库名 -- *代表全部字段 select * from student; -- 可以查询多个字段&#xff0c;并使用as起别名&#xff0c;as可以省略 select id as bbb ,name as hhh from student; -- 可以使用函数concat(a,b…

新版Java面试专题视频教程——虚拟机篇②

新版Java面试专题视频教程——虚拟机篇② 3 垃圾收回3.1 简述Java垃圾回收机制&#xff1f;&#xff08;GC是什么&#xff1f;为什么要GC&#xff09;3.2 对象什么时候可以被垃圾器回收3.2.1 引用计数法3.2.2 可达性分析算法 3.3 JVM 垃圾回收算法有哪些&#xff1f;——4种3.3…

Python实现DAS单点登录

❇️ 流程 进入登录页面 &#xff08;DAS验证的登录页面&#xff09; 获取验证码图像&#xff0c;百度OCR识别 登录 &#x1f3de;️ 环境 Windows 11 Python 3.12 PyCharm 2023 &#x1f9f5; 准备工作 安装必要依赖库 bs4 Jupyter 推荐安装 Jupyter&#xff08;Anaco…

数学建模【相关性模型】

一、相关性模型简介 相关性模型并不是指一个具体的模型&#xff0c;而是一类模型&#xff0c;这一类模型用来判断变量之间是否具有相关性。一般来说&#xff0c;分析两个变量之间是否具有相关性&#xff0c;我们根据数据服从的分布和数据所具有的特点选择使用pearson&#xff…

Linux系统——Nginx小总结

目录 一、影响用户体验的因素 二、网络连接——Apache/Nginx服务请求过程 三、I/O模型——Input/Output模型 1.同步/异步 2.阻塞/非阻塞 3.同步异步/阻塞非阻塞组合 四、Nginx用法 一、影响用户体验的因素 客户端硬件配置客户端网络速率客户端与服务端距离服务端网络速…

【JS】事件绑定方法自带一个形参e“function(e)”,what is e?

在学习js的时候 我跳过了一部分章节的内容&#xff0c;导致现在学习react的时候很多内容都不知所措&#xff0c;因为这些教程都是建立在它认为你js所有内容都掌握的前提下&#xff0c;当然这是我自身的原因。需要反省。 下面是正题&#xff1a; 我们知道js有很多事件&#…

【一】【SQL】表的增删查改(部分)

表之“增”操作 建表的操作 mysql> create table students(-> id int unsigned primary key auto_increment,-> sn int unsigned unique key,-> name varchar(20) not null,-> qq varchar(32) unique key-> ); Query OK, 0 rows affected (0.03 sec)mysql&g…

Day01:Web应用架构搭建站库分离路由访问配置受限DNS解析

目录 常规的Web应用搭建 三种常规网站搭建模式 程序源码 中间件配置 数据库类型 文件访问路径 总结 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&#xff1a;文件…

Pytorch添加自定义算子之(1)-安装配置Eigen库

一、安装对应的ubuntu环境 推荐使用Docker FROM nvcr.io/nvidia/pytorch:23.01-py3 RUN pip install tensorboardX RUN pip install pyyaml RUN pip install yacs RUN pip install termcolor RUN pip install opencv-python RUN pip install timm0.6.12 WORKDIR /app COPY . …

Python入门必学:print函数--从基础语法到高级用法

Python入门必学&#xff1a;print函数–从基础语法到高级用法 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您…

2024 春招市场行情报告:鸿蒙人才遭“爆抢”

前言 2024年可以说是布道鸿蒙开发行业的最佳时机&#xff0c;华为在千帆启航仪式会中发布会中表示&#xff0c;已有200家头部企业加入原生开发当中&#xff0c;并且一直有高薪抢人的局面&#xff0c;这一信息已经引起业界很大关注。 因此有很多公司开始准备要招聘鸿蒙工程师&…