Spring源码的分析之启动流程

一.前言

这篇文章的话就是我个人通过一些技术博客以及自己写一些Demo测试获得的一些感悟但是

由于本人的技术水平有限所以肯定就是会出现一些问题所以希望看这篇文章的时候如果发现错误的时候可以提出来然后我个人的话进行修改

二.SpringApplication 的构造函数

创建的一个简单的Spring的项目:

@SpringBootApplication
public class SpringSimpleDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringSimpleDemoApplication.class, args);Object testBean =context.getBean("testBean");System.out.println(testBean);}}

Run方法的执行: 这个就是说先创建一个SpringApplication的对象然后调用这个对象里面的run方法

通过查看SpringApplication的构造函数:这个就是说通过进行一些准备工作如加载文件中的内容以及获得监听器

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");// 保存启动类信息this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 初始化环境。环境分为三种 非web环境、web环境、reactive环境三种。其判断逻辑就是判断是否存在指定的类,默认是Servlet 环境this.webApplicationType = WebApplicationType.deduceFromClasspath();// getSpringFactoriesInstances 方法加载了 spring.factories文件。在这里进行了首次加载spring.factoies文件。设置 ApplicationContextInitializersetInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 获取监听器,也加载了spring.factories文件setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 设置启动类信息this.mainApplicationClass = deduceMainApplicationClass();}

run方法的流程:

public ConfigurableApplicationContext run(String... args) {//进行开始时间的记录long startTime = System.nanoTime();//这个就是创建一个上下文的对象用于存储和管理启动时所需的资源和配置信息DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;//这个就是获得系统的资源以及配置 通过system.peoperty方法configureHeadlessProperty();//获得监听器就是从META-INF/spring.factories文件中进行获得SpringApplicationRunListeners listeners = getRunListeners(args);//开启监听器listeners.starting(bootstrapContext, this.mainApplicationClass);try {//这个主要就是命令行参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//环境的封装ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);//配置BeanInfo的忽略configureIgnoreBeanInfo(environment);//这个就是打印信息的对象Banner printedBanner = printBanner(environment);//创建上下文对象context = createApplicationContext();//设置启动的信息context.setApplicationStartup(this.applicationStartup);//这个就是配置上下文需要的环境配置信息prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);//这个就是进行刷新上下文refreshContext(context);//这个刷新完成之后进行一些后置的处理afterRefresh(context, applicationArguments);Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}//开启监听器listeners.started(context, timeTakenToStartup);//调用运行器执行必要的初始化任务或者是一些命令行操作callRunners(context, applicationArguments);}//失败的处理catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {//准备时间的计算Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);//通知监听器启动完成listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;}

三.run方法具体的实现的步骤(重点)

1.获得监听器:

这个主要就是从 spring.factories 文件中获取监听器集合,当有事件发生时调用监听器对应事件的方法。下面就就就是代码的详情

private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),this.applicationStartup);}//当中实现的方法
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;}

这个从上两张图片可以看出来getSpringFactoriesInstances返回的是一个Collection类型的返回值,然后我们可以通过看下图就可以发现SpringApplicationRunListeners不是说是一个监听器而是说是一个监听器的集合

spring.factories:

 位置就是在依赖第三包中的META-INF目录之下,里面的内容就是监听器这个我们打开其他jar包的目录都会看到的所以这个就是一个外界的资源

2.准备一些环境:

  //环境的封装
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

具体代码的实现:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environment//这个就是获得以及配置环境就是根据不同的环境ConfigurableEnvironment environment = getOrCreateEnvironment();//进行一个环境的封装配置多个数据源以及加载 如根据一些命令决定加载的是dev文件还是说其他//的配置文件 就是基本的状态也就是确定大致的轮廓configureEnvironment(environment, applicationArguments.getSourceArgs());//这个就是更加的细分化了将这个些数据源或者配置文件中的内容配置到相对应的组件中//如配置文件中的数据库的密码 网址 就会配置到数据库组件当中ConfigurationPropertySources.attach(environment);//发布相关的事件listeners.environmentPrepared(bootstrapContext, environment);DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment = convertEnvironment(environment);}ConfigurationPropertySources.attach(environment);return environment;}

