SpringApplication 启动流程
SpringApplication#run() 是 Boot 的总调度器。它不直接创建所有业务 Bean,而是把环境、监听器、上下文和刷新阶段连接成一条有序流水线。
先给答案:Boot 启动是在运行时组装一个“符合当前环境的应用容器”
Section titled “先给答案:Boot 启动是在运行时组装一个“符合当前环境的应用容器””启动过程先收集环境和监听器,再推断 Web 类型、创建 ApplicationContext、加载配置类和自动配置,最后 refresh 并发布就绪事件。它不是简单调用 Spring,而是在 Spring 之上增加了环境推断、默认策略和失败诊断。
启动失败时不要只看最后一行异常:环境没准备好会导致配置读取错误,自动配置条件不满足会导致 Bean 缺失,refresh 失败则可能是依赖或生命周期问题。沿着事件和上下文阶段回溯,才能区分“没找到配置”和“配置找到了但创建失败”。
SpringApplication.run ├─► getRunListeners ├─► prepareEnvironment ├─► createApplicationContext ├─► prepareContext ├─► refreshContext └─► afterRefresh / runningrun(String... args)在core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java:304进入主流程。getRunListeners(args)在:312前后加载启动监听器。createApplicationContext()在:318根据应用类型选择上下文。prepareContext(...)在:380注册环境、启动参数和主配置源。refreshContext(context)在:441委托 Spring Framework 执行刷新。getRunListeners的实现入口在:453。createApplicationContext()的默认实现位于:579。- 启动完成后通过 listeners 发送 started/running 事件,避免把监控逻辑硬编码到业务 Bean。
为什么不把所有逻辑塞进一个静态 main 方法
Section titled “为什么不把所有逻辑塞进一个静态 main 方法”替代方案:由应用自己的 main() 读取配置、创建容器、启动服务器。
为什么不行:每个应用都会重复处理环境、异常、监听器和 Web 类型判断,扩展点也无法统一。
证据:SpringApplication#run() 将 prepareContext、refreshContext 和 listeners 分开,形成稳定的生命周期边界。
| 场景 | 现象 | 原因 | 规避 |
|---|---|---|---|
| 配置类不在扫描范围 | 启动成功但 Bean 缺失 | 主配置源选择不对 | 显式指定 sources 或调整包结构 |
| 启动阶段阻塞 | 应用迟迟不 ready | 监听器或初始化器执行阻塞任务 | 将外部任务移到就绪后异步执行 |
| 刷新失败 | 进程退出且 Bean 未完整创建 | Framework refresh 抛异常 | 优先查看 condition evaluation 与根因异常 |
把长流程拆成“发现、准备、执行、完成通知”四段,能让扩展点拥有明确插入位置,也便于在失败时定位阶段。
面试锚点
SpringApplication.run()的主要阶段有哪些?SpringApplicationRunListener在什么时候触发?- Boot 为什么委托 Framework 的
refresh()?