1 是什么?
是 Spring 框架内部,基于观察者模式实现的一套发布-订阅机制。类似于消息队列,不过只能在系统内部使用。
2 应用场景与优缺点
2.1 一些适合的场景
- 业务解耦
- 异步任务
- 日志记录与监控:可用于跟踪系统中的重要活动,并将这些信息用于日志记录或实时监控。
- 插件式扩展: 使用事件可以让应用变得更具扩展性,新添加的功能只需要监听相关的事件并提供相应的处理逻辑即可。
- 这里中一个现成的示例:SpringApplication 生命周期,便是使用这种机制通知的。如果想要在对应的生命周期进行操作,只需要监听对应的事件就可以。
2.2 优缺点
以下源自 AI。
优点:
- 松耦合: 发布者和订阅者之间不需要直接引用,它们通过事件接口进行通信。这样可以使系统中的组件更加独立,易于维护和扩展。
- 灵活性: 可以轻松添加新的事件类型或监听器,而不会影响到其他代码。这使得应用程序更容易适应变化的需求。
- 异步处理: 事件可以由不同的线程来处理,从而允许非阻塞操作,提高系统的响应能力。
- 可扩展性: 新的功能可以通过添加新的事件和监听器来实现,而不是修改现有代码。
- 解耦业务流程: 在复杂的业务流程中,事件可以帮助将各个步骤之间的依赖关系最小化,使每个步骤更专注于自己的任务。
- 可测试性: 单独的事件监听器类容易编写单元测试,因为它们通常只需要关注自己负责的逻辑。
缺点:
- 消息丢失: 如果没有特殊的设计(例如持久化队列),当发布事件后,如果没有正在运行的监听器或者监听器未能正确处理事件时,可能会导致数据丢失。
- 性能开销: 使用事件意味着需要创建额外的对象实例(事件对象)并传递给监听器。在高负载环境下,这些开销可能变得显著。
- 顺序问题: 如果多个监听器都对同一事件感兴趣,它们会按照注册顺序执行。如果需要控制特定的执行顺序,则需要特别设计。
- 调试复杂性: 由于事件驱动架构的分布式特性,跟踪错误和异常可能变得更加困难,尤其是在涉及多个监听器的情况下。
- 过度使用可能导致混乱: 如果不恰当地在整个应用中使用事件,可能会导致整个架构变得难以理解和维护。
3 SpringApplication生命周期的事件体系
图片出处:zhuanlan.zhihu.com/p/588889002
- ApplicationStartingEvent:在 Spring Boot 应用程序启动之前发布。
- ApplicationEnvironmentPreparedEvent:在 Spring Boot 应用程序的环境已经准备好,但正在创建 Application Context 上下文之前发布。
- ApplicationContextInitializedEvent:当 Spring Boot 应用程序 Application Context 上下文准备就绪并且调用 ApplicationContextInitializers,但尚未加载 bean 定义时发布。
- ApplicationPreparedEvent:在 Spring Boot 应用程序的 Application Context 上下文已经创建,但尚未刷新之前发布。
- ApplicationStartedEvent:在 Spring Boot 应用程序的 Application Context 上下文已经刷新,但尚未启动之前发布。
- ApplicationReadyEvent:在 Spring Boot 应用程序已经启动并准备接受请求之后发布。
- ApplicationFailedEvent:在 Spring Boot 应用程序启动失败时发布。
4 使用
4.1 创建事件对象
package pers.yefeng.springevent;
import lombok.Getter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;
@Getter
@ToString
public class OneEvent extends ApplicationEvent {
private final String msg;
public OneEvent(
Object source,
String msg
) {
super(source);
this.msg = msg;
}
}
4.2 发布事件
@Autowired
private ApplicationContext context;
@Test
void pushEvent() {
context.publishEvent(new OneEvent(this, "push a event !"));
}
4.3 监听事件
4.3.1 实现接口
package pers.yefeng.springevent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class OneEventListener implements ApplicationListener<OneEvent> {
@Override
public void onApplicationEvent(OneEvent event) {
log.info("收到事件: {}",event.toString());
}
}
4.3.2 基于注解
package pers.yefeng.springevent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MyBeans {
@EventListener
public void onOneEventEvent(OneEvent event) {
log.info("收到事件: {}", event.toString());
}
@EventListener
public void onTwoEventEvent(TwoEvent event) {
log.info("收到事件: {}", event.toString());
}
}
4.4 事件排序
如果一个事件被多个监听者监听时,可以通过 @Order()
,数字越小,越先执行。
@EventListener
@Order(1)
public void onOneEventEvent(OneEvent event) {
log.info("收到事件: {}", event.toString());
}
4.5 指定异步线程
默认事件是同步的,这意味着发布者线程将阻塞,直到所有侦听器都完成对事件的处理为止。我们可以使用 @Async
注解来标注一个事件监听器方法,表示这个方法是一个异步方法,应该在独立的线程中执行。
同时我们还需要在配置类(@Configuration
类之一或 @SpringBootApplication
类)中启用异步处理,才能使用 @Async
注解。
@EventListener
@Async
public void onOneEventEvent(OneEvent event) {
log.info("收到事件: {}", event.toString());
}