SpringBoot启动流程及自动配置
SpringBoot启动流程源码:
1、启动SpringBoot启动类SpringbootdemoApplication中的main方法。
@SpringBootApplication
public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplication.class, args);}
}
2、调用SpringApplication.run(SpringbootdemoApplication.class, args),该方法是一个静态方法。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);
}
3、继续调用SpringApplication内部的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);
}
,且构建了一个SpringApplication对象,应用程序将从指定的主要来源加载Bean
public SpringApplication(Class<?>... primarySources) {this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {//resourceLoader赋值为Nullthis.resourceLoader = resourceLoader;//primarySources不为空,继续向下执行。为空抛异常Assert.notNull(primarySources, "PrimarySources must not be null");//将SpringbootdemoApplication(启动类)赋值给primarySources this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//从classpath类路径推断Web应用类型,有三种Web应用类型,分别是//NONE: 该应用程序不应作为 Web 应用程序运行,也不应启动嵌入式 Web 服务器//SERVLET: 该应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。//REACTIVE: 该应用程序应作为响应式 Web 应用程序运行,并应启动嵌入式响应式 Web 服务器this.webApplicationType = WebApplicationType.deduceFromClasspath();//初始化bootstrapRegistryInitializers,通过getSpringFactoriesInstances()获取工厂实例,//底层使用的是反射Class<?> instanceClass = ClassUtils.forName(name, classLoader)动态加载实例对象。this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));//初始化ApplicationContextInitializer集合setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//初始化ApplicationListenersetListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//获取StackTraceElement数组遍历,通过反射获取堆栈中有main方法的类。this.mainApplicationClass = deduceMainApplicationClass();
}
4、运行 Spring 应用程序,创建并刷新一个新的 ApplicationContext。
public ConfigurableApplicationContext run(String... args) {long startTime = System.nanoTime();//通过BootstrapRegistryInitializer来initialize默认的DefaultBootstrapContextDefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;//配置java.awt.headless属性configureHeadlessProperty();//获取SpringApplicationRunListeners监听器SpringApplicationRunListeners listeners = getRunListeners(args);//启动SpringApplicationRunListeners监听,表示SpringApplication启动(触发ApplicationStartingEvent事件)listeners.starting(bootstrapContext, this.mainApplicationClass);try {//创建ApplicationArguments对象,封装了args参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//做相关环境准备,绑定到SpringApplication,返回可配置环境对象ConfigurableEnvironment ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);//配置spring.beaninfo.ignore,设置为true.即跳过搜索Bean信息configureIgnoreBeanInfo(environment);//控制台打印SpringBoot的Banner(横幅)标志Banner printedBanner = printBanner(environment);//根据WebApplicationType从ApplicationContextFactory工厂创建ConfigurableApplicationContextcontext = createApplicationContext();//设置ConfigurableApplicationContext中的ApplicationStartup为DefaultApplicationStartupcontext.setApplicationStartup(this.applicationStartup);//应用所有的ApplicationContextInitializer容器初始化器初始化context,触发ApplicationContextInitializedEvent事件监听,打印启动日志信息,启动Profile日志信息。//ConfigurableListableBeanFactory中注册单例Bean(springApplicationArguments),并为该BeanFactory中的部分属性赋值。//加载所有的source.并将Bean加载到ConfigurableApplicationContext,触发ApplicationPreparedEvent事件监听prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);//刷新容器(在方法中集成了Web容器具体请看 https://editor.csdn.net/md/?articleId=123136262)refreshContext(context);//刷新容器的后置处理(空方法)afterRefresh(context, applicationArguments);//启动花费的时间Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {//打印日志Started xxx in xxx seconds (JVM running for xxxx)new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}//触发ApplicationStartedEvent事件监听。上下文已刷新,应用程序已启动。listeners.started(context, timeTakenToStartup);//调用ApplicationRunner和CommandLineRunnercallRunners(context, applicationArguments);}//处理运行时发生的异常,触发ApplicationFailedEvent事件监听catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {//启动准备消耗的时间Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);//在run方法完成前立即触发ApplicationReadyEvent事件监听,表示应用上下文已刷新,并且CommandLineRunners和ApplicationRunners已被调用。listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;
}
SpringBoot的自动配置:
SpringBoot的启动类上总是有@SpringBootApplication这个注解。接下来我们来了解一下这个注解。进入@SpringBootApplication源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {......
}
由此可见,@SpringBootApplication注解是一个组合注解,由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan注解组成。
@SpringBootConfiguration其实就是一个@Configuration,表明这是一个配置类,可以向容器注入组件。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {......
}
@EnableAutoConfiguration由@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class})注解组成
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {......
}
@AutoConfigurationPackage内部用到了@Import导入Registrar
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {......
}
Registrar实现了ImportBeanDefinitionRegistrar接口,因此可将组件都扫描注冊到 spring 容器中
static class Registrar implements ImportBeanDefinitionRegistrar,
DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new
AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new
String[0]));
}
public Set determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
@AutoConfigurationPackage将主配置类(@SpringBootApplication
标注的类)所在包下的所有组件都扫描注册到Spring容器中。
@Import({AutoConfigurationImportSelector.class}) 将AutoConfigurationImportSelector(自动配置导入选择器)导入容器中
AutoConfigurationImportSelector类中的selectImports()方法的作用是选择导入过滤后的自动配置
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
getAutoConfigurationEntry(AnnotationMetadata
annotationMetadata)根据annotationMetadata(即我们的启动类SpringbootdemoApplication)获取AutoConfigurationEntry
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {//获取注解属性AnnotationAttributes attributes = this.getAttributes(annotationMetadata);//从META-INF/spring.factories中获取候选配置。List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//去除重复配置configurations = this.removeDuplicates(configurations);//获取注解中的排除项Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);//检查排除类this.checkExcludedClasses(configurations, exclusions);//从上面的候选配置中移除所有排除的配置类configurations.removeAll(exclusions);//通过ConfigurationClassFilter筛选配置configurations = this.getConfigurationClassFilter().filter(configurations);//触发自动配置导入事件this.fireAutoConfigurationImportEvents(configurations, exclusions);//返回排除后的自动配置Entryreturn new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}}
总结SpringBoot启动原理
1、调用有@SpringBootApplication注解的启动类的main方法
2、通过调用SpringApplication内部的run()方法构建SpringApplication对象。
创建SpringApplication对象:
2.1 PrimarySources 不为空,将启动类赋值给primarySources 对象。
2.2 从classpath类路径推断Web应用类型,有三种Web应用类型NONE、SERVLET、REACTIVE
2.3 初始化bootstrapRegistryInitializers
2.4 初始化ApplicationContextInitializer集合
2.5 初始化ApplicationListener
2.6 获取StackTraceElement数组遍历,通过反射获取堆栈中有main方法A的。 3、调用SpringBootApplication的run方法。 4、long startTime = System.nanoTime();
记录项目启动时间。 5、通过BootstrapRegistryInitializer来初始化DefaultBootstrapContext
6、getRunListeners(args)获取SpringApplicationRunListeners监听器 7、
listeners.starting()触发ApplicationStartingEvent事件
8、prepareEnvironment(listeners, bootstrapContext,
applicationArguments) 将配置文件读取到容器中,返回ConfigurableEnvironment 对象。
9、printBanner(environment) 打印Banner图,即SpringBoot启动时的图案。
10、根据WebApplicationType从ApplicationContextFactory工厂创建ConfigurableApplicationContext,并设置ConfigurableApplicationContext中的ApplicationStartup为DefaultApplicationStartup
11、
调用prepareContext()初始化context等,打印启动日志信息,启动Profile日志信息,并为BeanFactory中的部分属性赋值。
12、刷新容器,在该方法中集成了Tomcat容器 13、加载SpringMVC.
14、刷新后的方法,空方法,给用户自定义重写afterRefresh() 15、Duration timeTakenToStartup =
Duration.ofNanos(System.nanoTime() - startTime)算出启动花费的时间。
16、打印日志Started xxx in xxx seconds (JVM running for xxxx)
17、listeners.started(context,
timeTakenToStartup)触发ApplicationStartedEvent事件监听。上下文已刷新,应用程序已启动。
18、调用ApplicationRunner和CommandLineRunner 19、返回上下文。
相关文章:
SpringBoot启动流程及自动配置
SpringBoot启动流程源码: 1、启动SpringBoot启动类SpringbootdemoApplication中的main方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplication.class, …...
【Linux】进程轻松入门
目录 一, 冯* 诺依曼体系结构 1,存储结构 编辑 二, 操作系统 1,概念 2,设计OS的目的 3,定位 4,如何理解 "管理" 5, 总结 三,进程 1. 概念 那么…...
【使用时空RBF-NN进行非线性系统识别】实现了 RBF、分数 RBF 和时空 RBF 神经网络,用于非线性系统识别研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Tomcat 安装配置教程及成功后,启动失败报错解决方案
解决方案 我的报错原因是因为我的JDK是1.8的而我的Tomcat是10版本的,可能是因为版本原因吧,我重新装了Tomcat 9就可以启动成功了! 简单说下安装的时候需要注意哪些步骤吧 今天我在安装tomcat10的时候,安装成功后,启…...
C#文件操作从入门到精通(2)——查看某个dll中有哪些函数
kernel32.dll中含有ini文件操作使用的函数,我们可以通过VisualStudio自带的dumpbin.exe查看dll所包含的函数,操作步骤如下: 1、找到dumpbin.exe所在的文件夹 我的电脑中安装了VisualStudio2019社区版以及VisualStudio2017Professional,但是我发现VisualStudio2019社区版中…...
二分查找算法(全网最详细代码演示)
二分查找也称 半查找(Binary Search),它时一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字 有序 排列。 注意:使用二分查找的前提是 该数组是有序的。 在实际开…...
draw up a plan
爱情是美好的,却不是唯一的。爱情只是属于个人化的感情。 推荐一篇关于爱情的美文: 在一个小镇上,有一家以制作精美巧克力而闻名的手工巧克力店,名叫“甜蜜之爱”。这家巧克力店是由一位名叫艾玛的年轻女性经营的,她对…...
抖音seo源码开发源代码开发技术分享
一、 抖音SEO源码开发,需要掌握以下技术: 抖音API接口:抖音提供了丰富的API接口,包括用户信息、视频信息、评论信息等。 数据爬取技术:通过抓包分析抖音接口的数据结构,可以使用Python等编程语言编写爬虫程…...
QEMU(Quick Emulator)
QEMU(Quick Emulator)是一款由法布里斯贝拉等人编写的免费的可执行硬件虚拟化的开源托管虚拟机。它可以通过动态的二进制转换模拟CPU,并提供一组设备模型,使它能够运行多种未修改的客户机OS。QEMU还可以为user-level的进程执行CPU…...
Gateway结合nacos(lb://xxx)无效问题
Gateway结合nacos无效 版本如下: com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2021.0.1.0 org.springframework.cloud:spring-cloud-starter-gateway:3.1.1 配置如下: server:port: 7000 spring:application:name: springCloudGa…...
NODEJS笔记
全局对象 global/window console.log/info/warn/error/time/timeEnd process.arch/platform/version/env/kill/pid/nextTick Buffer.alloc(5,abcde) String/toString setTimeout/clearTimeout setInterval/clearInterval setImmediate/clearImmediate process.nextTi…...
无涯教程-jQuery - html( )方法函数
html(val)方法获取第一个匹配元素的html内容(innerHTML)。此属性在XML文档上不可用。 html( ) - 语法 selector.html( ) html( ) - 示例 以下是一个简单的示例,简单说明了此方法的用法- <html><head><title>The jQuery Example</title>…...
Linux vsftp三种模式的简单配置部署
环境:Debian 6.1.27-1kali1 (2023-05-12) vsftpd 安装 --查看是否当前系统是否已安装 apt list --installed | grep vsftpd 没有安装的话,就正常安装 apt-get update apt-get install vsftpd 一、匿名用户模式 分享一些不重要文件,任…...
6.1.tensorRT高级(1)-概述
目录 前言1. tensorRT高级概述总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。 本次课程学习 tensorRT 高级-概述 课程大纲可看下面的思维…...
【Python】将M4A\AAC录音文件转换为MP3文件
文章目录 m4aaac 基础环境: sudo apt-get install ffmpegm4a 要将M4A文件转换为MP3文件,你可以使用Python中的第三方库pydub。pydub使得音频处理变得非常简单。在开始之前,请确保你已经安装了pydub库,如果没有,可以通…...
个性新颖纯css手风琴效果选项卡
当涉及到个性新颖的纯CSS手风琴效果选项卡时,有多种方法可以实现。以下是三种可能的方法: 三种方法实现 方法一:使用:target伪类和CSS过渡效果 <style>.accordion {width: 300px;}.accordion-item {overflow: hidden;max-height: 0;…...
js的sendBeacon方法介绍
js的sendBeacon方法介绍 Beacon API是一种轻量级且有效的将网页活动记录到服务器的方法。它是一个 JavaScript API,可帮助开发人员将少量数据(例如分析或跟踪信息、调试或诊断数据)从浏览器发送到服务器。 在本文中,我们将介绍B…...
【Tomcat---1】IDEA控制台tomcat日志输出乱码解决
一、修改IDEA的文件编码配置为UTF-8 二、修改IDEA的vmoptions文件,添加-Dfile.encodingUTF-8 到Tomcat目录/conf文件夹修改logging.properties 重启idea即可。采用统一的编码...
Redis学习路线(2)—— Redis的数据结构
一、Redis的数据结构 Redis是一个Key-Value的数据库,key一般是String类型,不过Value的类型却有很多: String: Hello WorldHash: {name: "jack", age: 21}List: [A -> B -> C -> C]Set…...
【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(持久化功能分析)
探究Redis服务启动的过程机制的技术原理和流程分析的指南(持久化功能分析) Redis提供的持久化机制Redis持久化如何工作Redis持久化的故障分析持久化频率操作分析数据库多久调用一次write,将数据写入内核缓冲区?内核多久将系统缓冲…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...
