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

发布于:2025-07-20 ⋅ 阅读:(16) ⋅ 点赞:(0)

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

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


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

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

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

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

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


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

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

public class EarlyEnvInjector implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    @Override
    public 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> {
    @Override
    public 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);

    @Override
    public 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> {
    @Override
    public 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

网站公告

今日签到

点亮在社区的每一天
去签到