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

news2025/2/26 17:49:42

概览

如果你对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

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

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

相关文章

2024.6.7

思维导图 代码 #include <iostream>using namespace std;//创建一个RMB类 class RMB {friend const RMB operator(const RMB &p1, const RMB &p2);friend const RMB operator-(const RMB &p1, const RMB &p2);friend bool operator>(const RMB &…

《Windows API每日一练》3.1 绘制文本

本节我们将讲述如何在窗口客户区绘制文本。如果在客户区绘制文本&#xff0c;需要将整个客户区或指定文本所在的矩形区域设置为无效区域&#xff0c;然后产生WM_PANIT消息&#xff0c;调用GDI函数绘制文本。此外&#xff0c;如果要绘制文本还需要使用设备环境上下文句柄&#x…

阿里发布最强开源大模型通义千问Qwen2,国产最好用的LLM

前言 近年来&#xff0c;大模型技术发展迅速&#xff0c;开源模型的出现为AI研究和应用带来了新的活力。在这一背景下&#xff0c;阿里云通义千问团队发布了全新升级的Qwen2系列开源模型&#xff0c;为国内外开发者提供了更强大的工具和更丰富的选择。 Huggingface模型下载&am…

springboot3 数据访问

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 数据访问 一、准备数据库表二、项目创建2.1、使用spring initializer 创建2.2、添加数据库配置2.3 mapper2.4 编写controller2.5 总结 三、其他数据源 一、准备数据库表 CRE…

追觅科技2025校园招聘测评已发(真题)

&#x1f4e3;追觅科技 2025校园招聘测评已发&#xff0c;正在申请的小伙伴看过来哦&#x1f440; ㊙本次校招面向全球于2023年7月 - 2025年12月期间毕业的同学&#xff0c;开放了四大类岗位&#xff1a;营销类、研发类、制作供应类、职能类~ ✅测评解析 &#x1f449; 测评自…

Kimichat使用案例012:用Kimichat拆解雷军在小米汽车SU7发布会上的演讲技巧

文章目录 一、介绍二、输入内容三、输出内容四、继续追问五、继续回答六、讲解对比七、对比回答相似之处:不同之处:八、职场人士如何借鉴九、借鉴内容一、介绍 小米SU7发布会可以说是非常成功。雷军的演讲技巧是发布会成功的重要因素之一,很值得借鉴学习。 可以借助Kimichat…

攻防世界---misc---gif

1、题目描述 2、下载附件&#xff0c;是一堆黑白图片&#xff0c;看到这里我一头雾水 3、看别人写的wp&#xff0c;说是白色表示0&#xff0c;黑色表示1。按照顺序写出来后得到 4、解码的时候&#xff0c;把逗号去掉。二进制转字符串得到&#xff1a; 5、 flag{FuN_giF}

「OC」UI练习(一)—— 登陆界面

「OC」登陆界面 明确要求 一个登陆界面的组成&#xff0c;用户名提示以及输入框&#xff0c;密码提示提示以及输入框&#xff0c;登陆按钮&#xff0c;以及注册按钮&#xff0c;根据以上要求我们将我们的组件设置为成员变量。 //viewControl.h #import <UIKit/UIKit.h>…

Kimichat使用案例013:用kimichat批量识别出图片版PDF文件中的文字内容

文章目录 一、介绍二、具体操作三、信息识别一、介绍 图片版的PDF文件,怎么才能借助AI工具来提取其中全部的文字内容呢? 第一步:将PDF文件转换成图片格式 具体方法参见文章: Kimichat使用案例011:用kimichat将PDF自动批量分割成多个图片(零代码编程) 第二步:识别图片中…

Go模板页面浏览器显示HTML源码问题

<!--* Title: This is a file for ……* Author: JackieZheng* Date: 2024-06-09 17:00:01* LastEditTime: 2024-06-09 17:01:12* LastEditors: Please set LastEditors* Description:* FilePath: \\GoCode\\templates\\index.html --> <!DOCTYPE html> <html …

【安装笔记-20240610-Linux-免费域名服务之eu.org】

安装笔记-系列文章目录 安装笔记-20240610-Linux-免费域名服务之eu.org 文章目录 安装笔记-系列文章目录安装笔记-20240610-Linux-免费域名服务之eu.org 前言一、软件介绍名称&#xff1a;eu.org主页官方介绍 二、安装步骤测试版本&#xff1a;openwrt-23.05.3-x86-64注册填写…

Java基础——多线程(一)

概念 线程和进程 进程&#xff1a;进程是程序的基本执行实体 线程&#xff1a;线程是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;是进程的实际运作单位 简单理解&#xff1a;应用软件中互相独立&#xff0c;可以同时运行的功能。多线程可以…

C++ | Leetcode C++题解之第144题二叉树的前序遍历

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> preorderTraversal(TreeNode *root) {vector<int> res;if (root nullptr) {return res;}TreeNode *p1 root, *p2 nullptr;while (p1 ! nullptr) {p2 p1->left;if (p2 ! nullptr) {…

Nginx配置详细解释:(4)高级配置

目录 1.网页的状态页 2.Nginx第三方模块(echo) 3.变量 4.自定义访问日志 5.Nginx压缩功能 6.https功能 7.自定义图标 Nginx除了一些基本配置外&#xff0c;还有一些高级配置&#xff0c;如网页的状态&#xff0c;第三方模块需要另外安装&#xff0c;支持变量&#xff0c…

SpringTask-Timer实现定时任务

1、Timer 实现定时任务 1.1、JDK1.3 开始推出定时任务实现工具。 1.2、API 执行代码 public static void main(String[] args) throws ParseException {Timer timer new Timer();String str"2024-06-10 23:24:00";Date date new SimpleDateFormat("yyyy-MM…

【docker】日志

ocker 日志相关的操作主要涉及查看、管理和理解容器的日志输出。以下是一些常用的 Docker 日志命令和选项&#xff1a; 查看日志 docker logs container_id_or_name&#xff1a;获取指定容器的日志。docker logs -f container_id_or_name&#xff1a;跟随&#xff08;实时输出…

idea开发java高校社团推广管理系统springboot框架web结构java编程计算机网页LayUI技术

一、源码特点 java 高校社团推广管理系统是一套完善的完整信息系统&#xff0c;结合java web开发springboot框架和LayUI框架完成本系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 前段主要…

使用Vue CLI在其他磁盘创建项目出现错误及解决

Vue CLI是Vue.js官方推出的脚手架工具&#xff0c;可以帮我们快速的创建Vue项目框架。 我们创建Vue项目时一般默认都是在C盘&#xff0c;但由于某些因素我们需要在其他磁盘上创建Vue项目。 通过“winr”打开终端时默认位置都是C盘&#xff0c;但是Vue CLI不接受绝对路径作为参…

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…

OpenCV 双目三角法计算点云

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 基于三角法计算点坐标的过程类似于我们人类眼睛观察事物的过程: 如上图所示,通过两个相机观察到同一位置,我们可以通过两个相机得到这一位置的投影坐标 ( u r , v r ) , ( u l , v l )