springboot启动过程笔记
springboot启动过程笔记2.7.7为例面试被问到了答不上来做个笔记。一、概括1、启动代码概括 入口 → 创建SpringApplication → 运行run方法2、核心步骤 创建默认上下文-加载环境-上下文设置参数-刷新上下文-成功后的通知二、入口代码SpringBootApplicationpublicclassSpringbootApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SpringbootApplication.class,args);}}通过SpringApplication.run启动。点进run方法publicstaticConfigurableApplicationContextrun(Class?primarySource,String...args){returnrun(newClass?[]{primarySource},args);}/** * Static helper that can be used to run a {link SpringApplication} from the * specified sources using default settings and user supplied arguments. * param primarySources the primary sources to load * param args the application arguments (usually passed from a Java main method) * return the running {link ApplicationContext} */publicstaticConfigurableApplicationContextrun(Class?[]primarySources,String[]args){returnnewSpringApplication(primarySources).run(args);}可以看到经过一层包装后调用了new SpringApplication(primarySources).run(args);分成创建SpringApplication与启动run方法两个部分。下面一一介绍。三、创建SpringApplication代码publicSpringApplication(Class?...primarySources){this(null,primarySources);}publicSpringApplication(ResourceLoaderresourceLoader,Class?...primarySources){this.resourceLoaderresourceLoader;Assert.notNull(primarySources,PrimarySources must not be null);this.primarySourcesnewLinkedHashSet(Arrays.asList(primarySources));this.webApplicationTypeWebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializersnewArrayList(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClassdeduceMainApplicationClass();}加载了很多配置监听器。感觉是启动前的准备工作。功能简介resourceLoader资源加载器资源加载器决定文件加载方式默认nullprimarySources启动类的类名启动类的类名webApplicationType 应用类型staticWebApplicationTypededuceFromClasspath(){if(ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS,null)!ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS,null)!ClassUtils.isPresent(JERSEY_INDICATOR_CLASS,null)){returnWebApplicationType.REACTIVE;}for(StringclassName:SERVLET_INDICATOR_CLASSES){if(!ClassUtils.isPresent(className,null)){returnWebApplicationType.NONE;}}returnWebApplicationType.SERVLET;}根据相关类是否存在来推断应用类型默认SERVLET三种web类型对比如下REACTIVE 非阻塞响应并发情况效率更高NONE 不包含web部分启动快内存cpu占用更小SERVLET 最常见的bootstrapRegistryInitializers初始化阶段会调用默认null关键代码privateTCollectionTgetSpringFactoriesInstances(ClassTtype,Class?[]parameterTypes,Object...args){ClassLoaderclassLoadergetClassLoader();// Use names and ensure unique to protect against duplicatesSetStringnamesnewLinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type,classLoader));ListTinstancescreateSpringFactoriesInstances(type,parameterTypes,classLoader,args,names);AnnotationAwareOrderComparator.sort(instances);returninstances;}publicstaticListStringloadFactoryNames(Class?factoryType,NullableClassLoaderclassLoader){ClassLoaderclassLoaderToUseclassLoader;if(classLoaderToUsenull){classLoaderToUseSpringFactoriesLoader.class.getClassLoader();}StringfactoryTypeNamefactoryType.getName();returnloadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName,Collections.emptyList());}加载spring.factories文件下key为org.springframework.context.ApplicationContextInitializer的所有类ApplicationContextInitializer会在上下文创建完成时执行默认有如下实现类0 {DelegatingApplicationContextInitializer1579}1 {SharedMetadataReaderFactoryContextInitializer1580}2 {ContextIdApplicationContextInitializer1581}3 {ConfigurationWarningsApplicationContextInitializer1582}4 {RSocketPortInfoApplicationContextInitializer1583}5 {ServerPortInfoApplicationContextInitializer1584}6 {ConditionEvaluationReportLoggingListener1585}setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));getSpringFactoriesInstances方法同上获取所有ApplicationContextInitializerApplicationListener贯穿整个创建周期根据type决定是否执行默认如下实现类0 {EnvironmentPostProcessorApplicationListener1677}1 {AnsiOutputApplicationListener1678}2 {LoggingApplicationListener1679}3 {BackgroundPreinitializer1680}4 {DelegatingApplicationListener1681}5 {ParentContextCloserApplicationListener1682}6 {ClearCachesApplicationListener1683}7 {FileEncodingApplicationListener1684}也是通过getSpringFactoriesInstances方法获取setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));mainApplicationClass主启动类推断出当前应用的主启动类privateClass?deduceMainApplicationClass(){try{StackTraceElement[]stackTracenewRuntimeException().getStackTrace();for(StackTraceElementstackTraceElement:stackTrace){if(main.equals(stackTraceElement.getMethodName())){returnClass.forName(stackTraceElement.getClassName());}}}catch(ClassNotFoundExceptionex){// Swallow and continue}returnnull;}遍历当前线程的调用栈找到第一个名为 main 的方法然后返回它所在的类。四、run方法分析代码publicConfigurableApplicationContextrun(String...args){// 1、打印耗时longstartTimeSystem.nanoTime();// 2、创建默认上下文DefaultBootstrapContextbootstrapContextcreateBootstrapContext();ConfigurableApplicationContextcontextnull;// 3、设置Headless模式configureHeadlessProperty();// 4、获取SpringApplicationRunListeners 监听器集合SpringApplicationRunListenerslistenersgetRunListeners(args);// 5、启用监听器触发其‘spring.boot.application.starting’事件listeners.starting(bootstrapContext,this.mainApplicationClass);try{// 6、入参args转成ApplicationArgumentsApplicationArgumentsapplicationArgumentsnewDefaultApplicationArguments(args);// 7、创建环境并触发监听器‘spring.boot.application.environment-prepared’事件ConfigurableEnvironmentenvironmentprepareEnvironment(listeners,bootstrapContext,applicationArguments);// 8、配置是否需要忽略的beanconfigureIgnoreBeanInfo(environment);// 9、打印bannerBannerprintedBannerprintBanner(environment);// 10、 创建真正的应用上下文contextcreateApplicationContext();// 11、注册启动检测器context.setApplicationStartup(this.applicationStartup);// 12 上下文添加参数并触发监听器 spring.boot.application.environment-prepared 与spring.boot.application.context-loaded事件prepareContext(bootstrapContext,context,environment,listeners,applicationArguments,printedBanner);// 13 刷新应用上下文创建bean对象refreshContext(context);// 14、刷新上下文之后的操作里面啥也afterRefresh(context,applicationArguments);// 15、计算耗时DurationtimeTakenToStartupDuration.ofNanos(System.nanoTime()-startTime);// 16、打印启动完成日志if(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),timeTakenToStartup);}// 17、触发监听器启动完成日志 spring.boot.application.context-preparedlisteners.started(context,timeTakenToStartup);// 18 调用所有实现了 ApplicationRunner 和 CommandLineRunner 接口的 BeancallRunners(context,applicationArguments);}catch(Throwableex){// 19、异常处理handleRunFailure(context,ex,listeners);thrownewIllegalStateException(ex);}try{// 20、计算从应用启动开始到所有 Runner 执行完成的总耗时并通知监听器“应用已完全就绪”发布 ApplicationReadyEventDurationtimeTakenToReadyDuration.ofNanos(System.nanoTime()-startTime);listeners.ready(context,timeTakenToReady);}catch(Throwableex){// 19、异常处理handleRunFailure(context,ex,null);thrownewIllegalStateException(ex);}returncontext;}光看备注还是不清楚补充个图简单来说就是创建默认上下文-加载环境-上下文设置参数-刷新上下文-成功后的通知。具体内容有空再一一记录。四、核心事件介绍介绍创建默认上下文-加载环境-上下文设置参数-刷新上下文-成功后的通知4.1、创建默认上下文 createBootstrapContextprivateDefaultBootstrapContextcreateBootstrapContext(){DefaultBootstrapContextbootstrapContextnewDefaultBootstrapContext();this.bootstrapRegistryInitializers.forEach((initializer)-initializer.initialize(bootstrapContext));returnbootstrapContext;}直接new一个DefaultBootstrapContext然后遍历bootstrapRegistryInitializers执行对应方法。没啥好讲的。4.2、加载环境 prepareEnvironmentprivateConfigurableEnvironmentprepareEnvironment(SpringApplicationRunListenerslisteners,DefaultBootstrapContextbootstrapContext,ApplicationArgumentsapplicationArguments){// 创建环境 ConfigurableEnvironmentConfigurableEnvironmentenvironmentthis.getOrCreateEnvironment();this.configureEnvironment(environment,applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);listeners.environmentPrepared(bootstrapContext,environment);DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty(spring.main.environment-prefix),Environment prefix cannot be set via properties.);this.bindToSpringApplication(environment);if(!this.isCustomEnvironment){EnvironmentConverterenvironmentConverternewEnvironmentConverter(this.getClassLoader());environmentenvironmentConverter.convertEnvironmentIfNecessary(environment,this.deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);returnenvironment;}4.2.1 创建环境 getOrCreateEnvironmentprivateConfigurableEnvironmentgetOrCreateEnvironment(){if(this.environment!null){returnthis.environment;}else{ConfigurableEnvironmentenvironmentthis.applicationContextFactory.createEnvironment(this.webApplicationType);if(environmentnullthis.applicationContextFactory!ApplicationContextFactory.DEFAULT){environmentApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);}return(ConfigurableEnvironment)(environment!null?environment:newApplicationEnvironment());}}

相关新闻

多AI编程代理MCP与Skills统一同步方案:从配置割据到单一事实来源

多AI编程代理MCP与Skills统一同步方案:从配置割据到单一事实来源

先说个很真实的场景:某天我需要在本机的几个 AI 编程代理里加同一个 MCP 服务器,那个服务器能抓取网页正文给 Claude Code、Codex、Cursor、Hermes Agent 同时用。我原以为这是十分钟的小事,结果打开四个配置文件,逐个粘贴、调格式…

2026/10/15 21:28:45 阅读更多 →
vtstscripts.tgz部署避坑指南:Python版本、权限与YAML依赖详解

vtstscripts.tgz部署避坑指南:Python版本、权限与YAML依赖详解

简介:这是一套面向Linux运维工程师与服务器管理员的实用脚本工具集,聚焦系统监控、日志分析、服务管理、备份恢复及安全巡检等高频运维场景,助力提升日常操作效率与故障响应能力。资源包共152个文件,以Perl(104个.pl/.…

2026/10/15 21:27:43 阅读更多 →
手写C++词法分析器:从状态机到可调试Lexer实战

手写C++词法分析器:从状态机到可调试Lexer实战

1. 项目概述:为什么词法分析是编译器的“第一道安检门”你打开一个C源文件,敲下int main() { return 0; },按下编译键——几毫秒后,程序跑起来了。但你可能没意识到,就在你敲下回车的瞬间,这行代码已经悄悄…

2026/10/15 21:27:43 阅读更多 →

最新新闻

代码库变成书:AiReadCode从源码生成结构化阅读指南

代码库变成书:AiReadCode从源码生成结构化阅读指南

1. 为什么要做“代码库变成书”这件事先交代一个背景:有段时间我一直在啃 Spring AI Alibaba 的源码。这个项目体量不算夸张,但牵扯到模型接入、工具调用、Agent 编排、Spring 生态集成,读起来经常是“翻开一个类,跳转三个依赖&am…

2026/10/15 22:25:16 阅读更多 →
OpenCV undefined reference 报错排查:编译链接原理与解决方案

OpenCV undefined reference 报错排查:编译链接原理与解决方案

先说一个很典型的场景:某A同学照着网上的教程写了一个读取图片并显示的程序,编译时头文件、语法一点问题没有,g一执行,终端里刷出来一片undefined reference to cv::imread、undefined reference to cv::Mat。他第一反应是代码写错…

2026/10/15 22:25:16 阅读更多 →
基于S7-200 PLC与组态王的变频恒压供水系统设计详解

基于S7-200 PLC与组态王的变频恒压供水系统设计详解

1. 系统整体方案设计1.1 变频恒压供水为什么能解决实际问题小区供水这件事,听起来好像就是“水泵抽水、水箱放水”那么简单,但真正做过的人都知道,麻烦全藏在细节里。传统的老式供水方式是水泵直接给管网打水,到了晚上用水低谷&am…

2026/10/15 22:25:16 阅读更多 →
从CNN分类到语义分割:Encoder-Decoder结构设计与实战

从CNN分类到语义分割:Encoder-Decoder结构设计与实战

1. 从分类到分割:为什么CNN要长出“解码器”很多人学CNN都是从图像分类入门的——输入一张图,输出一个标签,比如“猫”或者“狗”。这套逻辑跑通之后,很容易产生一种错觉:CNN不过如此。但当你真正拿到一个语义分割任务…

2026/10/15 22:25:16 阅读更多 →
阿里云AI算力硬件体系:三层架构与实战选型指南

阿里云AI算力硬件体系:三层架构与实战选型指南

1. 为什么“看懂阿里云AI算力硬件体系”这件事,比大多数人想的更难也更重要你有没有遇到过这样的情况:项目刚立项,技术方案里写着“需部署大模型推理服务”,负责人拍板:“上阿里云,资源好调、生态成熟。”结…

2026/10/15 22:25:16 阅读更多 →
无需Mac如何一键创建iOS发布证书与描述文件:码友上架证书管理实操分享

无需Mac如何一键创建iOS发布证书与描述文件:码友上架证书管理实操分享

做iOS上架,第一步往往就卡在证书上:传统流程需要一台Mac,在钥匙串里生成CSR、导出P12,再手动创建描述文件。团队协作时证书分散在个人电脑里,交接容易出错。本文分享用码友上架做证书管理的实操流程——没有Mac也能在线…

2026/10/15 22:24:09 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →