当前位置: 首页 > ai >正文

驾驭 Spring Boot 事件机制:8 个内置事件 + 自定义扩展实战

驾驭 Spring Boot 事件机制:8 个内置事件 + 自定义扩展实战

在 Spring Boot 应用的完整生命周期中,框架为我们预埋了 8 个关键事件(Application-level & Context-level)。 理解并善用这些事件,可以在“不侵入框架、不修改源码”的前提下,注入个性化初始化、监控、清理逻辑。 本文将带你从 0 到 1 掌握事件机制,并给出可直接落地的代码模板。


一、为什么需要事件机制?

场景传统做法事件机制优势
启动时加载字典缓存CommandLineRunner无侵入、可插拔、可排序
优雅停机@PreDestroy与 Spring 生命周期同步,确保资源释放顺序
多模块解耦直接调用发布-订阅,模块间零依赖

二、Spring Boot 8 大内置事件一览

事件触发阶段典型用途监听器注册方式
ApplicationStartingEventrun() 刚被调用,日志系统尚未初始化极早期检查、初始化日志桥接SpringApplication.addListeners(...)
ApplicationEnvironmentPreparedEventEnvironment 已就绪,但 BeanDefinition 尚未加载动态修改配置源、激活 Profile同上
ApplicationContextInitializedEventApplicationContext 已创建,但尚未 refresh注册 BeanFactoryPostProcessor同上
ApplicationPreparedEventBeanDefinition 已加载,Environment 可用读取配置、校验必备属性同上
ContextRefreshedEventrefresh() 完成,所有单例已实例化缓存预热、注册监控@Component
ServletWebServerInitializedEvent内嵌容器端口已打开获取运行时端口、注册服务发现@Component
ApplicationStartedEvent容器已启动,所有 CommandLineRunner 已执行发送启动成功指标@Component
ApplicationReadyEvent同上,额外保证所有应用初始化器已完成开启流量、发送通知@Component

Spring Boot 2.x 之后新增 ApplicationStartingEventApplicationStartedEvent 等,旧版只有 5 个核心事件。


三、实战:监听 4 个高频事件

1. 启动早期动态注入配置

public class EarlyEnvInjector implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {ConfigurableEnvironment env = event.getEnvironment();// 模拟从 Apollo/Nacos 拉取最新配置Map<String, Object> override = Map.of("spring.datasource.url", "jdbc:mysql://newHost/dev");env.getPropertySources().addFirst(new MapPropertySource("dynamic", override));}
}

注册方式(在 main 方法里):

SpringApplication app = new SpringApplication(DemoApp.class);
app.addListeners(new EarlyEnvInjector());
app.run(args);

2. 容器刷新后预热缓存

@Component
public class CacheWarmer implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext().getParent() == null) { // 防止重复执行DictCache.loadAll();}}
}

3. 优雅停机前释放资源

@Component
public class GracefulShutdown implements ApplicationListener<ContextClosedEvent> {private final ExecutorService pool = Executors.newFixedThreadPool(10);@Overridepublic void onApplicationEvent(ContextClosedEvent event) {pool.shutdown();try {if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {pool.shutdownNow();}} catch (InterruptedException e) {pool.shutdownNow();}}
}

4. 启动完毕发送监控告警

@Component
public class StartupReporter implements ApplicationListener<ApplicationReadyEvent> {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {InetAddress host = InetAddress.getLocalHost();String port = event.getApplicationContext().getEnvironment().getProperty("local.server.port");DingTalk.send("✅ 服务启动完成: " + host.getHostAddress() + ":" + port);}
}

四、扩展:自定义业务事件

1. 定义领域事件

public class OrderPaidEvent extends ApplicationEvent {private final Long orderId;private final BigDecimal amount;public OrderPaidEvent(Object source, Long orderId, BigDecimal amount) {super(source);this.orderId = orderId;this.amount = amount;}// getters ...
}

2. 发布事件

@Service
@RequiredArgsConstructor
public class OrderService {private final ApplicationEventPublisher publisher;public void pay(Long orderId) {// 业务逻辑...publisher.publishEvent(new OrderPaidEvent(this, orderId, BigDecimal.valueOf(99)));}
}

3. 多监听器异步消费

@Component
public class InvoiceGenerator {@EventListener@Async("invoiceTaskExecutor")   // 线程池隔离public void onOrderPaid(OrderPaidEvent event) {// 生成电子发票...}
}

五、最佳实践清单

  1. 顺序控制:使用 @Order 或实现 Ordered 接口。
  2. 线程安全:早期事件(如 ApplicationStartingEvent)发布时,Bean 尚未实例化,此时注册逻辑需避免依赖 IOC 容器。
  3. 条件化监听@ConditionalOnPropertyEnvironment 判断,避免在测试环境触发线上逻辑。
  4. 异步场景@Async + 自定义线程池,防止阻塞主流程。
  5. 可观测性:通过 Micrometer 记录事件处理耗时,及时发现慢监听器。

六、小结

目标推荐事件
动态修改配置ApplicationEnvironmentPreparedEvent
容器初始化后一次性任务ContextRefreshedEvent
优雅停机ContextClosedEvent
服务启动成功通知ApplicationReadyEvent
业务解耦自定义 ApplicationEvent
http://www.xdnf.cn/news/15750.html

相关文章:

  • windows wsl ubuntu 如何安装 maven
  • 前端知识回顾-登录界面
  • 实现el-select下拉框,下拉时加载数据
  • 【RK3576】【Android14】摄像头MIPI开发调试
  • [Python] -实用技巧10- 时间处理:datetime 和 time 模块入门
  • 【数据结构初阶】--双向链表(二)
  • 跨境卖家紧急自查,Endryko Karmadi四季版画版权维权
  • 【嵌入式电机控制#16】电流环(三):过采样提高采集精度看门狗监测总线电压
  • 【Linux系统】进程控制
  • 从0开始学习R语言--Day51--PH检验
  • OpenCV 官翻 1 -介绍、安装、功能概览、核心操作
  • 云计算与 DevOps(开发与运维)
  • sqli-labs靶场通关笔记:第32-33关 宽字节注入
  • Java 中的继承与多态
  • 基于GEE与哨兵2号的土地覆盖分类方法及实现
  • Vue Swiper组件
  • n8n教程分享,从Github读取.md文档内容
  • MySQL(145)如何升级MySQL版本?
  • 【爬虫】04 - 高级数据存储
  • Fortran实战:快速解析气象NC数据
  • OpenCV 官翻8 - 其他算法
  • 牛客-倒置字符串
  • SQL Server和PostgreSQL填充因子
  • debian的pulseaudio删掉也没事
  • SIMATIC WinCC Unified 使用 KPI 优化流程
  • Nacos配置管理
  • 【Unity3D实例-功能-移动】角色移动-通过WSAD(Rigidbody方式)
  • Kafka、RabbitMQ 与 RocketMQ 高可靠消息保障方案对比分析
  • TinyMCE 富文本编辑器在 vue2 中的使用 @tinymce/tinymce-vue
  • MySQL——约束类型