Spring Boot 项目启动时在 prepareContext 阶段做了哪些事?

概览

如果你对Spring Boot 启动流程还不甚了解,可阅读《Spring Boot 启动流程详解》这篇文章。如果你已了解,那就让我们直接看看prepareContext() 源码。

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	//将环境对象放到上下文中
	context.setEnvironment(environment);
	//后置处理应用上下文
	postProcessApplicationContext(context);
	//应用初始化上下文
	applyInitializers(context);
	//触发监听器‘上下文准备完成’时间
	listeners.contextPrepared(context);
	//打印启动信息和激活的profile
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	//将 applicationArguments 和 printedBanner 实例注册到bean工厂
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	//是否设置bean懒加载
	if (this.lazyInitialization) {
		//是则beanFactory后置处理添加LazyInitializationBeanFactoryPostProcessor实例
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// Load the sources
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	//加载sources
	load(context, sources.toArray(new Object[0]));
	//触发监听器‘上下文加载完毕’事件
	listeners.contextLoaded(context);
}

通过上面的源码我们可以看出,在准备上下文阶段,spring boot 做了很多事情,这其中主要包括以下内容:

  1. postProcessApplicationContext(),后置处理应用上下文。
  2. applyInitializers(),‘应用’初始化上下文。
  3. listeners.contextPrepared(),触发监听器的‘应用上下文准备完成’事件。
  4. 懒加载模式为 beanFactory 添加 LazyInitializationBeanFactoryPostProcessor() 后置处理器。
  5. 加载sources,包含 primarySources 和自定义的 sources。
  6. listeners.contextLoaded(),触发监听器的‘上下文加载完成’事件。

接下来让我们详细的看看每个步骤具体做了什么。

后置处理应用上下文

postProcessApplicationContext() 上下文的后置处理,这里由于 beanNameGenerator 和 resourceLoader 都为null,addConversionService 为true,故仅对bean工厂设置了 conversionService 转换服务。这里的转换服务为 ApplicationConversionService 类的单例实例。bean工厂为 GenericApplicationContext 无参构造函数创建的 DefaultListableBeanFactory()。

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
	if (this.beanNameGenerator != null) {
		context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
				this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		if (context instanceof GenericApplicationContext) {
			((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
		}
		if (context instanceof DefaultResourceLoader) {
			((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
		}
	}
	if (this.addConversionService) {
		context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
	}
}

‘应用’初始化器

@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
				ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		initializer.initialize(context);
	}
}

applyInitializers()主要是执行SpringApplication对象实例化阶段从spring.factories配置文件中读取并加载的ApplicationContextInitializer接口实现类的initialize()方法。从方法的注释上看主要是在上下文刷新之前做一些操作。spring-boot-x.x.x.jar 默认的spring.factories中包含以下这些实现类:

DelegatingApplicationContextInitializer执行一些其他的ApplicationContextInitializer,这些ApplicationContextInitializer来源于context.initializer.classes属性配置;相当一些自定义的初始化器委托给DelegatingApplicationContextInitializer执行
ContextIdApplicationContextInitializer构建一个上下文ContextId对象并注册到bean工厂中,这里的id一般为从environment环境对象中读取的spring.application.name属性配置,因此一般就是你设置的项目名
ConfigurationWarningsApplicationContextInitializer添加一个ConfigurationWarningsPostProcessor后置处理器到bean工厂后置处理器中;该后置处理器主要校验@ComponentScan扫描的包或类所在包是否包含org.springframework或org,包含则进行warning级别的提示
RSocketPortInfoApplicationContextInitializer将上下文对象包装成一个listener监听器添加到当前应用上下文的applicationListeners监听器列表中;包装的listener主要监听了RSocketServerInitializedEvent事件,将获取的端口设置到类型名为server.ports的属性map中,key为local.rsocket.server.port
ServerPortInfoApplicationContextInitializer将自身添加到当前应用上下文的applicationListeners监听器列表中;ServerPortInfoApplicationContextInitializer本身也实现的ApplicationListener接口,主要监听了WebServerInitializedEvent事件

以上表格的ApplicationContextInitializer都是按优先级排过序的,程序执行时就是从上到下执行。另外实际断点的过程中,我们可以看到除了以上的initializer之外,还有一些其他jar包中的initializer,如下:

  • org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
    • 将CachingMetadataReaderFactoryPostProcessor添加到上下文对象的beanFactoryPostProcessors列表中
  • io.dubbo.springboot.DubboConfigurationApplicationContextInitializer
    • environment环境中是否设置spring.dubbo.scan属性,如果设置了则实例化一个AnnotationBean,并注册到bean工厂中,同时添加到上下文对象的bean工厂后置处理器列表中和beanFactory对象的bean后置处理器列表中。
  • org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    • 构建一个ConditionEvaluationReportListener监听器并添加到当前应用上下文的applicationListeners监听器列表中;同时从bean工厂中获取ConditionEvaluationReport。ConditionEvaluationReportListener监听所有的ApplicationEvent事件并触发ConditionEvaluationReportLoggingListener的onApplicationEvent()方法。主要目的打印条件评估报告,以帮助开发者了解Spring Boot 启动过程中自动配置的细节。

实际执行顺序如下图所示:

这里解释下GenericTypeResolver.resolveTypeArgumen() 方法作用,其主要是解析initializer类实现泛型接口的泛型类型,从而判断当前context是否是initializer类实现接口的泛型类型的子类。这里有点绕口,大家可以理解为 ApplicationContextInitializer 接口的泛型类型必须和context接口类型一致,代码上看ApplicationContextInitializer接口的泛型类型就是ConfigurableApplicationContext 类型,所以感觉这里的判断有点多此一举。

触发监听器‘上下文准备完成’事件

listeners.contextPrepared() 表示在上下文准备完成阶段要触发的操作。该事件触发的流程与listeners.starting() 执行流程一致,这里不再讲解,如不清楚可先看看《Spring Boot 启动流程详解》这篇文章。通过打断点可以看到,这里监听了 ApplicationContextInitializedEvent 事件的监听器有:BackgroundPreinitializer、DelegatingApplicationListener、DubboHolderListener;实际这三个listener 都未有 ApplicationContextInitializedEvent 事件的处理逻辑,这里之所有会被扫描出来,是因为其实现的接口泛型类型为 ApplicationContextInitializedEvent 的父类或者未指定,其中 DubboHolderListener 属于未指定。

打印启动信息和激活的profile

上面表格之所以没有列这一步,是因为这里只是打印启动信息及激活profile,没有这一步骤对框架执行并不影响,所以也不重要。

注册 ApplicationArguments 和 Banner bean,设置bean是否可以被覆盖

这里通过 DefaultListableBeanFactory 将 ApplicationArguments 和 Banner 实例对象以单例的方式进行注入,同时设置不允许 bean 被覆盖,让我们看看 registerSingleton() 方法源码:

@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
	//调用父类DefaultSingletonBeanRegistry的registerSingleton() 方法
	super.registerSingleton(beanName, singletonObject);
	//更新工厂内部维护的单例bean名称
	updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
	//清除映射
	clearByTypeCache();
}

