事件发布模型剖析


异步是编程中的一大核心思想,在提升项目代码质量以及系统服务性能中扮演重要角色。

而实现异步的途径可谓五花八门,不论是最基础的线程异步,亦或借助消息队列等第三方服务,都可实现异步解耦的效果。

今天,则让我们聚焦于 Spring 框架为我们所提供的事件发布功能,实现优雅的代码异步操作。

1. 模型介绍

在开始具体的介绍前先让我们看一下在传统的调用链路中是如何实现方法执行。

假设存在 MyService 服务类且需要在 MyTest 类中调用服务方法,最简单的方式即通过 new 声明对象后进行调用,如下代码所示:

public class MyTest {
    public void test() {
        MyService service = new MyService();
        service.sayHello();
    }
}

public class MyService {
    public void sayHello() {
        System.out.println("Hello World!")
    }
}

从代码可以看出,测试类 MyTest 显然强依赖于服务类 MyService。让我们继续放大这一特点,如果将两个类分别比作两个独立的系统,同理两个系统则高度耦合。

应对此类场景,通常才引入第三方存在如队列等容器,生产者将数据入队后由消费者操作出队读取数据,模块间不再由强关联,这便是服务解耦的理念。

2. 事件声明

秉持着相同的理念,Spring 提供了 ApplicationEvent 支持事件的发布与监听,从而实现代码流程异步。

Spring 事件发布模型中设计三个核心概念:发布者、事件、监听器。如果你熟悉消息队列等中间件服务,针对这些名词想必早已耳熟目染。

其中发布者与监听器故名思意即数据的生产者与消费者,而事件则可理解为这一过程所传递的数据内容。

那么就让我们以最直观的代码示例进行演示,以数据广播推送场景为例,声明 BroadcastContext 上下文用于携带事件数据。同时新建事件类继承于 ApplicationEvent 事件服务类,代码并不复杂具体如下:

// 数据载体
public class BroadcastContext {

    private Long id;

    private String content;
}

// 事件定义
public class BroadcastEvent extends ApplicationEvent {

    public BroadcastEvent(BroadcastContext context) {
        super(context);
    }
}

而完成事件的定义之后便可通过生产者发布事件,在 Spring 工程中使用也及其简单,注入 ApplicationEventPublisher 实例后通过 publishEvent() 服务方法即可发布事件。

@Service
@RequiredArgsConstructor
public class BroadcastResources {

    private final ApplicationEventPublisher publisher;


    public void publish() {
        // 构建上下文
        BroadcastContext context = new BroadcastContext();
        context.setId(1L);
        context.setContent("123456");

        // 事件发布
        publisher.publishEvent(new BroadcastEvent(context));
    }
}

3. 事件监听

既然有事件发布则必然也存在消费监听器,与 ApplicationEvent 相对应的通过 ApplicationListener 实现事件的监听。

根据上述定义的事件发布者,其相对应的事件监听器定义如下,在 onApplicationEvent() 服务方法中则可定义具体的事件消费处理逻辑。

@Slf4j
@Component
@RequiredArgsConstructor
public class BroadcastListener implements ApplicationListener<BroadcastEvent> {

    @Override
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onApplicationEvent(BroadcastEvent event) {
        // 获取上下文
        BroadcastContext context = (BroadcastContext) event.getSource();
        
        // TODO 数据处理

    }
}

这里需注意一点,在 ApplicationEvent 事件发布模型下整个过程流程上仍处理同步事务,即事件的发布与监听消费仍处于同一线程中,所实现的效果仅仅是代码异步接口。

如果想要实现流程逻辑上的异步,则可在上述的 onApplicationEvent() 服务方法上搭配 @Async 注解实现异步。

正是因为流程仍是同步的关系,在数据库事务场景下,如若监听器逻辑事务无关且流程耗时,可通过 @TransactionalEventListener 注解配置监听器的触发时机。当监听器服务方法开启注解后,默认在会数据库事务提交后触发,避免长事务所带来的性能损耗。


文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录