如何在 SpringBoot 启动时执行特定代码?有哪些方式?
- 标签:Spring Boot、后端、Spring
🏛 业务架构师
Spring Boot 启动时执行特定代码的方式按执行时机排列:
- @PostConstruct:Bean 依赖注入完成后立即执行——只适合轻量校验(必填配置检查),不要做重 IO(会拖慢启动);
- ApplicationRunner / CommandLineRunner:容器就绪后、
ApplicationReadyEvent前执行——业务初始化的标准位置(缓存预热、字典加载、注册元数据)。多个 Runner 用@Order控制顺序;重活建议异步化 + readiness 就绪后放流量,避免拉长启动窗口; ApplicationListener<ApplicationReadyEvent>:全就绪后触发,语义上最适合"对外宣告可用前"的最后动作;- InitializingBean / init-method:效果同 @PostConstruct,但耦合框架接口,不如注解优雅;
- @EventListener(ContextRefreshedEvent):容器刷新完成时——注意父子容器或上下文刷新可能触发多次,要幂等;
- ApplicationStartedEvent / SmartInitializingSingleton:所有单例就绪后的扩展点,框架或平台组件用得多。
架构师视角的选择原则:
- 初始化逻辑分两类:"不成功就不能服务"的(关键字典)同步做 + 失败快速终止启动;"锦上添花"的(预热、统计)异步后台做;
- 长耗时初始化与 readiness 探针联动:K8s 场景 readiness 通过前完成关键路径,否则扩容的新 Pod 带着冷缓存接流量,发布事故的经典形态;
- 启动钩子里的异常策略要明确:fail-fast 还是降级,不允许"初始化失败但服务照常接流量"的静默半残状态。
🏗 系统架构师
按执行时点选(这是关键,不是罗列 API):
| 方式 | 时点 | 适用 |
|---|---|---|
@PostConstruct | Bean 初始化(早) | 本 Bean 依赖就绪即可 |
| InitializingBean/init-method | 同上(接口耦合,少用) | 遗留代码 |
| ApplicationRunner/CommandLineRunner | refresh 后、Ready 前 | 大部分初始化的默认选择,支持 @Order |
| ApplicationReadyEvent | 就绪事件(最后) | 重预热:缓存、连接池、JIT |
| SmartLifecycle | 生命周期相位 | 启停对称的资源(MQ 消费) |
我的「启动预算」管理:
- 初始化分必需(Ready 前完成,阻塞 readiness 探针)与可延迟(Ready 后后台预热)两档,重活后移,扩容速度优先;
- 预热动作:连接池预建、本地缓存加载、HTTP 客户端连接预热——目标是第一个生产请求不承担冷启动成本;
- 失败策略:必需初始化失败就快速失败退出(K8s 重建),可延迟失败降级加告警;
- 多 Runner 依赖顺序用
@Order显式声明。
💻 全栈开发专家
按执行时机排五种:
java
// 1. @PostConstruct:Bean 初始化期,容器没 refresh 完,适合本 Bean 资源校验
@Component
public class DictCache {
@PostConstruct
public void check() { Assert.notEmpty(cache, "字典未加载,禁止启动"); }
}
// 2. InitializingBean#afterPropertiesSet / @Bean(initMethod):同阶段,历史风味
// 3. CommandLineRunner / ApplicationRunner:容器 refresh 完成后执行,
// 参数分别是命令行原始 args 和解析后的 ApplicationArguments
@Component
public class WarmupRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
cacheService.preload(); // 常驻缓存预热
}
}
// 4. @EventListener(ApplicationReadyEvent.class):就绪事件,语义最明确
// 5. SmartLifecycle / @DependsOn:精细控制 Bean 启停顺序我的选型习惯:配置/资源合法性校验放 @PostConstruct(带病启动不如早死,抛异常直接中断启动);重活(缓存预热、证书加载)放 ApplicationRunner 或 ReadyEvent,且可异步——K8s 里启动慢会被 liveness 杀掉,预热要挪到 readiness 通过之后。多个 Runner 用 @Order 定序。
细节:Runner 抛异常会中断启动,可失败的旁路初始化要 try-catch + 告警,别把核心服务拖死。