不同的环境创建不同的环境默认的情况下就是ApplicationEnvironment

3.创建上下文

context = createApplicationContext();

细分化看: 默认的情况下就是创建annotationapplicationcontext

//这个就是根据webApplicationType创建上下文
protected ConfigurableApplicationContext createApplicationContext() {return this.applicationContextFactory.create(this.webApplicationType);}

4.上下文的准备工作:

  作用:准备和初始化 ApplicationContext,包括设置环境、应用初始化器、注册监听器、注册单例Bean(这个就是将符合条件的类注册为BeanDefinition然后要通过BeanFactory进行创建)

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// 设置环境context.setEnvironment(environment);// 后处理应用程序上下文postProcessApplicationContext(context);// 应用初始化器applyInitializers(context);// 触发监听器的 contextPrepared 方法listeners.contextPrepared(context);// 关闭 BootstrapContextbootstrapContext.close(context);// 记录启动信息if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// 注册 Boot 特定的单例 BeanConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}// 配置 BeanFactoryif (beanFactory instanceof AbstractAutowireCapableBeanFactory) {((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}// 配置懒加载if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// 配置属性源顺序context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));// 加载来源Set<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[0]));// 触发监听器的 contextLoaded 方法listeners.contextLoaded(context);
}

5.刷新容器:

refreshContext(context);

详细如下:这个refresh的话我在下面的文章中会仔细进行说明这个也是容器启动一个很重要的部分

private void refreshContext(ConfigurableApplicationContext context) {if (this.registerShutdownHook) {shutdownHook.registerApplicationContext(context);}refresh(context);}

6.ApplicationRunner以及CommandLineRunner接口:

    我们可以通过实现这个接口实现一些额外的业务逻辑因为调用实现这个接口的实现类的时候所有的Bean以及应用上下文完成了初始化

//这个方法就是说应用程序启动成功之后
//然后context完成了刷新之后那么就会调用这个方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList<>();runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);for (Object runner : new LinkedHashSet<>(runners)) {if (runner instanceof ApplicationRunner) {callRunner((ApplicationRunner) runner, args);}if (runner instanceof CommandLineRunner) {callRunner((CommandLineRunner) runner, args);}}}

这个就是我写的Demo测试:

//这个就是一个很简单的启动类
@SpringBootApplication
public class SpringSimpleDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringSimpleDemoApplication.class, args);}
}
//这个就是我写的Bean类
@Component("testBean")
public class TestBean  {public void test() {System.out.println("测试成功");}
}
//这个就是实现了ApplictionRunner接口的类
@Component
public class MyApplicationRunner implements ApplicationRunner {@Autowiredprivate ApplicationContext context;@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("执行一些额外的逻辑");TestBean bean = (TestBean) context.getBean("testBean");bean.test();}
}

最后的结果的展示:

这个的话后面加上一个总结吧因为光看代码的话其实本身就是枯燥的:

1.就是创建一个SpringApplication对象,在这个构造函数里面就是一个环境的类型就是通过判断是否存在一些特定的类进行判断,然后就是加载Springfactory文件然后获得监控器进行设置,后面的话其实就是在run方法

2.第一步就是进行环境的准备通过configureEnvironment方法包含运行的时候的所需的配置的信息如配置文件 系统属性以及命令行参数如:spring.profiles.active=dev通过解析这个命令行来决定加载哪个配置文件然后就是说通过ConfigurationPropertySources.attach(environment)将配置的属性源和当前的运行环境进行一个关联这个就是为了环境对象对配置属性源更好地进行利用和管理如:如果添加了或者删除了属性源的话就会自动进行移除

