很多人第一次看Spring Boot启动,会觉得只写了一行SpringApplication.run就把应用带起来了,但日志里又出现了一堆事件、环境加载与自动配置输出,越看越像黑盒。把启动拆成明确的阶段,再把每个阶段对应到Spring Framework的容器刷新流程,你就能解释清楚为什么它能自动装配、为什么某些配置会生效或失效,也更容易定位启动慢与卡住的根因。
一、Spring Boot的启动流程是什么
Spring Boot的启动流程可以用一条主线串起来:准备启动器与扩展点,准备环境与属性源,创建ApplicationContext,上下文refresh刷新完成Bean装配,然后进入可服务状态并发布就绪信号。你只要抓住这些阶段边界,很多日志就能对上号。
1、入口从main方法进入到SpringApplication.run
典型启动类在main里调用SpringApplication.run,run是一次启动编排的总入口,负责把环境准备、上下文创建、监听器回调、容器刷新这些动作串成一条稳定的时间线。
2、构造SpringApplication时先确定应用类型与基础配置
SpringApplication在初始化阶段会识别应用类型,例如普通应用、Servlet Web应用、Reactive Web应用,并据此决定后续要创建哪种ApplicationContext,这一步决定了你最终看到的是哪类WebServer与哪套启动事件顺序。
3、启动早期加载监听器与初始化器,建立可插拔的启动钩子
run开始后会先装载一批启动期扩展组件,例如ApplicationContextInitializer与ApplicationListener,它们会在Environment准备前后、Context创建前后被回调,方便你在不侵入业务代码的情况下做早期定制。
4、准备Environment并合并外部化配置
启动会构建Environment,把命令行参数、配置文件、系统属性、环境变量等属性源合并进来,并按优先级形成统一的属性读取口径,后续的条件装配与自动配置判断都基于这份Environment做决策。
5、创建ApplicationContext并执行refresh刷新容器
Spring Boot会创建对应的ApplicationContext,并执行refresh,refresh内部会完成BeanDefinition处理、BeanFactory后处理器执行、Bean后处理器注册、单例Bean初始化、事件广播等核心动作,Web应用还会在合适阶段创建并启动内嵌WebServer。
6、启动完成后进入可用状态并触发Runner
容器refresh完成后,应用会进入Started阶段,随后执行CommandLineRunner与ApplicationRunner这类一次性启动任务,等这些任务结束后才会进入Ready阶段,你在健康检查与就绪探针里看到的状态变化通常与这里对应。
二、Spring Boot的启动原理解析
Spring Boot的启动原理可以拆成三层:第一层是SpringApplication负责生命周期编排,第二层是Spring Framework的refresh负责容器初始化模板流程,第三层是自动配置把依赖与环境映射成一组可条件生效的配置类。理解这三层之间的分工,你就不会把所有现象都归结为自动配置。
1、SpringApplication负责把启动拆成可控阶段并统一对外暴露扩展点
SpringApplication把启动拆成若干稳定阶段,并在每个阶段发布事件或执行回调,这让你能在正确时点介入,例如在Environment准备后补属性源,在Bean装配前调整上下文初始化逻辑,在应用就绪后启动异步任务。
2、自动配置的候选收集依赖类路径发现机制
EnableAutoConfiguration对应的候选配置类并不是写死在你的应用里,而是从类路径上收集出来,再交给后续的条件判断筛选,这也是为什么引入一个starter依赖后,很多默认Bean会自动出现。
3、条件装配决定自动配置是否生效
自动配置本质还是普通的Configuration配置类,只是大量使用ConditionalOnClass、ConditionalOnMissingBean、ConditionalOnProperty等条件注解,启动时会根据当前依赖、环境属性、已存在Bean进行评估,评估通过才会注册对应Bean。
4、refresh是容器启动的核心模板方法
refresh负责把BeanDefinition的处理链路跑完,再把Bean生命周期的关键节点串起来,包括后处理器、事件系统、单例初始化等,Spring Boot并没有改写这套核心机制,而是把上下文准备好并把refresh作为启动的关键关口。
5、Web应用的关键是内嵌容器与上下文的组合
在Servlet Web模式下,ServletWebServerApplicationContext会在容器中寻找ServletWebServerFactory并创建WebServer,再把DispatcherServlet与相关组件装配进去,最终形成可对外提供HTTP服务的运行形态,内嵌服务器之所以能随应用启动,本质原因就在这条链路上。
6、启动日志与事件能反推卡点位置
当你遇到启动慢或卡住,优先把现象映射到阶段上,例如卡在Environment阶段通常与配置源或网络配置中心有关,卡在refresh阶段通常与Bean初始化、条件评估或扫描范围有关,卡在WebServer阶段通常与端口占用、容器初始化或过滤器链有关。
三、Spring Boot启动扩展点与排查落点
理解原理之后,真正有价值的是把扩展与排查落到稳定点位上,避免把逻辑写进不可控的时机,导致顺序混乱或条件判断被破坏。你可以把常见需求分成环境定制、容器定制、启动任务、启动观测四类来放置。
1、需要改环境与属性源就放在Environment准备阶段
如果你要注入额外配置源、修正某些默认属性、合并多套配置口径,更适合在Environment已准备但容器未refresh的阶段完成,这样后续条件装配能读到一致的配置视图。
2、需要改BeanDefinition就使用BeanFactory后处理器
当你要批量注册BeanDefinition、调整扫描到的定义、替换某些默认定义,选择BeanFactoryPostProcessor这类机制更稳,它在单例实例化前执行,改动影响范围清晰,也更容易与自动配置共存。
3、需要增强Bean行为就使用Bean后处理器
如果你的目标是对某类Bean做代理增强、注入额外逻辑、修正初始化后的状态,使用BeanPostProcessor更合适,它作用在实例化前后,不会把启动顺序搞乱,也能把增强逻辑从业务代码里抽离出来。
4、一次性启动任务优先用Runner来承载
需要在应用可用前完成的任务,例如预热缓存、校验外部依赖、初始化本地数据,可以放在CommandLineRunner或ApplicationRunner,时机明确,失败时也能直接中断启动,便于在部署侧做回滚与告警。
5、观测启动阶段耗时要选可量化的启动跟踪点
如果你的目标是定位到底哪一步慢,不要只看最后的启动总耗时,可以引入启动跟踪与阶段划分思路,把关键阶段的耗时拆开,再针对性优化扫描范围、条件评估、初始化逻辑与外部依赖调用路径。
6、排查自动配置不生效要沿着条件判断去定位
遇到某个自动配置没生效,不要先猜版本问题,先确认条件注解对应的依赖是否在类路径上,属性开关是否满足,是否被已有Bean抢占,再检查配置加载顺序与Environment取值是否符合预期,这条排查链路通常能把问题收敛到一两个条件点。
总结
Spring Boot启动流程的主干是SpringApplication.run负责编排,ApplicationContext.refresh负责初始化容器,自动配置负责把依赖与环境转换成可条件生效的配置。把日志现象映射到启动阶段,再把定制需求落到合适的扩展点,你不仅能讲清Spring Boot的启动原理,也能在启动慢、配置不生效、容器行为异常时快速定位到具体阶段与具体机制。