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

驾驭 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.dtcms.com/a/287509.html

相关文章:

  • Custom SRP - Custom Render Pipeline
  • SurfaceView、TextureView、SurfaceTexture 和 GLSurfaceView
  • 立创EDA中双层PCB叠层分析
  • 原码、反码和补码在计算机中的运算规则有何不同?
  • 医疗AI与融合数据库的整合:挑战、架构与未来展望(上)
  • 小谈相机的学习过程
  • 软考 系统架构设计师系列知识点之杂项集萃(112)
  • jvm-sandbox-repeater 录制和回放
  • 基于深度学习的微表情识别算法研究
  • 智慧园区工程监控与工单管理系统需求文档
  • Go语言里的map
  • RocketMQ源码级实现原理-NameServer路由机制
  • 解锁C++性能密码:TCMalloc深度剖析
  • 低代码平台ToolJet实战总结
  • Java学习--------消息队列的重复消费、消失与顺序性的深度解析​
  • n8n教程分享,从Github读取.md文档内容
  • Redisson RLocalCachedMap 核心参详解
  • Astro:前端性能革命!从原生 HTML 到 Astro + React 的升级指南
  • Flutter基础(前端教程①⑤-API请求转化为模型列成列表展示实战)
  • 前端面试专栏-工程化:28.团队协作与版本控制(Git)
  • 运用KANO模型分析扫地机器人用户需求
  • LangGraph教程9:LangGraph检查点和Send机制
  • Linux 基础命令:文件和目录操作、文件内容查看、进程管理
  • 【嵌入式电机控制#16】电流环(三):过采样提高采集精度看门狗监测总线电压
  • Nginx 实战 :使用logrotate实现日志轮转与保留策略!
  • 【数据结构】二叉树初阶详解(一):树与二叉树基础 + 堆结构全解析
  • 2025 Data Whale x PyTorch 安装学习笔记(Windows 版)
  • Kotlin方差
  • 403 Forbidden:无权限访问请求的资源如何处理
  • Apache Kafka 学习笔记