Spring底层入门(九)
boot的执行流程分为构造SpringApplication对象、调用run方法两部分
1、Spring Boot 执行流程-构造
通常我们会在SpringBoot的主启动类中写以下的代码:
参数一是当前类的字节码,参数二是main的args参数。
public class StartApplication {public static void main(String[] args) {SpringApplication.run(SpringApplication.class,args);}
}
在SpringApplication.run的内部,会做两件事:
创建SpringApplication对象:
以及调用SpringApplication对象的run方法:
在创建SpringApplication对象时,通常又会做下面几件事:
- 获取bean definition源
- 获取推断应用类型
- ApplicationContext初始化器
- 监听器与事件
- 主类推断
1.1、获取bean definition源
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
将传入的 primarySources数组转换为 LinkedHashSet,并赋值给当前对象的 primarySources成员变量。这个集合用于存储应用程序的主要源,通常是启动类(例如,@SpringBootApplication注解标记的类)。
1.2、获取推断应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
通过deduceFromClasspath() 方法进行应用类型推论:
deduceFromClasspath() 是WebApplicationType的方法,WebApplicationType是一个枚举类:
简单说明一下这段代码:第一个if判断,如果是reactive.DispatcherHandler,并且不是servlet.DispatcherServlet和servlet.ServletContainer,就推断类型为REACTIVE并返回。
如果上面的条件不成立,也就是应用类型没有被推断为REACTIVE,就会循环成员变量的SERVLET_INDICATOR_CLASSES数组,该数组中有两个元素:
- javax.servlet.Servlet
- org.springframework.web.context.ConfigurableWebApplicationContext
如果任意一个元素不存在就推断类型为NONE并返回。
最后推论类型为SERVLET并返回
1.3、ApplicationContext初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
获取所有注册的 ApplicationContextInitializer实例,并将其设置为应用程序的初始化器。
通过SpringApplication实例的.addInitializers()方法注册初始化器
springApplication.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {if (applicationContext instanceof GenericApplicationContext){((GenericApplicationContext) applicationContext).registerBean("bean3",Bean3.class);}}});
1.4、监听器与事件
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
获取所有注册的 ApplicationListener实例,并将其设置为应用程序的监听器。
通过SpringApplication实例的.addListeners()方法注册监听器与事件
springApplication.addListeners(new ApplicationListener<ApplicationEvent>() {@Overridepublic void onApplicationEvent(ApplicationEvent event) {System.out.println("事件:"+event.getClass());}});
1.5、主类推断
this.mainApplicationClass = deduceMainApplicationClass();
调用deduceMainApplicationClass() 方法进行主类推论:
- 通过创建一个新的 RuntimeException对象来获取当前线程的堆栈轨迹(stack trace),即调用堆栈信息。
- 遍历堆栈信息,找到方法名为"main"的函数,并返回类的class对象
- 没有找到就抛出异常
2、Spring Boot 执行流程-run
2.1、事件发布器
在run方法中,首先会通过getRunListeners(args);得到 SpringApplicationRunListeners。
SpringApplicationRunListeners是一个事件发布器,会在run方法的执行不同阶段中发布对应的事件:
- 得到事件发布器后,会发布listeners.starting(); 事件,代表spring boot 开始启动。
- prepareEnvironment(listeners, applicationArguments);方法执行时,会发布listeners.environmentPrepared(environment); 事件,代表环境信息准备完成
- prepareContext(context, environment, listeners, applicationArguments, printedBanner); 方法执行时,会发布listeners.contextPrepared(context);事件,代表spring容器创建,但未调用初始化器。listeners.contextLoaded(context);事件,代表所有bean definition加载完毕。(然后会调用context.refresh();方法)
- 在调用context.refresh();方法后,会发布listeners.started(context);事件
- 如果在这个过程中发生了异常,会发布listeners.failed(context, exception); 事件
- 最后会发布listeners.running(context);事件,代表spring boot 启动完成
在第六点中,如果在发布listeners.running(context);事件的过程中出现异常,不会发布listeners.failed(context, exception); 事件:
我们也可以模拟一下上述过程:
事件发布器SpringApplicationRunListener是一个接口,其与子类的对应关系是放在spring.factories的配置文件中:
public class A34 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {SpringApplication springApplication = new SpringApplication(A34.class);springApplication.addListeners(new ApplicationListener<ApplicationEvent>() {@Overridepublic void onApplicationEvent(ApplicationEvent event) {System.out.println(event.getClass());}});//演示事件发送器,获取事件发送器类名//事件发送器是一个接口,和子类的对应关系存放在spring.factories配置文件中List<String> factoryNames = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, A34.class.getClassLoader());for (String name : factoryNames) {System.out.println(name);//创建事件发布器实现类Class<?> clazz = Class.forName(name);Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class);EventPublishingRunListener publishingRunListener = (org.springframework.boot.context.event.EventPublishingRunListener) constructor.newInstance(springApplication, args);//模拟SpringApplication源码public ConfigurableApplicationContext run(String... args) 方法中的七个事件//spring boot 开始启动publishingRunListener.starting();//环境信息准备完成publishingRunListener.environmentPrepared(new StandardEnvironment());//在spring容器创建,并调用初始化器之前,发送此事件GenericApplicationContext context = new GenericApplicationContext();publishingRunListener.contextPrepared(context);//所有bean definition加载完毕publishingRunListener.contextLoaded(context);context.refresh();//spring 容器初始化完成 refresh方法调用完成,加载了所有后处理器,初始化所有单例publishingRunListener.started(context);//spring boot 启动完毕publishingRunListener.running(context);//过程中发生错误publishingRunListener.failed(context,new Exception("报错"));}}
}
2.2、封装args参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
用于将args参数进行封装,便于最后一步执行实现了ApplicationRunner接口的方法。(实现了CommandLineRunner或ApplicationRunner接口的方法会在boot启动时运行其中的逻辑)
2.3、创建环境对象
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); 用于创建环境对象:
getOrCreateEnvironment()方法用于根据不同的webApplication类型,去创建对应的环境对象。(webApplication类型是在构造SpringApplication时推论出的)
configureEnvironment(ConfigurableEnvironment environment, String[] args) 方法用于配置应用程序的环境:
environment.setConversionService(new ApplicationConversionService()); 会先添加转换器:
其中又有configureProfiles(environment, args);方法来配置环境的配置文件:
configurePropertySources(environment, args) 方法配置环境的属性源(重点:在这一步只加入命令行参数,没有加入application.properties):
首先从环境中获取systemProperties和systemEnvironment
2.4、配置文件读取统一命名处理
prepareEnvironment中的 ConfigurationPropertySources.attach(standardEnvironment); 方法用于统一命名处理:
因为配置文件在读取的过程中可能会存在这样的问题:
我们定义了一个配置文件,每个键的格式都不一样:
当我们不做任何处理,在读取时键名统一使用"-"分隔时,只能读取到第一个的值,因为命名和文件中不匹配。ConfigurationPropertySources.attach() 方法可以解决这样的问题。
public class Step4 {public static void main(String[] args) throws IOException {StandardEnvironment standardEnvironment = new StandardEnvironment();standardEnvironment.getPropertySources().addLast(new ResourcePropertySource("step4",new ClassPathResource("step4.properties")));// run 源码中做了统一命名ConfigurationPropertySources.attach(environment);ConfigurationPropertySources.attach(standardEnvironment);//不做任何处理 只能读取第一个 因为命名和文件中的不匹配System.out.println(standardEnvironment.getProperty("user.first-name"));System.out.println(standardEnvironment.getProperty("user.middle-name"));System.out.println(standardEnvironment.getProperty("user.last-name"));}
}
在attach方法内部,最后会将configurationProperties加入environment的头部,以便优先被访问
2.5、EnvironmentPostProcessor功能扩展
EnvironmentPostProcessor相当于环境对象的后处理器,可以增加一些扩展。
prepareEnvironment 中的listeners.environmentPrepared(bootstrapContext, environment);
使用事件发布器去触发监听器,调用其中的后处理器方法:(监听器是在SpringApplication构造时加入的,但是需要等到环境对象创建后才应该触发其中的那些后处理器)
通过debug发现,在构造SpringApplication时已经加入了对于环境对象后处理器的监听器
关于后处理器的实现关系也是定义在spring.factories文件中的:
EnvironmentPostProcessorApplicationListener就是去读取EnvironmentPostProcessor中的后处理器
2.6、将环境中的键值和SpringApplication中的属性匹配
prepareEnvironment 中的bindToSpringApplication(environment); 用于将环境中的键值和SpringApplication中的属性匹配:
读取配置文件中以spring.main开头的键,并且与SpringApplication中的成员变量绑定:
上面配置文件中的键都是SpringApplication中的成员变量:
2.7、输出Banner图标
run()方法中的printBanner(environment); 用于在控制台或日志中输入图标:
2.8、创建容器
在环境准备完成并发布listeners.environmentPrepared(environment); 事件后,会创建容器:
createApplicationContext() 方法根据构造SpringApplication推断的不同的类型有不同的实现:
2.9、准备容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner); 方法用于准备容器:
在准备容器时又会回调构造SpringApplication时编写的初始化器中的增强逻辑:
2.10、加载Bean定义
prepareContext方法中的load(context, sources.toArray(new Object[0]));
这段代码大致的意思是,加载应用程序的配置源,并根据需要设置相关的配置,然后执行加载操作。
根据不同的配置类型去分派对应的加载方法:
2.11、refresh
执行run中的refreshContext()方法:
调用refresh方法,加载配置、初始化所有单例 、重新启动应用程序上下文。
在这之前还会判断,如果this.registerShutdownHook为true,那么会通过 shutdownHook.registerApplicationContext(context);方法为当前的应用程序上下文注册一个关机钩子(shutdown hook)。这个钩子用于在应用程序关闭时执行一些清理操作或释放资源。
2.12、执行初始化方法
run方法中的callRunners 用于执行初始化方法中编写的逻辑:
“2.2、封装args参数”的作用就体现在此:
ApplicationRunner需要将args参数包装成为ApplicationArguments类型:
虽然无论是ApplicationRunner还是CommandLineRunner,调用callRunner() 方法时传递的args参数类型都是ApplicationArguments。但是不代表CommandLineRunner需要ApplicationArguments参数类型:
会取出原本的String args 参数
相关文章:
Spring底层入门(九)
boot的执行流程分为构造SpringApplication对象、调用run方法两部分 1、Spring Boot 执行流程-构造 通常我们会在SpringBoot的主启动类中写以下的代码: 参数一是当前类的字节码,参数二是main的args参数。 public class StartApplication {public static…...
掌握Android Fragment开发之魂:Fragment的深度解析(下)
在上一篇文章中,我们深入探讨了Fragment 通信,包含Fragment 向 Activity 传递数据、Activity 向 Fragment 传递数据、Fragment 之间的通信方式。感兴趣的朋友,请前往查阅: 掌握Android Fragment开发之魂:Fragment的深度…...
小巧简单实用的Linux端口转发工具Rinetd
Linux下实现端口转发有很多种方法,尤其是在可以联网的情况下,更是容易。最近在资源受限的定制系统中,找到一个方便离线安装和使用的端口转发工具Rinetd,安装包仅几十K,而且有很多版本的Linux发行系统的支持。 1、安装…...
HackBar 新手使用教程(入门)
啥是Hackbar? Hackbar是一个Firefox 的插件,它的功能类似于地址栏,但是它里面的数据不受服务器的相应触发的重定向等其它变化的影响。 有网址的载入于访问,联合查询,各种编码,数据加密功能。 这个Hackbar可以帮助你在测试SQL注入,XSS漏洞和网站的安全性,主要是帮助…...
<Linux> 权限
目录 权限人员相对于文件来说的分类更改权限文件的拥有者与所属组umask粘滞位 权限 权限是操作系统用来限制对资源访问的机制,权限一般分为读、写、执行。系统中的每个文件都拥有特定的权限、所属用户及所属组,通过这样的机制来限制哪些用户、哪些组可以…...
Nacos Docker 快速部署----解决nacos鉴权漏洞问题
Nacos Docker 快速部署 1. 说明 1.1 官方文档 官方地址 https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html docker启动文件的gitlhub地址 https://github.com/nacos-group/nacos-docker.git 问题: 缺少部分必要配置与说明 1.2 部署最新版本Nacos&…...
存储或读取时转换JSON数据
一、 数据库类型 二、使用Hutool工具 存储时将数据转换为JSON数据 获取时将JSON数据转换为对象 发现问题: 原本数据对象是Address 和 Firend但是转换完成后数据变成了JSONArray和JSONObject 三、自定义TypeHandler继承Mybatis的BaseTypeHandler处理器 package …...
Spring Data JPA的作用和用法
Spring Data JPA 是 Spring 框架的一个模块,它提供了一种数据访问抽象,允许以一种声明式和简洁的方式来处理数据库操作。它基于 Java Persistence API (JPA),是一个行业标准的 ORM(对象关系映射)规范,用于将…...
【go项目01_学习记录08】
学习记录 1 模板文件1.1 articlesStoreHandler() 使用模板文件1.2 统一模板 1 模板文件 重构 articlesCreateHandler() 和 articlesStoreHandler() 函数,将 HTML 抽离并放置于独立的模板文件中。 1.1 articlesStoreHandler() 使用模板文件 . . . func articlesSt…...
Java中的线程
一、创建线程的几种方式? ① 通过继承Thread类并重写run方法 ,实现简单但不可以继承其他类 Thread底层也是实现了Runnable接口,重写的是run而不是start方法 ②实现Runnable接口并重写run方法, 避免了单继承的局限性ÿ…...
顺序表的实现(迈入数据结构的大门)(完整代码)
seqlist.h #pragma once typedef int SLDataType;#include<stdio.h> #include<stdlib.h> #include<assert.h>typedef struct SeqList {SLDataType* a;int size; // 有效数据个数int capacity; // 空间容量 }SL;//初始化和销毁 void SLInit(SL* ps); void SL…...
neo4j-5.11.0安装APOC插件or配置允许使用过程的权限
在已经安装好neo4j和jdk的情况下安装apoc组件,之前使用neo4j-community-4.4.30,可以找到配置apoc-4.4.0.22-all.jar,但是高版本neo4j对应没有apoc-X.X.X-all.jar。解决如下所示: 1.安装好JDK与neo4j 已经安装对应版本的JDK 17.0…...
mybatis 中 #{}和 ${}的区别是什么?
在 MyBatis 中,#{} 和 ${} 是两种用于参数替换的语法,但它们之间存在一些重要的区别,主要体现在安全性、预编译和动态 SQL 上。 安全性: #{}:这是预编译处理,MyBatis 会为传入的参数生成 PreparedStatement…...
深入解析C#中的接口设计原则
深入解析C#中的接口设计原则 目录 深入解析C#中的接口设计原则 一、接口设计的SOLID原则 二、接口设计的最佳实践 三、接口设计的高级技术 四、结论 接口在面向对象编程中扮演着至关重要的角色。它们是定义行为契约的一种方式,允许实现者提供这些行为的具体实现…...
106短信群发平台在金融和法务行业的应用分析
一、金融行业应用 1.客户通知与提醒:银行、证券、保险等金融机构经常需要向客户发送各类通知和提醒,如账户余额变动、交易确认、扣费通知、理财产品到期提醒等。106短信群发平台可以快速、准确地将这些信息发送到客户的手机上,确保客户及时获…...
Spring AOP(2)
目录 Spring AOP详解 PointCut 切面优先级Order 切点表达式 execution表达式 切点表达式示例 annotation 自定义注解MyAspect 切面类 添加自定义注解 Spring AOP详解 PointCut 上面代码存在一个问题, 就是对于excution(* com.example.demo.controller.*.*(..))的大量重…...
Spring-依赖注入的处理过程
前置知识 1 入口 DefaultListableBeanFactory#resolveDependency 2 每个依赖都有对应的DependencyDescriptor 3 自定绑定候选对象处理器AutowireCapableBeanFactory 注入处理 我们可以看到接口AutowireCapableBeanFactory中有两个方法。 第一个是单个注入: Null…...
2.用python爬取的保存在text文件中的格式为MP4的视频url
文章目录 一、url的保存格式二、MP4视频获取 一、url的保存格式 爬取的视频名字和url保存在text文件中,每一个视频都是一个单独的text,其中text的文件名就是视频的名字,text内容是视频的下载url,并且所有的text都保存在同一个文件…...
Java基于B/S医院绩效考核管理平台系统源码java+springboot+MySQL医院智慧绩效管理系统源码
Java基于B/S医院绩效考核管理平台系统源码javaspringbootMySQL医院智慧绩效管理系统源码 医院绩效考核系统是一个关键的管理工具,旨在评估和优化医院内部各部门、科室和员工的绩效。一个有效的绩效考核系统不仅能帮助医院实现其战略目标,还能提升医疗服…...
UE 蓝图堆栈调试
蓝图打断点后如果想查看断点前的执行逻辑,Tools→Debug→BlueprintDebugger 然后打断点运行,执行顺序是从下往上...
UE4_摄像机_使用摄像机的技巧
学习笔记,不喜勿喷!祝愿生活越来越好! 知识点: a.相机跟随。 b.相机抖动。 c.摄像机移动 d.四元数插值(保证正确旋转方向)。 e.相机注视跟踪。 1、新建关卡序列,并给小车添加动画。 2、创…...
ssm115乐购游戏商城系统+vue
毕业生学历证明系统 设计与实现 内容摘要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统毕业生学历信息管理难…...
【可实战】被测需求理解(需求文档是啥样的、从哪些角度进行需求评审、需求分析需要分析出哪些内容、如何提高需求分析能力)
产品人员会产出一个需求文档,然后组织一个需求的宣讲。测试人员的任务就是在需求宣讲当中,分析需求有没有存在一些问题,然后在需求宣讲结束之后通过分析需求文档,分析里面的测试点并预估一个排期。 一、需求文档是什么样的&#x…...
伪类和伪元素的区别是什么?
一、两者的定义 1.伪类(pseudo-class)是一个以冒号作为前缀,被添加到一个选择器末尾的关键字,当你希望样式在特定状态才被呈现到指定的元素时,你可以往元素的选择器后面加上对应的伪类。 2.伪元素用于创建一些不在文档…...
gorm-sharding分表插件升级版
代码地址: GitHub - 137/gorm-sharding: Sharding 是一个高性能的 Gorm 分表中间件。它基于 Conn 层做 SQL 拦截、AST 解析、分表路由、自增主键填充,带来的额外开销极小。对开发者友好、透明,使用上与普通 SQL、Gorm 查询无差别.解决了原生s…...
MoviePy(Python音视频开发)
音视频基础帧率、码率、分辨率视频格式H.264和H.265视频压缩算法 Moviepy常见剪辑类VideoFlieClipImageFlieClipColorClipTextClipCompositeVideoClipAudioFlieClipCompositeAudioClip 常见操作音视频的读入与导出截取音视频 音视频基础 帧率、码率、分辨率 体积(V…...
Spring中的FileCopyUtils:文件复制的利器与详解
1. 概述 在Spring框架中,FileCopyUtils是一个用于文件复制操作的实用工具类。它提供了一系列静态方法,简化了文件从输入流到输出流、从文件到文件等的复制过程。这些方法都基于NIO(New I/O)技术,提供了高效的文件复制…...
【操作系统】读者—写者问题python解析
一个数据问价或记录可以被多个进程共享,我们把只读该文件的进程称为“读者进程”,其他进程为“写者进程”。允许多个进程同时读一个共享对象,但不允许一个写者进程和其他写者进程或读者进程同时访问共享对象。即:保证一个写者进程…...
【driver5】调用堆栈函数,printk,动态打印,ftrace,proc,sysfs
文章目录 1.内核函数调用堆栈:4个函数2.printk:cat /proc/cmdline查看consolettyS03.动态打印:printk是全局的且只能设打印等级,动态打印可控制选择模块的打印,在内核配置打开CONFIG_DYNAMIC_DEBUG4.top&perf&…...
计算机毕业设计springboot基于vue电商抢购限时秒杀系统ch0h8
技术栈 ide工具:IDEA 或者eclipse 编程语言: java 数据库: mysql5.7以上版本 可选框架:ssmspringboot都有的 前端:vue.jsElementUI 详细技术:springbootSSMvueMYSQLMAVEN 数据库工具:Navicat/SQLyog都可以 开发工具 Ec…...
装修网站建设/百度网站快速优化
https://community.fs.com/blog/do-you-know-the-differences-between-hubs-switches-and-routers.html 介绍hub,switch,router,有动图,很形象 http://blog.csdn.net/wuruixn/article/details/8350773 介绍交换机和路由器 http://…...
做婚礼设计在哪个网站下载素材/营销策略包括哪些内容
第一次真正意义上的数据技术嘉年华,来自不同方向的原生数据库产品的创造者,架构者,应用者欢聚一堂,分享经验、传播知识、碰撞思维,一起探索数据价值,重塑企业未来! 本届大会更是一次Oracle ACE大…...
教育网站开发价钱/站内seo内容优化包括
摘要: ITSM,ITIL这些词越来越热门。 有人认为这是一种新的技术,有人认为ITSM不过是MIS的一种应用,也有人认为无非是网管+工作流。 其实ITSM的出现应该放在整个IT行业发展的过程中来看。可以说,ITSM的出现映射着IT行业的…...
青岛市建设网站/百度网盘手机版
某天无意中看见一道关于Integer的笔试题,问下面的输出结果是多少:package test;public class Test {public static void main(String[] args) {Integer i1 127;Integer i2 127;System.err.println(i1 i2);i1 128;i2 128;System.err.println(i1 i2)…...
手机网站app制作/天津百度推广电话号码
修复此问题 禁用内核调试,在 Visual Studio 中调试。- 或 -使用内核调试器而不是 Visual Studio 进行调试。- 或 -在内核调试器中禁用用户模式异常。在当前会话中禁用内核调试 在命令提示处,键入: 复制代码 Kdbgctrl.exe -d对所有会话禁用内核…...
wordpress自然志/2021全国大学生营销大赛
Windows Phone 7上的异步编程模型其实也就是说把C#里面的异步编程模型在Windows Phone 7应用开发上使用。下面来看一下异步编程模型里面的一些关键的概念。 2个方法和一个委托和一个接口: (1)BeginInvoke方法用于启动异步调用 Begin 方法包含同步方法签名中的任何参…...