1.详细介绍

Spring Event是Spring框架内建的一种发布/订阅(Publish-Subscribe)模式的实现,它允许应用内部不同组件之间通过事件进行通信。当某个特定事件发生时,系统中对这类事件感兴趣的监听器可以接收到通知并执行相应操作。

2.使用场景

  1. 内部模块间的通信:在一个Spring应用程序内部,不同服务或组件之间可以通过发布和监听事件来进行松耦合交互,比如在用户注册成功后触发邮件通知、权限更新等操作。
  2. 生命周期管理:Spring容器可以在Bean的生命周期中发布事件,如初始化完成后、销毁前等阶段,其他组件可以监听这些事件以执行相应的逻辑。
  3. 异步处理:虽然Spring Event默认是同步的,但也可以配置为异步传播,用于触发异步任务,比如用户行为跟踪、数据同步、日志记录、资源清理等。
  4. 业务流程编排:在复杂的业务流程中,事件驱动的方式有助于实现各个步骤之间的解耦,每个步骤作为独立的服务只关注处理特定的业务事件。

3.注意事项

  • 同步与异步:默认情况下,事件会被同步发送给所有监听器,这意味着如果监听器耗时较长,则会阻塞后续的监听器和发布线程。若需要异步处理,可以配置ApplicationEventMulticaster为异步模式。
  • 事务边界:在有事务控制的地方发布或消费事件时,需要注意事务的传播行为和一致性问题。例如,一个事务中的事件可能被另一个事务内的监听器处理,这可能导致预期之外的行为。
  • 资源释放:确保在监听器处理完事件后释放任何占用的资源,避免内存泄漏等问题。

服务关闭问题
Spring 广播消息时,Spring会在 ApplicationContext 中查找所有的监听者,即需要 getBean 获取 bean 实例。然而 Spring 有个限制————ApplicationContext 关闭期间,不得GetBean 否则会报错。
堆栈中的信息 解释了原因。Do not request a bean from a BeanFactory in a destroy method implementation

在应用上下文关闭时,不得从上下文中Get Bean。恰好,这个问题出现在服务关闭期间…

由于系统流量较高,日订单几百万,即便在低峰期单机的并发度也是比较高的,所以服务在关闭期间有少量流量进来或未处理完。这个场景下,使用 Spring Event 发布事件,Spring 无法正常广播事件,一定会出现异常,导致处理失败!

大家一定要切记!使用 SpringEvent 之前,一定要先治理服务,确保服务关闭时,先切断入口流量(Http、MQ、RPC),然后再关闭服务,关闭 Spring 上下文!

详细的分析请参考:https://juejin.cn/post/7281159113882468371

4.案例分析

假设有一个电商应用,在用户下单成功后,希望执行以下操作:

  • 发送订单确认邮件
  • 更新用户的积分信息
  • 向库存系统发送减库存请求

使用Spring Event的实现方式:

  1. 定义一个自定义事件类 OrderPlacedEvent,包含订单相关数据。
public class OrderPlacedEvent extends ApplicationEvent {
    private final Order order;

    public OrderPlacedEvent(Order source) {
        super(source);
        this.order = source;
    }

    public Order getOrder() {
        return order;
    }
}
  1. 创建事件监听器,继承ApplicationListener接口,并实现事件处理方法。
@Component
public class OrderEventListener implements ApplicationListener<OrderPlacedEvent> {

    @Autowired
    private MailService mailService;

    @Override
    public void onApplicationEvent(OrderPlacedEvent event) {
        Order order = event.getOrder();
        // 发送订单确认邮件
        mailService.sendOrderConfirmationEmail(order);
        // 这里还可以进行积分更新或其他操作
    }
}
  1. 在处理下单流程的服务中,下单成功后发布事件。
@Service
public class OrderProcessingService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void processOrder(Order order) {
        // ... 处理订单的核心逻辑 ...
        // 订单成功创建后,发布事件
        eventPublisher.publishEvent(new OrderPlacedEvent(order));
    }
}

5.代码实现

