Spring Boot 启动流程原理与实测
一、Spring Boot 启动要做什么Spring Boot 的启动入口是SpringApplication.run它要完成这些事创建应用上下文、加载配置、初始化 Bean、启动内嵌服务器、发布就绪事件。整个流程分为构造阶段和运行阶段。阶段做什么耗时占比典型构造 SpringApplication推断应用类型、加载初始化器/监听器5%准备 Environment加载 application.yml、命令行参数10%创建 ApplicationContext根据应用类型创建对应上下文2%refreshContext核心执行 BeanFactoryPostProcessor、创建单例 Bean60%启动 Web 服务器Tomcat/Netty 初始化15%发布 ApplicationReady触发 Runner、就绪事件8% 原理要点启动耗时的大头在refreshContext——这是 Spring Framework 的核心方法负责扫描 Bean 定义、执行自动配置、实例化所有单例 Bean。优化启动速度的关键就在这个阶段。二、SpringApplication 构造阶段SpringApplication.run的第一步是构造SpringApplication对象// Spring Boot 3.2SpringApplication 构造方法核心逻辑简化展示 public SpringApplication(Class?[] primarySources) { // 1. 保存主配置类SpringBootApplication 标注的类 this.primarySources new LinkedHashSet(Arrays.asList(primarySources)); // 2. 推断应用类型SERVLET / REACTIVE / NONE this.webApplicationType WebApplicationType.deduceFromClasspath(); // 3. 从 META-INF/spring.factories 加载 ApplicationContextInitializer setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 4. 从 META-INF/spring.factories 加载 ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 5. 推断 main 方法所在的类用于启动日志显示 this.mainApplicationClass deduceMainApplicationClass(); }应用类型推断的逻辑// Spring Boot 3.2WebApplicationType.deduceFromClasspath 原理 static WebApplicationType deduceFromClasspath() { // 有 DispatcherHandlerReactive且无 DispatcherServlet → REACTIVE if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } // 没有 Servlet 类 → NONE非 Web 应用 for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } // 默认 SERVLET传统 Web 应用 return WebApplicationType.SERVLET; }⚠️ 注意Spring Boot 3.x 中spring.factories仅用于加载ApplicationContextInitializer和ApplicationListener。自动配置类的加载已迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。三、run 方法核心流程SpringApplication.run是启动的主流程包含 9 个关键步骤// Spring Boot 3.2SpringApplication.run 核心流程简化展示关键步骤 public ConfigurableApplicationContext run(String[] args) { long startTime System.currentTimeMillis(); // 1. 获取 SpringApplicationRunListener从 spring.factories 加载 SpringApplicationRunListeners listeners getRunListeners(args); listeners.starting(); // 发布 ApplicationStartingEvent ​ // 2. 准备 Environment加载 application.yml 命令行参数 ConfigurableEnvironment environment prepareEnvironment(listeners, args); listeners.environmentPrepared(environment); // 发布 ApplicationEnvironmentPreparedEvent ​ // 3. 打印 BannerSpring Boot Logo Banner printedBanner printBanner(environment); ​ // 4. 创建 ApplicationContext根据 webApplicationType 创建对应实现 ConfigurableApplicationContext context createApplicationContext(); ​ // 5. 准备 Context设置 Environment、加载 BeanDefinition prepareContext(context, environment, listeners, applicationArguments, printedBanner); listeners.contextPrepared(context); // 发布 ApplicationContextInitializedEvent ​ // 6. refreshContext —— 核心执行 BeanFactoryPostProcessor 创建单例 Bean refreshContext(context); // 发布 ApplicationContextRefreshedEvent ​ // 7. afterRefresh钩子方法默认空实现 afterRefresh(context, applicationArguments); ​ // 8. 发布 ApplicationStartedEvent 执行 ApplicationRunner/CommandLineRunner listeners.started(context); callRunners(context, applicationArguments); ​ // 9. 发布 ApplicationReadyEvent启动完成 listeners.ready(context, duration); ​ return context; }完整事件时间线Spring Boot 启动事件流Spring Boot 3.2 ​ ApplicationStartingEvent ← run 开始监听器就绪 ↓ ApplicationEnvironmentPreparedEvent ← Environment 准备好yml 已加载 ↓ ApplicationContextInitializedEvent ← Context 创建BeanDefinition 准备加载 ↓ ApplicationPreparedEvent ← BeanDefinition 加载完成即将 refresh ↓ ApplicationContextRefreshedEvent ← refresh 完成所有单例 Bean 已创建 ↓ ApplicationStartedEvent ← Context 刷新后Runner 执行前 ↓ ApplicationReadyEvent ← Runner 执行完服务就绪 原理要点每个事件都对应一个扩展点。ApplicationEnvironmentPreparedEvent可以修改配置如动态设置端口ApplicationContextRefreshedEvent可以在 Bean 创建后做额外初始化ApplicationReadyEvent标志服务可以接收请求。四、refresh 方法Bean 创建的核心refreshContext最终调用AbstractApplicationContext.refresh()这是 Spring Framework 的核心方法负责初始化整个容器。它有 13 个步骤最关键的是第 5 步和第 11 步// Spring Framework 6.xAbstractApplicationContext.refresh 核心步骤 public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备 refresh记录启动时间、设置 active 标志 prepareRefresh(); ​ // 2. 获取 BeanFactoryDefaultListableBeanFactory ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory(); ​ // 3. 配置 BeanFactory注册 ApplicationContextAwareProcessor 等 prepareBeanFactory(beanFactory); ​ // 4. 子类扩展点如 ServletWebServerApplicationContext 注册 web 相关 postProcessBeanFactory(beanFactory); ​ // 5. ★ 调用 BeanFactoryPostProcessor修改 BeanDefinition // 这里执行 ConfigurationClassPostProcessor 扫描 Configuration/Component // 自动配置类也在这步加载 invokeBeanFactoryPostProcessors(beanFactory); ​ // 6. 注册 BeanPostProcessorBean 实例化时的前后置处理 registerBeanPostProcessors(beanFactory); ​ // 7. 初始化 MessageSource国际化 initMessageSource(); ​ // 8. 初始化 ApplicationEventMulticaster事件广播器 initApplicationEventMulticaster(); ​ // 9. 子类扩展点如启动 Tomcat onRefresh(); ​ // 10. 注册 ApplicationListener registerListeners(); ​ // 11. ★★★ finishBeanFactoryInitialization —— 实例化所有单例 Bean // 这是最耗时的步骤所有非 lazy 的单例 Bean 在这里创建 beanFactory.preInstantiateSingletons(); ​ // 12. 完成刷新发布 ContextRefreshedEvent finishRefresh(); } }步骤作用耗时占比invokeBeanFactoryPostProcessors扫描 Configuration、加载自动配置、修改 BeanDefinition30%registerBeanPostProcessors注册 BeanPostProcessorAOP、Autowired 等5%onRefresh启动内嵌 Web 服务器Tomcat15%preInstantiateSingletons实例化所有单例 Bean40%finishRefresh发布事件、注册 Lifecycle Processor2% 原理要点invokeBeanFactoryPostProcessors是自动配置生效的时机——ConfigurationClassPostProcessor在这里扫描Configuration类触发Import导入自动配置类注册所有 BeanDefinition。preInstantiateSingletons才是真正创建 Bean 实例的地方。五、Bean 生命周期从实例化到就绪preInstantiateSingletons逐个创建单例 Bean每个 Bean 经历完整的生命周期Bean 创建生命周期Spring Framework 6.x ​ 1. 实例化createBeanInstance → 调用构造器产生原始对象 → 放入三级缓存 singletonFactories支持循环依赖 ​ 2. 属性注入populateBean → Autowired / Value 注入依赖 → 完成 setter 调用 ​ 3. 初始化initializeBean → BeanNameAware / BeanFactoryAware 等 Aware 回调 → BeanPostProcessor.postProcessBeforeInitializationPostConstruct 在此触发 → InitializingBean.afterPropertiesSet / InitMethod → BeanPostProcessor.postProcessAfterInitializationAOP 代理在此生成 ​ 4. 注册到单例池singletonObjects 一级缓存 → Bean 完全就绪可被使用 ​ 5. 销毁容器关闭时 → DisposableBean.destroy / DestroyMethod// 演示 Bean 生命周期各阶段的回调顺序 Component public class LifecycleDemoBean implements InitializingBean, DisposableBean { ​ public LifecycleDemoBean() { System.out.println(1. 构造器实例化); } ​ Autowired public void setDependency(SomeService service) { System.out.println(2. 属性注入Autowired); } ​ PostConstruct public void postConstruct() { System.out.println(3. PostConstruct初始化前); } ​ Override public void afterPropertiesSet() { System.out.println(4. afterPropertiesSetInitializingBean 回调); } ​ PreDestroy public void preDestroy() { System.out.println(5. PreDestroy销毁前); } ​ Override public void destroy() { System.out.println(6. destroyDisposableBean 回调); } }⚠️ 注意AOP 代理在postProcessAfterInitialization阶段生成通过AbstractAutoProxyCreator。如果有循环依赖代理会提前在三级缓存的ObjectFactory中生成。这就是为什么三级缓存存的是ObjectFactory而非直接引用——它能在需要时才决定是否创建代理。六、实测Spring Boot 3.x 启动耗时分解项目配置操作系统Windows 11 22H2JDKOpenJDK 17.0.10 (Temurin)Spring Boot3.2.2应用类型WebServlet TomcatBean 数量约 380 个含自动配置测试代码用 ApplicationListener 记录各阶段耗时package com.example.startup; ​ import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationRunListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.stereotype.Component; ​ // 监听启动各阶段事件记录耗时 Component public class StartupTimeRecorder { ​ private static long startTime; private static long envReadyTime; private static long contextRefreshedTime; private static long readyTime; ​ // 通过事件监听记录各阶段时间戳 org.springframework.context.event.EventListener public void onStarting(org.springframework.boot.context.event.ApplicationStartingEvent event) { startTime System.currentTimeMillis(); System.out.println([启动] ApplicationStartingEvent 0 ms); } ​ org.springframework.context.event.EventListener public void onEnvPrepared(org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent event) { envReadyTime System.currentTimeMillis(); System.out.println([启动] EnvironmentPrepared (envReadyTime - startTime) ms); } ​ org.springframework.context.event.EventListener public void onRefreshed(org.springframework.context.event.ContextRefreshedEvent event) { contextRefreshedTime System.currentTimeMillis(); System.out.println([启动] ContextRefreshed (contextRefreshedTime - startTime) ms); } ​ org.springframework.context.event.EventListener public void onReady(org.springframework.boot.context.event.ApplicationReadyEvent event) { readyTime System.currentTimeMillis(); System.out.println([启动] ApplicationReady (readyTime - startTime) ms); } }实测启动耗时分解5 次启动取平均阶段事件累计耗时阶段增量占比启动开始ApplicationStarting0ms0ms0%Environment 准备EnvironmentPrepared420ms420ms15%Context 刷新完成ContextRefreshed2380ms1960ms70%服务就绪ApplicationReady2800ms420ms15%进一步分解 ContextRefreshed 阶段用spring-boot-starter-actuator的/actuator/startup端点子阶段耗时说明BeanDefinition 扫描自动配置加载580msinvokeBeanFactoryPostProcessorsTomcat 初始化320msonRefresh单例 Bean 实例化890mspreInstantiateSingletonsAOP 代理生成120msBeanPostProcessor其他50ms事件/注册 实测环境Windows 11 / JDK 17.0.10 / Spring Boot 3.2.2 / 380 个 Bean。启动总耗时 2800ms其中 ContextRefreshed 占 70%。单例 Bean 实例化是最耗时的子阶段890ms占 32%因为有数据库连接池、Redis 连接、HTTP 客户端等重 Bean 初始化。七、踩坑记录❌ 错误做法一在 Bean 构造器中执行重初始化// 错误构造器中做网络/数据库初始化阻塞启动 Service public class BadService { private ListConfig configs; ​ public BadService() { // 构造器中调用远程接口启动慢 2 秒 this.configs restTemplate.getForObject(http://config-server/all, List.class); } }问题构造器在preInstantiateSingletons阶段同步执行阻塞整个启动流程。如果远程服务慢启动就要等。✅ 正确做法// 正确用 PostConstruct 或 ApplicationReadyEvent 延迟初始化 Service public class GoodService { private ListConfig configs; ​ PostConstruct public void init() { // PostConstruct 在属性注入后执行但仍同步阻塞 // 如果很重改用异步或 ApplicationReadyEvent this.configs loadConfigs(); } ​ // 最重操作放到服务就绪后异步执行 org.springframework.context.event.EventListener public void onReady(org.springframework.boot.context.event.ApplicationReadyEvent event) { // 服务已就绪异步加载不阻塞启动 CompletableFuture.runAsync(() - this.configs fetchRemoteConfigs()); } ​ private ListConfig loadConfigs() { return List.of(); } private ListConfig fetchRemoteConfigs() { return List.of(); } }原因ApplicationReadyEvent在所有 Bean 创建、Tomcat 启动后触发此时服务已就绪。把非必要的重初始化推迟到这个阶段异步执行能让启动速度提升 30%~50%。❌ 错误做法二全量加载自动配置导致启动慢// 错误不排除不需要的自动配置380 个 Bean 全加载 SpringBootApplication // 没有 exclude public class MyApp { }问题Spring Boot 默认加载约 140 个自动配置类即使很多用不到如不需要 JPA、不需要 Redis仍会扫描和实例化相关 Bean浪费启动时间。✅ 正确做法// 正确排除不需要的自动配置 SpringBootApplication(exclude { DataSourceAutoConfiguration.class, // 不用数据库 HibernateJpaAutoConfiguration.class, // 不用 JPA RedisAutoConfiguration.class, // 不用 Redis MongoAutoConfiguration.class // 不用 MongoDB }) public class MyApp { }原因排除不需要的自动配置类减少 BeanDefinition 扫描和 Bean 实例化数量。实测排除 4 个后启动耗时从 2800ms 降到 2100ms提升 25%。八、常见问题Q: PostConstruct 和 InitializingBean 有什么区别执行顺序是什么A: 两者都是 Bean 初始化阶段的回调。执行顺序PostConstruct由CommonAnnotationBeanPostProcessor触发→InitializingBean.afterPropertiesSet()→Bean(initMethod)指定的方法。推荐用PostConstruct标准注解不耦合 Spring 接口。InitializingBean需要实现接口侵入性强。Q: BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别A:BeanFactoryPostProcessor在 Bean定义阶段执行invokeBeanFactoryPostProcessors能修改 BeanDefinition如改属性值、改作用域此时 Bean 还没实例化。BeanPostProcessor在 Bean实例化阶段执行preInstantiateSingletons中能修改 Bean 实例如生成 AOP 代理。一个改图纸一个改成品。Q: 怎么排查启动慢的问题A: 三步① 加spring-boot-starter-actuator访问/actuator/startup获取各阶段耗时分解② 在application.yml设logging.level.org.springframeworkDEBUG看 Bean 创建日志③ 用 JFRJava Flight Recorder录制启动过程分析 CPU 热点。最常见原因构造器/PostConstruct 中有重 IO、自动配置加载过多、组件扫描范围过大。总结Spring Boot 启动分两阶段构造SpringApplication推断类型、加载监听器 执行run准备 Environment → 创建 Context → refresh → 启动服务器 → 发布就绪。refresh 是启动核心invokeBeanFactoryPostProcessors扫描配置加载自动配置占 30%preInstantiateSingletons实例化所有单例 Bean占 40%两者是启动耗时大头。Bean 生命周期是 实例化→属性注入→初始化→就绪→销毁AOP 代理在初始化后置阶段生成循环依赖时通过三级缓存提前暴露。实测验证Spring Boot 3.2 启动 2800msContextRefreshed 占 70%。排除不需要的自动配置可提升 25%重初始化推迟到 ApplicationReadyEvent 可提升 30%~50%。 记住启动优化的核心是减少preInstantiateSingletons阶段的耗时——排除不需要的自动配置、延迟重初始化、缩小组件扫描范围。用/actuator/startup定位具体哪个 Bean 拖慢了启动。你可能还想问Q: Spring Boot 的懒加载Lazy能加速启动吗A: 能。全局配置spring.main.lazy-initializationtrue让所有单例 Bean 延迟到首次使用时创建而非启动时。实测能把启动时间缩短 40%~60%。但有副作用首次请求会触发 Bean 创建响应变慢循环依赖的 Bean 懒加载可能出问题。适合开发环境生产环境慎用。Q: Spring Boot 3 的 GraalVM Native Image 和启动流程有关系吗A: 有。Native Image 在编译时把 Bean 定义提前处理AOTAhead-Of-Time跳过了运行时的 BeanDefinition 扫描和大部分反射。启动时间从秒级降到毫秒级通常 50~100ms但牺牲了运行时动态性不能动态加 Bean。Spring Boot 3 的 AOT 引擎在spring-boot-maven-plugin的process-aot阶段执行。你在项目中遇到过 Spring Boot 启动慢的问题吗怎么排查的欢迎评论区交流

