Spring 事件机制

45 阅读4分钟

1 是什么?

是 Spring 框架内部,基于观察者模式实现的一套发布-订阅机制。类似于消息队列,不过只能在系统内部使用。

2 应用场景与优缺点

2.1 一些适合的场景

  • 业务解耦
  • 异步任务
  • 日志记录与监控:可用于跟踪系统中的重要活动,并将这些信息用于日志记录或实时监控。
  • 插件式扩展: 使用事件可以让应用变得更具扩展性,新添加的功能只需要监听相关的事件并提供相应的处理逻辑即可。
    • 这里中一个现成的示例:SpringApplication 生命周期,便是使用这种机制通知的。如果想要在对应的生命周期进行操作,只需要监听对应的事件就可以。

2.2 优缺点

以下源自 AI。

优点:

  1. 松耦合: 发布者和订阅者之间不需要直接引用,它们通过事件接口进行通信。这样可以使系统中的组件更加独立,易于维护和扩展。
  2. 灵活性: 可以轻松添加新的事件类型或监听器,而不会影响到其他代码。这使得应用程序更容易适应变化的需求。
  3. 异步处理: 事件可以由不同的线程来处理,从而允许非阻塞操作,提高系统的响应能力。
  4. 可扩展性: 新的功能可以通过添加新的事件和监听器来实现,而不是修改现有代码。
  5. 解耦业务流程: 在复杂的业务流程中,事件可以帮助将各个步骤之间的依赖关系最小化,使每个步骤更专注于自己的任务。
  6. 可测试性: 单独的事件监听器类容易编写单元测试,因为它们通常只需要关注自己负责的逻辑。

缺点:

  1. 消息丢失: 如果没有特殊的设计(例如持久化队列),当发布事件后,如果没有正在运行的监听器或者监听器未能正确处理事件时,可能会导致数据丢失。
  2. 性能开销: 使用事件意味着需要创建额外的对象实例(事件对象)并传递给监听器。在高负载环境下,这些开销可能变得显著。
  3. 顺序问题: 如果多个监听器都对同一事件感兴趣,它们会按照注册顺序执行。如果需要控制特定的执行顺序,则需要特别设计。
  4. 调试复杂性: 由于事件驱动架构的分布式特性,跟踪错误和异常可能变得更加困难,尤其是在涉及多个监听器的情况下。
  5. 过度使用可能导致混乱: 如果不恰当地在整个应用中使用事件,可能会导致整个架构变得难以理解和维护。

3 SpringApplication生命周期的事件体系

10 事件机制.png 图片出处: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());  
}

4.6 参考文章

juejin.cn/post/727711…

www.hangge.com/blog/cache/…