上述例子已经展示了如何创建事件、监听器以及如何在服务中发布事件的基本结构。对于异步处理,可以在配置文件中设置SimpleApplicationEventMulticaster为异步模式,或者使用AsyncConfigurer来自定义异步事件处理器。

6.与消息队列的区别

尽管Spring Event提供了事件驱动的方式,但它并不具备消息队列(MQ)的功能特性,例如持久化、分布式、消息堆积、重试机制等。在高并发、分布式环境和需要保证消息可靠传递的场景下,通常会采用RabbitMQ、Kafka等消息中间件替代Spring Event进行消息传递。

与MQ的区别:
1.范围与架构层次:Spring Event主要应用于单个应用内部,而MQ(如RabbitMQ、RocketMQ、Kafka等)适用于分布式系统间的消息传递,跨越多个独立运行的应用实例或微服务。

2.持久化与可靠性:Spring Event默认是非持久化的,消息一旦未被监听器消费,则可能丢失。相比之下,MQ通常支持消息持久化存储和可靠传输,即使消费者暂时不可用,消息也能在之后恢复时重新消费。

3.扩展性与性能:MQ通常提供集群部署和高可用保障,能够水平扩展以处理大量并发消息。而Spring Event的扩展性取决于应用本身的部署架构,难以应对大规模高并发场景。

7.SpringEvent结合线程池异步实现

在Spring框架中,为了实现异步处理事件,可以结合线程池使用TaskExecutor来配置ApplicationEventMulticaster以发布事件到监听器。以下是如何结合线程池实现Spring Event异步发布的步骤和示例:

  1. 首先,在Spring配置类中定义一个线程池任务执行器(如ThreadPoolTaskExecutor)。
@Configuration
public class AsyncConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 根据实际情况设置线程池参数
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(20);
        executor.setThreadNamePrefix("event-");
        executor.initialize();
        return executor;
    }
}
  1. 接下来,配置SimpleApplicationEventMulticaster以使用上述线程池执行器进行异步事件分发。
@Configuration
public class EventConfig {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        multicaster.setTaskExecutor(taskExecutor);
        return multicaster;
    }
}
  1. 现在,当你通过ApplicationEventPublisher发布事件时,它们将由配置好的线程池异步处理。
@Service
public class SomeService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void someMethodThatTriggersEvent() {
        // 业务逻辑...
        eventPublisher.publishEvent(new CustomEvent(this, "Some data"));
    }
}

@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {

    @Override
    public void onApplicationEvent(CustomEvent event) {
        // 异步处理事件的逻辑
        System.out.println("Received event: " + event.getData());
    }
}

这样,当SomeService中的方法触发事件时,CustomEventListener将会在一个独立的线程池线程上异步接收并处理该事件。这有助于提高应用性能,特别是对于耗时较长的事件处理任务。

在Spring框架中,CustomEvent通常是指自定义事件类,开发者可以根据业务需求创建一个继承自ApplicationEvent的类来表示特定的业务事件。例如:

import org.springframework.context.ApplicationEvent;

public class CustomEvent extends ApplicationEvent {
    private String eventData;

    public CustomEvent(Object source, String eventData) {
        super(source);
        this.eventData = eventData;
    }

    public String getEventData() {
        return eventData;
    }
}

在这个例子中,CustomEvent是一个自定义事件类型,它包含了一个字符串类型的eventData属性,用于携带具体的业务数据。构造函数接受两个参数,第一个是事件源(source),通常会传入触发该事件的对象实例;第二个参数是自定义的数据信息。

然后,在应用中的其他地方可以注册一个监听这个自定义事件的监听器,实现ApplicationListener<CustomEvent>接口,并在onApplicationEvent()方法中处理该事件。

import org.springframework.context.ApplicationListener;

@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {

    @Override
    public void onApplicationEvent(CustomEvent event) {
        System.out.println("Received custom event with data: " + event.getEventData());
        // 在这里执行对CustomEvent的处理逻辑
    }
}

通过这种方式,当应用程序发布一个CustomEvent时,所有注册了该事件类型的监听器都会异步或同步地接收到通知并执行相应的处理逻辑。如果结合线程池进行异步处理,那么这些事件将在后台线程中被监听器消费,从而不会阻塞主线程。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