JAVA后端开发面试基础知识(九)——SpringBoot
启动原理
SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可:
@SpringBootApplication(scanBasePackages = {"cn.dark"})
public class SpringbootDemo {public static void main(String[] args) {// 第一种SpringApplication.run(SpringbootDemo .class, args);// 第二种new SpringApplicationBuilder(SpringbootDemo .class)).run(args);// 第三种SpringApplication springApplication = new SpringApplication(SpringbootDemo.class);springApplication.run(); }
}
可以看到第一种是最简单的,也是最常用的方式,需要注意类上面需要标注@SpringBootApplication
注解,这是自动配置的核心实现,稍后分析,先来看看SpringBoot启动做了些什么?
在往下之前,不妨先猜测一下,run方法中需要做什么?对比Spring源码,我们知道,Spring的启动都会创建一个ApplicationContext
的应用上下文对象,并调用其refresh方法启动容器,SpringBoot只是Spring的一层壳,肯定也避免不了这样的操作。
另一方面,以前通过Spring搭建的项目,都需要打成War包发布到Tomcat才行,而现在SpringBoot已经内置了Tomcat,只需要打成Jar包启动即可,所以在run方法中肯定也会创建对应的Tomcat对象并启动。以上只是我们的猜想,下面就来验证,进入run方法:
public ConfigurableApplicationContext run(String... args) {// 统计时间用的工具类StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();// 获取实现了SpringApplicationRunListener接口的实现类,通过SPI机制加载// META-INF/spring.factories文件下的类SpringApplicationRunListeners listeners = getRunListeners(args);// 首先调用SpringApplicationRunListener的starting方法listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 处理配置数据ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);// 启动时打印bannerBanner printedBanner = printBanner(environment);// 创建上下文对象context = createApplicationContext();// 获取SpringBootExceptionReporter接口的类,异常报告exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 核心方法,启动spring容器refreshContext(context);afterRefresh(context, applicationArguments);// 统计结束stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 调用startedlisteners.started(context);// ApplicationRunner// CommandLineRunner// 获取这两个接口的实现类,并调用其run方法callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {// 最后调用running方法listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;}
SpringBoot的启动流程就是这个方法,先看getRunListeners
方法,这个方法就是去拿到所有的SpringApplicationRunListener
实现类,这些类是用于SpringBoot事件发布的,关于事件驱动稍后分析,这里主要看这个方法的实现原理:
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}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;}public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());}public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);return result;}}
一步步追踪下去可以看到最终就是通过SPI机制根据接口类型从META-INF/spring.factories
文件中加载对应的实现类并实例化,SpringBoot的自动配置也是这样实现的。
为什么要这样实现呢?通过注解扫描不可以么?当然不行,这些类都在第三方jar包中,注解扫描实现是很麻烦的,当然你也可以通过@Import
注解导入,但是这种方式不适合扩展类特别多的情况,所以这里采用SPI的优点就显而易见了。
回到run方法中,可以看到调用了createApplicationContext
方法,见名知意,这个就是去创建应用上下文对象:
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET:contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);break;case REACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default:contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);}}return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
注意这里通过反射实例化了一个新的没见过的上下文对象AnnotationConfigServletWebServerApplicationContext
,这个是SpringBoot扩展的,看看其构造方法:
public AnnotationConfigServletWebServerApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}
相关文章:

JAVA后端开发面试基础知识(九)——SpringBoot
启动原理 SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可: @SpringBootApplication(scanBasePackages = {"cn.dark"}) public class SpringbootDemo {public static void main(String[] args) {// 第一种SpringApplication.run(S…...

C#调用Halcon出现尝试读取或写入受保护的内存,这通常指示其他内存已损坏。System.AccessViolationException
一、现象 在C#中调用Halcon,出现异常提示:尝试读取或写入受保护的内存,这通常指示其他内存已损坏。System.AccessViolationException 二、原因 多个线程同时访问Halcon中的某个公共变量,导致程序报错 三、测试 3.1 Halcon代码 其中tsp_width…...

ts基础知识
1. any 类型,unknown 类型,never 类型 TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个 1、any 1.1 基本含义 any 类型表示没有任何限制,该类型的变量可以赋予任意类型的…...

KLayout Python Script ------ 绘制1个 Box 物体
KLayout Python Script ------ 绘制两个 Box 物体 引言正文引言 本人通常使用 IPKISS 进行版图绘制,然而很多时候,IPKISS 的功能十分鸡肋。因此,萌生了一种自己写绘制软件的想法,因为 IPKISS 绘制的版图最终也是使用 KLayout 来呈现的,因此,再研究了 KLayout 提供的 API…...

c# 编辑、删除一条数据
1、编辑数据 [HttpPost] public MessageModel<string> Put([FromBody] Dtable request) { var data new MessageModel<string>(); request.UPDATETIME DateTime.Now; if (request.ID>0) { …...

高性能服务系列【八】C10M时代,网络IO库需要重建
在目前网络上能搜索到的,关于网络IO模型的文章,基本都是关于多路复用的iocp/epoll的,这些技术是为了解决C10K问题而提出的解决方案。现代网卡已经普遍支持10Gb,100Gb也不少见,这些解决方案已经无法提升性能的需求。 我…...

Go语言与Rust哪一个更有发展前景?
Go语言和Rust都是目前非常受欢迎的编程语言,它们各自具有独特的优势和适用场景。关于哪一个更有发展前景,这实际上取决于多个因素,包括个人偏好、项目需求、社区支持以及未来技术的发展趋势等。 Go语言是由Google推出的,具有简洁…...

STM32使用定时器驱动电机
STM32使用定时器驱动电机 1、对定时器进行初始化配置1.1、include "encoder.c"文件 主函数 1、对定时器进行初始化配置 1.1、include "encoder.c"文件 #include "encoder.h"void TIM4_Encoder_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO…...

C语言游戏实战(4):人生重开模拟器
前言: 人生重开模拟器是前段时间非常火的一个小游戏,接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 网页版游戏: 人生重开模拟器 (ytecn.com) 1.实现一个简化版的人生重开模拟器 (1) 游戏开始的时…...

MVC架构模式学习笔记(动力节点老杜2022)
GitHub代码笔记:laodu-mvc: 动力节点学习javaweb中的mvc笔记。 文章目录 1.视频链接 2.不使用MVC架构模式程序存在的缺陷 3.MVC架构模式理论基础 4.JavaEE设计模式-DAO模式 5.pojo & bean & domain 6.业务层抽取以及业务类实现 7.控制层 8.MVC架构模式与三…...

docker常用操作-docker私有仓库的搭建(Harbor),并将本地镜像推送至远程仓库中。
1、docker-compose安装,下载docker-compose的最新版本 第一步:创建docker-compose空白存放文件vi /usr/local/bin/docker-compose 第二步:使用curl命令在线下载,并制定写入路径 curl -L "https://github.com/docker/compos…...

什么是MVC
MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面ÿ…...

ChatGPT浪潮来袭!谁先掌握,谁将领先!
任正非在接受采访时说 今后职场上只有两种人, 一种是熟练使用AI的人, 另一种是创造AI工具的人。 虽然这个现实听起来有些夸张的残酷, 但这就是我们必须面对的事实 📆 对于我们普通人来说,我们需要努力成为能够掌握…...
Focal and Global Knowledge Distillation forDetectors
摘要 文章指出,在目标检测中,教师和学生在不同领域的特征差异很大,尤其是在前景和背景中。如果我们 平等地蒸馏它们,特征图之间的不均匀差异将对蒸馏产生负面影响。因此,我们提出了局部和全局蒸馏。局部蒸馏分离前景和…...

FX110网:1月美国零售货币资金环比上升2.61%,嘉盛环比上升1.86%
美国商品期货交易委员会(CFTC)发布的最新月度报告显示,2024年1月零售货币存款与上月相比上升2.61%。 这份报告涵盖在美国运营的注册零售货币对交易商(RFED)和经纪自营商。包括嘉信理财(CHARLES SCHWAB Futu…...

全量知识系统的核心-全量知识的一个“恰当组织”的构想及百度AI答问
全量知识系统的核心-全量知识的一个恰当组织 Q1. 以下是对 我刚刚完成的文档“全量知识系统的核心:全量知识的一个恰当组织构想”的百度AI答复。由于字数400的限制,内容被分成四段. 第一次回答:学科和科学的框架 关于技术学科、一般学科和…...

C++中using 和 typedef 的区别
C中using 和 typedef 的区别_typedef using-CSDN博客 在C中,“using”和“typedef”执行声明类型别名的相同任务。两者之间没有重大区别。C中的“Using”被认为是类型定义同义词。此方法也称为别名声明。定义这些别名声明的工作方式类似于使用“using”语句定义C中…...

LeetCode-1944题: 队列中可以看到的人数(原创)
【题目描述】 有 n 个人排成一个队列,从左到右 编号为 0 到 n - 1 。给你以一个整数数组 heights ,每个整数 互不相同,heights[i] 表示第 i 个人的高度。一个人能 看到 他右边另一个人的条件是这两人之间的所有人都比他们两人 矮 。更正式的&…...

Java基础面试题整理2024/3/13
1、可以使用switch的数据类型 Java5以前,switch(arg)表达式中,arg只能是byte、short、char、int。 Java5之后引入了枚举类型,也可以是枚举类型。 Java7开始引入了字符串类型。 2、Java中的goto有什么作用 goto是Java中的保留字,…...

MachineSink - 优化阅读笔记
注:该优化与全局子表达式消除刚好是相反的过程,具体该不该做这个优化得看代价模型算出来的结果(有采样文件指导算得会更准确) 该优化过程将指令移动到后继基本块中,以便它们不会在不需要其结果的路径上执行。 该优化过程并非旨在替代或完全…...

虾皮shopee根据ID取商品详情 API
公共参数 名称类型必须描述keyString是免费申请调用key(必须以GET方式拼接在URL中)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等]cacheString否[yes,no]默认y…...

你知道数据库有哪些约束吗?
目录 1. NULL约束 2. 唯一(UNIQUE)约束 3. 默认值(DEFAULT)约束 4. 主键约束 5. 外键约束 6. CHECK约束 数据库约束是一种用于确保数据库中数据完整性和一致性的规则或条件。这些约束可以应用于表、列或整个数据库࿰…...

QT----基于QT的人脸考勤系统(未完成)
目录 1 编译opencv库1.1 下载源代码1.2 qt编译opencv1.3 执行Cmake一直卡着data: Download: face_landmark_model.dat 2 编译SeetaFace2代码2.1 遇到报错By not providing "FindOpenCV.cmake" in CMAKE_MODULE_PATH this project has2.2遇到报错Model missing 3 测试…...

机试:成绩排名
问题描述: 代码示例: #include <bits/stdc.h> using namespace std;int main(){cout << "样例输入" << endl; int n;int m;cin >> n;int nums[n];for(int i 0; i < n; i){cin >> nums[i];}// 排序for(int i 0; i < n; i){//…...

C编程基础四十分笔记
都是一些基础的C语言 一 输入一个整数,计算这个整数有几位二 编写程序计算一个分布函数三 输入一个字符串,再随便输入一个字母,判断这个字母出现几次四 求 1到10的阶乘之和五 求一个球体体积六 写一个链表,存1,2&#…...

k8s关于pod
目录 1、POD 的创建流程 kubectl 发起创建 Pod 请求: API Server 接收请求并处理: 写入 Etcd 数据库: Kubelet 监听并创建 Pod: Pod 状态更新和汇报: 2、POD 的状态解析 1. Pending Pod 2. Running Pod 3. S…...

yum安装mysql 数据库tab自动补全
centos7上面没有mysql,它的数据库名字叫做mariadb [rootlocalhost ~]#yum install mariadb-server -y [rootlocalhost ~]#systemctl start mariadb.service [rootlocalhost ~]#systemctl stop firewalld [rootlocalhost ~]#setenforce 0 [rootlocalhost ~]#ss -na…...

MBT-Net
feature F,edge feature E-F where r related to the relative position 辅助信息 作者未提供代码...

大数据赋能,能源企业的智慧转型之路
在数字洪流中,大数据已经成为推动产业升级的新引擎。特别是在能源行业,大数据的应用正引领着一场深刻的智慧转型。今天,我们就来探讨大数据如何在能源企业中发挥其独特的魅力,助力企业提效降本,实现绿色发展。 动态监控…...

2024考研国家线公布,各科分数线有哪些变化?考研国家线哪些涨了,哪些跌了?可视化分析告诉你
结论在文章结尾 2024考研国家线 一、近五年国家线趋势图-学术硕士 文学 管理学 工学照顾专业 体育学 交叉学科 军事学 历史学 理学 享受少数名族照顾政策的考生 中医类照顾专业 教育类 艺术类 医学 工学 哲学 法学 农学 经济学 二、近五年国家线趋势图-专业硕士 中医 应用心理 …...