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 - 优化阅读笔记
注:该优化与全局子表达式消除刚好是相反的过程,具体该不该做这个优化得看代价模型算出来的结果(有采样文件指导算得会更准确) 该优化过程将指令移动到后继基本块中,以便它们不会在不需要其结果的路径上执行。 该优化过程并非旨在替代或完全…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