相关新闻

河北机械手自动喷涂机喷涂技术

河北机械手自动喷涂机喷涂技术

在河北的制造产业版图中,喷涂技术正经历一场深刻的变革。过去,工人在弥漫着化学气味的车间里,手持喷枪,依靠经验控制着每一道漆膜的厚度与均匀度;如今,机械手自动喷涂机正在取代这一场景,它带来…

2026/10/26 16:47:52 阅读更多 →
华为OD机试真题解析:几何平均值最大子数组的算法与实现

华为OD机试真题解析:几何平均值最大子数组的算法与实现

1. 项目概述:从一道真题看算法思维与工程实践最近在准备华为OD机试的朋友,估计没少被“几何平均值最大子数组”这道题刷屏。它频繁出现在各类真题汇总和备考攻略里,俨然成了检验候选人算法功底和思维缜密度的一块“试金石”。乍一看标题&…

2026/10/24 7:33:06 阅读更多 →
Spring Boot 整合 Swagger2 和 Knife4j实现接口文档与可视化调试

Spring Boot 整合 Swagger2 和 Knife4j实现接口文档与可视化调试

文章目录一、Swagger2(Springfox)核心依赖与配置1.1 导入依赖1.2 配置类1.3 Spring Boot 2.6 兼容处理1.4 跨模块引用配置二、常用注解2.1 注解实例三、Knife4j 整合3.1 导入依赖3.2 配置文件3.3 访问与鉴权总结后端开发中接口文档的维护一直是痛点——代…

2026/10/25 14:20:06 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/25 22:36:10 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/26 5:00:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/26 3:16:46 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 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/26 12:00:19 阅读更多 →
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/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练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/26 8:34:37 阅读更多 →