源码很简单,调用父类的 DefaultSingletonBeanRegistry 的 registerSingleton() 方法,让我再看看 DefaultSingletonBeanRegistry 类的 registerSingleton() 方法。

@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
	Assert.notNull(beanName, "Bean name must not be null");
	Assert.notNull(singletonObject, "Singleton object must not be null");
	synchronized (this.singletonObjects) {
		Object oldObject = this.singletonObjects.get(beanName);
		if (oldObject != null) {
			throw new IllegalStateException("Could not register object [" + singletonObject +
					"] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
		}
		addSingleton(beanName, singletonObject);
	}
}

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);
	}
}

其逻辑也很简单,就是先加锁,再判断内部维度单例bean的map是否包含当前bean,包含则抛出异常,单例bean不能被重复创建。如果不存在,则加到缓存map中。

加载sources

先看看源码~

protected void load(ApplicationContext context, Object[] sources) {
	if (logger.isDebugEnabled()) {
		logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
	}
	//创建一个BeanDefinitionLoader用于加载bean
	BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
	if (this.beanNameGenerator != null) {
		loader.setBeanNameGenerator(this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		loader.setResourceLoader(this.resourceLoader);
	}
	if (this.environment != null) {
		loader.setEnvironment(this.environment);
	}
	//进行bean的装载
	loader.load();
}

getBeanDefinitionRegistry() 方法用于从上下文中获取bean工厂,断点可以看到返回的是context自身,因为其实现了BeanDefinitionRegistry接口。createBeanDefinitionLoader() 方法就是调用 BeanDefinitionLoader() 的构造函数,函数入参包含当前上下文context 和 sources,这里sources主要就是 primarySources,而 primarySources 就是Spring Boot 启动类。让我们再看看 BeanDefinitionLoader() 构造函数。

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
	Assert.notNull(registry, "Registry must not be null");
	Assert.notEmpty(sources, "Sources must not be empty");
	this.sources = sources;
	//创建注解bean定义reader
	this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
	//创建xml bean定义reader
	this.xmlReader = new XmlBeanDefinitionReader(registry);
	if (isGroovyPresent()) {
		//如果有加载groovy.lang.MetaClass,在创建groovy定义bean的reader
		this.groovyReader = new GroovyBeanDefinitionReader(registry);
	}
	//创建类路径bean定义扫描器
	this.scanner = new ClassPathBeanDefinitionScanner(registry);
	//添加类排除过滤器
	this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

BeanDefinitionLoader 构造函数主要就是创建各种bean定义解析器用于bean的定义加载。接下来就是调用创建好的 BeanDefinitionLoader 的 load() 方法,其主要作用就是加载定义的bean,包括注解定义、xml定义、groovy脚本定义以及classPath定义。由于这部分实现比较复杂,这里不做详细说明,后面会单独写一篇文章进行详细介绍。

触发监听器‘上下文加载完成’事件

一样的事件处理流程就不再多说了,这里主要看看执行了哪些listener的相应事件。同样断点可以看到有如下监听器监听了 ApplicationPreparedEvent 事件,这里也通过表格展示下每个监听器具体干了什么。

CloudFoundryVcapEnvironmentPostProcessor从内部定义的延迟日志DeferredLog切换到实时日志并进行打印
ConfigFileApplicationListener添加一个属性源后置处理 PropertySourceOrderingPostProcessor 到bean工厂后置处理器列表中
LoggingApplicationListener注册springBootLoggingSystem、springBootLogFile、springBootLoggerGroups等相关bean
BackgroundPreinitializer无响应的事件处理
DelegatingApplicationListener无响应的事件处理
DubboHolderListenerspring-boot-dubbo-starter-1.0.0.jar中的监听器,内部啥也没干。。。

至此,spring boot 在启动时的上下文准备阶段主要就干了这些事。

注:spring boot 版本为2.3.10

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Knife4j 全局鉴权需求 (在OpenAPI3规范中添加Authorization鉴权请求Header)

文章目录 引言I Knife4j 全局鉴权需求1.1 利用springdoc项目提供的customizer接口解决1.2 常见问题II 添加自定义Header参数(签名字段)see also引言 OpenAPI3规范对于Security的定义说明,主要分为两部分: 在compoents组件下定义Security的鉴权方案类型在接口级别的Operati…

工业机器人远程运维,增强智慧工厂运营管理

1、需求背景 随着工业自动化技术的普及和工业机器人应用的增加&#xff0c;制造业对于生产线稳定性和效率的要求不断提高。然而&#xff0c;传统的现场监控方式存在着地理位置限制、实时监控难度大以及诊断能力有限等问题&#xff0c;迫切需要一种更具灵活性和效率的监控方式。…

这可能是最清晰易懂的 G1 GC 资料

滑动验证页面 概述 G1 (Garbage-First) 于JDK 6u14版本发布&#xff0c;JDK 7u4版本发行时被正式推出&#xff0c;在JDK9时已经成了默认的垃圾回收器&#xff0c;算是CMS回收器的替代 方案&#xff08;CMS在JDK9以后已经废弃&#xff09; G1是一款分代的 (generational)&a…

AI漫画赛道,10分钟快速赚钱秘诀!

AI百宝箱-Chatgpt4.0、Midjourney绘画、人工智能绘画、AI换脸、AI图片放大、AI图片分析、AI图片融合https://h5.cxyhub.com/?invitationhmeEo7 先使用ChatGPT写小说 ComicAI 漫画小说生成网站 1. 创建小说漫画 2. 故事模板 3. 生成角色形…

基于spring boot+MySQL 小区物业管理系统-计算机毕设 附源码37236

spring boot 小区物业管理系统 摘 要 在网络信息的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;小区物业只能以客户为导向&#xff0c;以产品的持续创新作为小区物…

音视频集式分布式拉流管理

一直以来&#xff0c;由于srs zlm等开源软件采用传统直播协议&#xff0c;即使后面实现了webrtc转发&#xff0c;由于信令交互较弱&#xff0c;使得传统的安防监控方案需要在公网云平台上部署大型流媒体服务器&#xff0c;而且节点资源不能统一管理调度&#xff0c;缺乏灵活性和…

问题:小石明知道上课玩手机会影响学习,但就是管不住自己。这说明小石缺乏()教育。 #职场发展#职场发展#经验分享

问题&#xff1a;小石明知道上课玩手机会影响学习&#xff0c;但就是管不住自己。这说明小石缺乏&#xff08;&#xff09;教育。 A.道德情感 B.道德意志 C.道德行为 D.道德认识 参考答案如图所示

B-6 Web应用程序文件包含-Server2233(环境+解析)

B-6 Web应用程序文件包含 任务环境说明:服务器场景名称:Server2233(关闭链接) 通过扫

C++ | Leetcode C++题解之第143题重排链表

题目&#xff1a; 题解&#xff1a; class Solution { public:void reorderList(ListNode* head) {if (head nullptr) {return;}ListNode* mid middleNode(head);ListNode* l1 head;ListNode* l2 mid->next;mid->next nullptr;l2 reverseList(l2);mergeList(l1, l…

Apple WWDC24的18条总结:从GPT-4o开始集成ChatGPT

大家好,我是木易,一个持续关注AI领域的互联网技术产品经理,国内Top2本科,美国Top10 CS研究生,MBA。我坚信AI是普通人变强的“外挂”,所以创建了“AI信息Gap”这个公众号,专注于分享AI全维度知识,包括但不限于AI科普,AI工具测评,AI效率提升,AI行业洞察。关注我,AI之…

通过 CartPole 游戏详细说明 PPO 优化过程

CartPole 介绍 在一个光滑的轨道上有个推车&#xff0c;杆子垂直微置在推车上&#xff0c;随时有倒的风险。系统每次对推车施加向左或者向右的力&#xff0c;但我们的目标是让杆子保持直立。杆子保持直立的每个时间单位都会获得 1 的奖励。但是当杆子与垂直方向成 15 度以上的…

以太网基础知识(二)——PAM4NRZ

概念&#xff1a;PAM4&#xff08;Pulse Amplitude Modulation 4&#xff09;是用于将数字数据转换成模拟信号。在400G以太网中PAM4被用于将数字数据转换成光信号以便在光纤中传输。PAM4技术通过改变光信号的振幅和相位来表示4个不同的数字状态&#xff0c;每个状态代表2个比特…

46-4 等级保护 - 网络安全等级保护概述

一、网络安全等级保护概述 原文:没有网络安全就没有国家安全 二、网络安全法 - 安全立法 中华人民共和国主席令 第五十三号 《中华人民共和国网络安全法》已于2016年11月7日由中华人民共和国第十二届全国人民代表大会常务委员会第二十四次会议通过,并自2017年6月1日起正式…

问题:高炉风口前的燃烧带的边界是以()含量降为1%为界限标志的。 #媒体#媒体#笔记

问题&#xff1a;高炉风口前的燃烧带的边界是以()含量降为1%为界限标志的。 参考答案如图所示

RESTful API最佳实践:Python构建指南

目录 一、引言 二、RESTful API设计原则 三、Python构建RESTful API的技术栈 四、Flask构建RESTful API实践 安装Flask 定义路由和资源 处理HTTP方法 错误处理 数据验证和序列化 使用Flask扩展 五、最佳实践案例 七、结论 一、引言 在当今的软件开发领域&#…

C51学习归纳10 --- 单总线通信协议、DS18B20

通信协议是最重要的&#xff0c;我们之前学习了I2C通信协议&#xff0c;这一节我们学习一下新的通信协议&#xff0c;单总线通信。 一、开发板原理图 可以看出直接由P3_7口控制&#xff0c;但是遵循单总线协议。 单总线的电路要求 现在介绍单总线的通信协议细节&#xff1a; 1…

免费插件集-illustrator插件-Ai插件-文本属性批处理

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.总结 1.介绍 本文介绍一款免费插件&#xff0c;加强illustrator使用人员工作效率&#xff0c;进行文本属性批处理。首先从下载网址下载这款插件 https://download.csdn.net/download/m0_67316550/87890501&am…

【递归、搜索与回溯】综合练习 {回溯恢复现场;剪枝优化}

一、经验总结 在递归算法中某些变量需要在回溯到上一层递归后恢复现场&#xff08;如递归路径&#xff09;&#xff0c;恢复现场的方法有&#xff1a; 全局变量手动恢复&#xff1a;如果该变量的类型为自定义类型&#xff08;vector, string等&#xff09;则推荐定义为全局变…

全彩LED显示屏可视角度分析

在当今数字化时代&#xff0c;全彩LED显示屏已成为公共场所、商业中心、体育赛事等场合传递信息与视觉震撼的重要媒介。然而&#xff0c;对于这些显示屏而言&#xff0c;一个关键的技术指标——可视角度&#xff0c;直接决定了观众能否从各个方位享受到一致且优质的视觉体验。本…

SQLserver前五讲课堂笔记

第一讲 基本内容 为什么要学习数据库系统?什么是数据库?什么是数据库系统?什么是数据库管理系统&#xff1f;本课程学什么以及学到什么程度? 重点难点 一组概念的区分&#xff1a;数据库、数据库系统和数据库管理系统熟悉表 的相关要素及术语熟悉数据库系统的构成(工作…