3.第二步就是创建应用上下文:主要就是通过WebApplicationType(包括NONESERVLETREACTIVE,默认的条件下的话Spring Boot 通常会创建AnnotationConfigApplicationContext作为应用上下文然后就会到刷新上下文(refresh()这个就是后面的话就是会进行介绍)

4.第三步就是 ApplicationRunner以及CommandLineRunner接口:这个的话就是交给我们的消费这个进行书写然后这个时候我们可以进行一些拓展如:可以进行日志的打印或者如果这个时候可以调用Bean中的一个定时的任务的话(这个和Bean生命周期中的BeanPostProcessor接口一样可以添加一些额外的逻辑这个我在上面的内容进行进行演示,你们也可以尝试一下)

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

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

相关文章

Scala学习记录,全文单词统计

package test32 import java.io.PrintWriter import scala.io.Source //知识点 // 字符串.split("分隔符"&#xff1a;把字符串用指定的分隔符&#xff0c;拆分成多个部分&#xff0c;保存在数组中) object test {def main(args: Array[String]): Unit {//从文件1.t…

Linux下的三种 IO 复用

目录 一、Select 1、函数 API 2、使用限制 3、使用 Demo 二、Poll 三、epoll 0、 实现原理 1、函数 API 2、简单代码模板 3、LT/ET 使用过程 &#xff08;1&#xff09;LT 水平触发 &#xff08;2&#xff09;ET边沿触发 4、使用 Demo 四、参考链接 一、Select 在…

LeetCode 3208.交替组 II:滑动窗口

【LetMeFly】3208.交替组 II&#xff1a;滑动窗口 力扣题目链接&#xff1a;https://leetcode.cn/problems/alternating-groups-ii/ 给你一个整数数组 colors 和一个整数 k &#xff0c;colors表示一个由红色和蓝色瓷砖组成的环&#xff0c;第 i 块瓷砖的颜色为 colors[i] &a…

与7无关的数

与7无关的数 C语言代码C 语言代码Java语言代码Python语言代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 一个正整数&#xff0c;如果它能被7整除,或者它的十进制表示法中某一位上的数字为7&#xff0c;则称其为与7相关的数。现求所有小…

07.ES11 08.ES12

7.1、Promise.allSettled 调用 allsettled 方法&#xff0c;返回的结果始终是成功的&#xff0c;返回的是promise结果值 <script>//声明两个promise对象const p1 new Promise((resolve, reject) > {setTimeout(() > {resolve("商品数据 - 1");}, 1000)…

git 上传代码时报错

在上传代码时&#xff0c;显示无法上传 PS E:\JavaWeb\vue3-project> git push To https://gitee.com/evening-breeze-2003/vue3.git! [rejected] master -> master (non-fast-forward) error: failed to push some refs to https://gitee.com/evening-breeze-20…

视觉语言模型(VLM)学习笔记

目录 应用场景举例 VLM 的总体架构包括&#xff1a; 深度解析&#xff1a;图像编码器的实现 图像编码器&#xff1a;视觉 Transformer 注意力机制 视觉-语言投影器 综合实现 训练及注意事项 总结 应用场景举例 基于文本的图像生成或编辑&#xff1a;你输入 “生成一张…

digit_eye开发记录(3): C语言读取MNIST数据集

在前两篇&#xff0c;我们解读了 MNIST 数据集的 IDX 文件格式&#xff0c;并分别用 C 和 Python 做了 读取 MNIST 数据集的实现。 基于 C 的代码稍长&#xff0c;基于 Python 的代码则明显更短&#xff0c;然而它们的共同特点是&#xff1a;依赖了外部库&#xff1a; 基于 C …

C#窗体小程序计算器

使其能完成2个数的加、减、乘、除基本运算。界面如下图&#xff0c;单击相应的运算符按钮&#xff0c;则完成相应的运算&#xff0c;并将结果显示出来&#xff0c;同时不允许在结果栏中输入内容 代码如下&#xff1a; private void button1_Click(object sender, EventArgs e)…

Linux命令进阶·如何切换root以及回退、sudo命令、用户/用户组管理,以及解决创建用户不显示问题和Ubuntu不显示用户名只显示“$“符号问题

目录 1. root用户&#xff08;超级管理员&#xff09; 1.1 用于账户切换的系统命令——su 1.2 退回上一个用户命令——exit 1.3 普通命令临时授权root身份执行——sudo 1.3.1 为普通用户配置sudo认证 2. 用户/用户组管理 2.1 用户组管理 2.2 用户管理 2.2.1 …

【JavaEE】JavaEE、web 开发、框架(Spring) 、Maven

文章目录 一、JavaEE 发展历程二、什么是 web 开发1、什么是 web 开发&#xff1f;2、web 网站的工作流程 三、框架1、什么是框架&#xff1f;2、为什么要学框架&#xff1f;3、框架的优点&#xff08;Spring Boot VS Servlet&#xff09; 四、Maven 一、JavaEE 发展历程 Java…

虚拟机玩游戏,轻松实现多开不同IP

嘿&#xff0c;亲爱的游戏小伙伴们&#xff01;今天要和大家分享一个超级实用的技巧&#xff0c;让你在游戏中轻松多开不同IP&#xff0c;享受开挂的乐趣&#xff01; 第一步&#xff1a;准备虚拟机 首先&#xff0c;你需要下载一个虚拟机软件&#xff0c;比如VMware或者Virt…

MySQL常用语句整理

《SQL必知必会》(第3版)SQL是目前使用最为广泛的数据库语言之一。本书没有涉及理论&#xff0c;而是从实践出发&#xff0c;由浅入深地讲解了广大读者所必需的SQL知识&#xff0c;适用于各种主流数据库。实例丰富&#xff0c;便于查阅。本书涉及不同平台上数据的排序、过滤和分…

【MATLAB】基于RSSI的蓝牙定位与例程,设置4个基站、二维定位

目录 ​编辑 商品描述 主要功能 技术细节 适用场景 下载链接 商品描述 这款基于接收信号强度指示&#xff08;RSSI&#xff09;原理的蓝牙定位程序&#xff0c;专为需要高效、可靠定位解决方案的开发者和研究人员设计。它能够在二维平面内&#xff0c;通过4个锚点实现对未…

DreamCamera2相机预览变形的处理

最近遇到一个问题&#xff0c;相机更换了摄像头后&#xff0c;发现人像角度顺时针旋转了90度&#xff0c;待人像角度正常后&#xff0c;发现 预览时图像有挤压变形&#xff0c;最终解决。在此记录 一人像角度的修改 先放示意图 设备预览人像角度如图1所示&#xff0c;顺时针旋…

鸿蒙修饰符

文章目录 一、引言1.1 什么是修饰符1.2 修饰符在鸿蒙开发中的重要性1.3 修饰符的作用机制 二、UI装饰类修饰符2.1 Styles修饰符2.1.1 基本概念和使用场景2.1.2 使用示例2.1.3 最佳实践 2.2 Extend修饰符2.2.1 基本概念2.2.2 使用示例2.2.3 Extend vs Styles 对比2.2.4 使用建议…

phpmyadmin导出wordpress数据教程

网站搬家或网站修改&#xff0c;需要导出数据或备份数据&#xff0c;一般主机控制面板最常用phpmyadmin。下面这个是一个有详细图文操作步骤的phpmyadmin导出wordpress数据教程。 1、先登陆到你主机的管理面板&#xff0c;找到数据库&#xff0c;再找到phpmyadmin&#xff0c;…

阅读笔记——SVD本质+计算+应用

摘要&#xff1a;本文讨论了正交相似对角化的方法&#xff0c;几何含义&#xff1b;方阵&#xff0c;非方阵的奇异值分解的计算&#xff0c;矫正方法以及与正交相似对角化存在区别&#xff1b;最后讨论了奇异值分解的应用。 1.实对称矩阵A的变换是一种线性变换&#xff0c;对应…

Linux网络_网络协议_网络传输_网络字节序

一.协议 1.概念 协议&#xff08;Protocol&#xff09; 是一组规则和约定&#xff0c;用于定义计算机网络中不同设备之间如何进行通信和数据交换。协议规定了数据的格式、传输方式、传输顺序等详细规则&#xff0c;确保不同设备和系统能够有效地互联互通。 在网络通信中&#…

数据结构 (16)特殊矩阵的压缩存储

前言 特殊矩阵的压缩存储是数据结构中的一个重要概念&#xff0c;它旨在通过找出特殊矩阵中值相同的矩阵元素的分布规律&#xff0c;把那些呈现规律性分布的、值相同的多个矩阵元素压缩存储到一个存储空间中&#xff0c;从而节省存储空间。 一、特殊矩阵的定义 特殊矩阵是指具有…