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);}

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

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

相关文章

【ceph】ceph中osd报错,have spurious read error

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

提高转换效率的利器NCP13992ACDR2G 高性能电流模式LLC谐振变换器控制芯片

NCP13992ACDR2G产品概述&#xff1a; NCP13992ACDR2G是一款用于半桥谐振变换器的高性能电流模式控制器。该控制器实现了600 V栅极驱动器&#xff0c;简化了布局并减少了外部组件数量。内置的Brown−Out输入功能简化了控制器在所有应用程序中的实现。在需要PFC前级的应用中&…

采购代购系统独立站,接口采集商品上货

采购代购系统独立站的建设与商品上货接口的采集是一个综合性的项目&#xff0c;涉及前端开发、后端开发、数据库设计以及API接口的对接等多个环节。以下是一个大致的步骤和考虑因素&#xff1a; 一、系统规划与需求分析 明确业务需求&#xff1a;确定代购系统的核心功能&…

Python尝试循环连接服务器,如果连接成功则退出,如果超过3次连接失败则退出

下面是一个使用Python实现的程序&#xff0c;可以实现你描述的功能&#xff1a;通过SSH连接服务器并重启服务器&#xff0c;然后循环尝试连接服务器&#xff0c;如果连接成功则退出&#xff0c;如果超过3次连接失败则退出。 首先&#xff0c;请确保你已经安装了paramiko库&…

【SQL】1084. 销售分析III (多种解法;is null 和 =null 的区别图示 )

前述 知识点学习&#xff1a;MySQL 中 is null 和 null 的区别 题目描述 leetcode题目&#xff1a; 1084. 销售分析III 写法一 思路&#xff1a;“所有售出日期都在这个时间内”&#xff0c;也就是“在这个时间内售出的商品数量等于总商品数量” -- 解法1&#xff1a;ACCE…

桌面客户端软件开发框架

桌面客户端软件开发框架是用于创建桌面应用程序的工具集合&#xff0c;它们提供了开发者需要的基本组件、库和工具&#xff0c;以便于快速构建功能丰富、可靠的桌面应用程序。以下是一些常用的桌面客户端软件开发框架&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司…

30家地方征信平台名单

安徽省征信股份有限公司云南省征信有限责任公司甘肃征信股份有限公司北京金融大数据有限公司吉林省惠金信用信息服务有限公司江苏省联合征信有限公司广东图腾征信有限公司青海省征信有限公司浙江浙里信征信有限公江西普惠征信股份有限公司上海市联合征信有限公司四川征信有限公…

bpmn-js中实现shape的内置属性、节点的默认配置

bpmn-js中使用elementfactory模块来构建一个元素的结构,其构建构成和元素属性的组成可参考:聊一聊bpmn-js中的elementFactory模块https://blog.csdn.net/chf1142152101/article/details/136294768。构建元素的属性会自动帮我们生成一个对应类型的shape的Id,其余属性均为空,…

Android开发 Activity启动模式、ViewModel与LiveData,及Kotlin Coroutines

目录 Activity启动模式 onNewIntent解释 Activity启动模式的考虑时机 Service启动模式 ContentProvider的作用 Broadcast的注册方式 AsyncTask的作用 ViewModel LiveData Kotlin Coroutines 结合使用 Activity启动模式 Android中Activity的启动模式有四种&#xff0…

【深入理解设计模式】命令设计模式

命令设计模式&#xff1a; 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;它将请求封装为一个对象&#xff0c;从而使你可以用不同的请求对客户端进行参数化&#xff0c;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 概述…

CentOS本地部署Tale博客并结合内网穿透实现公网访问本地网站

文章目录 前言1. Tale网站搭建1.1 检查本地环境1.2 部署Tale个人博客系统1.3 启动Tale服务1.4 访问博客地址 2. Linux安装Cpolar内网穿透3. 创建Tale博客公网地址4. 使用公网地址访问Tale 前言 今天给大家带来一款基于 Java 语言的轻量级博客开源项目——Tale&#xff0c;Tale…

数据采集实训电商数据爬取python代码 电商数据抓取

电商平台的数据抓取&#xff0c;一直是网页抓取公式的热门实战实例&#xff0c;之前我们通常是针对国内的电商平台进行数据抓取&#xff0c;昨天小编受到委托&#xff0c;针对一个俄罗斯电商平台wildberries做了数据抓取&#xff0c;抓取的主要内容是商品标题、价格及评价数量。…

python导入的缓存机制

问题来源&#xff1a; logger文件 import sysfrom loguru import loggerfrom app.internal.component.configer import settingsdef configure_logger():"""多进程环境&#xff0c;需要确保子进程能拿到正确初始化的logger实例:return:"""logg…

基于单片机的指纹采集识别系统设计

目 录 摘 要 I Abstract II 引 言 3 1 硬件选择与设计 5 1.1 总体设计及方案选择 5 1.1.1主控单片机选择 5 1.1.2传感器模块选择 6 1.1.3显示器模块选择 6 1.2 系统总体设计 7 2 系统硬件电路设计 8 2.1 系统主电路设计 8 2.1.1 主体电路设计 8 2.1.2 单片机最小系统设计 8 2.…

h5唤起微信小程序

wx-open-launch-weapp 就用这个 开放标签属于自定义标签&#xff0c;Vue会给予未知标签的警告&#xff0c;可通过配置Vue.config.ignoredElements [wx-open-launch-weapp] 来忽略Vue对开放标签的检查。 sdk授权。 调试打开时iOS会弹窗 noPermissionJsApi: []&#xff0c;confi…

wpscan专门针对wordpress的安全扫描工具

说明 WPScan是一款专门针对WordPress的漏洞扫描工具&#xff0c;它使用Ruby编程语言编写。WPScan能够扫描WordPress网站中的多种安全漏洞&#xff0c;包括WordPress本身的漏洞、插件漏洞和主题漏洞。此外&#xff0c;WPScan还能扫描类似robots.txt这样的敏感文件&#xff0c;并…

题目 3152: 接龙数列

题目描述: 对于一个长度为 K 的整数数列&#xff1a;A1, A2, . . . , AK&#xff0c;我们称之为接龙数列当且仅当 Ai 的首位数字恰好等于 Ai−1 的末位数字 (2 ≤ i ≤ K)。 例如 12, 23, 35, 56, 61, 11 是接龙数列&#xff1b;12, 23, 34, 56 不是接龙数列&#xff0c;因为…

基于FPGA的PSRAM接口设计与实现

该系列为神经网络硬件加速器应用中涉及的模块接口部分&#xff0c;随手记录&#xff0c;以免时间久了遗忘。 一 PSRAM与HyperRAM 1、概述 2、异同 接口协议不同&#xff0c;因此在IP设计时需要注意。 Hyperram(Winbond)&#xff1a;HyperBus协议 PSRAM(AP公司)&#xff1a;X…

【Linux内核文档翻译】NTB驱动程序

原文&#xff1a;NTB Drivers — The Linux Kernel documentation 译者&#xff1a;jklincn <jklincnoutlook.com> 日期&#xff1a;2024.03.07 NTB 驱动程序 NTB&#xff08;Non-Transparent Bridge&#xff0c;非透明桥&#xff09;是一种 PCI-Express 桥接芯片类型&a…

CSS中position的属性有哪些,区别是什么

position有以下属性值&#xff1a; 属性值概述absolute生成绝对定位的元素&#xff0c;相对于static定位以外的一个父元素进行定位。元素的位置通过left、top、right、bottom属性进行规定。relative生成相对定位的元素&#xff0c;相对于其原来的位置进行定位。元素的位置通过…