SpringEvent事件发布&订阅Demo

本文参考:

基础用法:Spring Event事件发布&消费Demo - HumorChen99 - 博客园 (cnblogs.com)

比较全面的用法:Spring Event 事件发布/监听机制 详解并使用-CSDN博客

@EventListener 源码简单探究:SpringBoot中@EventListener注解的使用-CSDN博客

广播器与监听器初始化详细源码:深入理解Spring事件机制(一):广播器与监听器的初始化 - Createsequence - 博客园 (cnblogs.com)

事件如何广播推送的:[深入理解Spring事件机制(二):事件的推送通俗易懂]-腾讯云开发者社区-腾讯云 (tencent.com)

在看源码的过程中,看到大佬们通过 Spring Event 事件发布与订阅机制,都实现了比较优雅的代码,但是自己对于这方面的实践还是较少,因此这篇文章准备从 Demo 引入,了解相关的用法,后面再出文章进一步分析其中的原理。

实现Spring事件机制主要有4个类:

  1. ApplicationEvent:事件,每个实现类表示一类事件,可携带数据。
  2. ApplicationListener:事件监听器,用于接收事件处理时间。
  3. ApplicationEventMulticaster:事件管理者,用于事件监听器的注册和事件的广播。
  4. ApplicationEventPublisher:事件发布者,委托ApplicationEventMulticaster完成事件发布。

实现事件

自定义事件,继承 ApplicationEvent

package com.jxz;

import lombok.Getter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;

/**
 * @Author jiangxuzhao
 * @Description 自定义事件,继承 ApplicationEvent
 * @Date 2024/5/31
 */
@Getter
@ToString
public class JxzEvent extends ApplicationEvent {

    private String jxzTopic;

    private String jxzContent;

    public JxzEvent(String topic, String content) {
        super(topic);
        this.jxzTopic = topic;
        this.jxzContent = content;
    }
}

定义事件监听器

方式一

自定义事件监听器,实现 ApplicationListener 接口,监听 JxzEvent 事件,同时别忘了将 JxzEventListener 通过 @Component 注入给 Spring IOC 容器

package com.jxz;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Author jiangxuzhao
 * @Description 自定义事件监听器,实现 ApplicationListener 接口,监听 com.jxz.JxzEvent 事件
 * @Date 2024/5/31
 */
@Slf4j
@Component
public class JxzEventListener implements ApplicationListener<JxzEvent> {
    @Override
    public void onApplicationEvent(JxzEvent event) {
        log.info("topic is = {}", event.getJxzTopic());
        log.info("content is = {}", event.getJxzContent());
    }
}

方式二

通过 @EventListener 注解,实现监听事件的效果

package com.jxz;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2024/6/1
 */
@Component
@Slf4j
public class JxzEventListener2 {
    @EventListener(value = {JxzEvent.class}) // @EventListener 注解
    public void listener(JxzEvent event) {
        log.info("topic is = {}", event.getJxzTopic());
        log.info("content is = {}", event.getJxzContent());
    }
}

定义事件发布者

方式一

需要引入 ApplicationEventPublisher 事件发布者,然后构造 JxzEvent 事件发布事件

package com.jxz;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author jiangxuzhao
 * @Description Controller 类方便测试
 * @Date 2024/5/31
 */
@RestController
public class JxzController {
    /**
     * 需要引入 ApplicationEventPublisher 事件发布者
     */
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @GetMapping(path = "test/")
    public String test(String topic, String content) {
        // 通过传入参数构造 JxzEvent 事件
        JxzEvent jxzEvent = new JxzEvent(topic, content);
        // 发布事件
        applicationEventPublisher.publishEvent(jxzEvent);
        return jxzEvent.toString();
    }
}

方式二

Spring 中顶级的容器管理类 ApplicationContext

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver

extends 继承于 ApplicationEventPublisher,因此也可以通过注入父类的接口引入这个发布者

@Resource
private ApplicationContext applicationContext;

@GetMapping(path = "/test2")
public String test2(String topic, String content) {
    // 通过传入参数构造 JxzEvent 事件
    JxzEvent jxzEvent = new JxzEvent(topic, content);
    // 发布事件
    applicationContext.publishEvent(jxzEvent);
    return jxzEvent.toString();
}

其实这两种方式都是通过接口的方式,自动装配了 AnnotationConfigServletWebServerApplicationContext 这个实现类。

项目测试

最后再给程序加一个启动类就可以测试了

package com.jxz;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2024/5/31
 */
@SpringBootApplication
public class SpringEventApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringEventApplication.class, args);
    }
}

启动这个类并加上 VM Option = -Dserver.port=8071,本地端口 8071 接收请求

http://localhost:8071/test?topic="jxztopic"&content="明天放假"

控制台输出如下,表明事件成功发出,监听者成功接收并打印内容

2024-05-31 23:59:23.930  INFO 45974 --- [nio-8071-exec-4] com.jxz.JxzEventListener                 : topic is = "jxztopic"
2024-05-31 23:59:23.932  INFO 45974 --- [nio-8071-exec-4] com.jxz.JxzEventListener                 : content is = "明天放假"

原理

原理这块还是值得细细看源码的,这里姑且做个引子,起个提纲挈领的作用,需要有些 Spring 源码的底子,小弟我也只能管中窥豹。

本质上就是使用到了观察者模式,之前也写过一篇文章讲过 设计模式-从回调函数入手理解观察者模式_回调函数与观察者模式-CSDN博客

监听器的逻辑:

SpringApplication#run -> AbstractApplicationContext#createApplicationContext() -> AnnotationConfigServletWebServerApplicationContext -> new AnnotatedBeanDefinitionReader -> AnnotationConfigUtils.registerAnnotationConfigProcessors -> new RootBeanDefinition(EventListenerMethodProcessor.class) -> AbstractApplicationContext#finishBeanFactoryInitialization -> beanFactory.preInstantiateSingletons() -> smartSingleton.afterSingletonsInstantiated() -> EventListenerMethodProcessor#afterSingletonsInstantiated -> EventListenerMethodProcessor#processBean -> context.addApplicationListener -> applicationEventMulticaster.addApplicationListener(listener) -> AbstractApplicationEventMulticaster#addApplicationListener -> this.defaultRetriever.applicationListeners.add(listener)

由于 EventListenerMethodProcessor 实现了 SmartInitializingSingleton 接口,因此在 beanFactory 结束初始化结束之后会被调用,最终调用到其afterSingletonsInstantiated 方法,在 processBean 方法中,会找出所有被@EventListener 注解的方法,将它们初始化成 ApplicationListener 对象,并最终放入 AbstractApplicationEventMulticaster#ListenerRetriever#applicationListeners 集合中,同时放入 AbstractApplicationContext#applicationListeners 集合中,这里就是完成了监听者的注册。

发布者逻辑:

ApplicationEventPublisher#publishEvent -> AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType) -> getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType) -> SimpleApplicationEventMulticaster#multicastEvent

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
  ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
  for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    Executor executor = getTaskExecutor();
    if (executor != null) {
      executor.execute(() -> invokeListener(listener, event));
    }
    else {
      invokeListener(listener, event);
    }
  }
}

其中 getApplicationListeners 方法就会从通过 retrieveApplicationListeners 从前面注册的 defaultRetriever.applicationListeners 里面拿到所有的监听者,同时配置了一个本地缓存 retrieverCache 来提速。最终调用 invokeListener -> doInvokeListener -> listener.onApplicationEvent(event) 就是成功调用了这些监听者的 onApplicationEvent 方法。

由此可见,观察者模式是通过同步的方式实现的事件通知,而非消息队列那样依赖消息中心实现的异步通知方式。

相关推荐

  1. SpringEvent事件发布&订阅Demo

    2024-06-08 10:36:02       7 阅读
  2. Unity3D EventMgr事件订阅发布详解

    2024-06-08 10:36:02       19 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-08 10:36:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-08 10:36:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-08 10:36:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-08 10:36:02       18 阅读

热门阅读

  1. Dart语言中mixins的使用

    2024-06-08 10:36:02       6 阅读
  2. TS 系列:使用元祖生成联合类型

    2024-06-08 10:36:02       8 阅读
  3. 【无标题】

    2024-06-08 10:36:02       7 阅读
  4. CISSP—实现安全治理和原则的策略

    2024-06-08 10:36:02       8 阅读
  5. 小程序中 使用 UDPSocke通讯的流程

    2024-06-08 10:36:02       8 阅读
  6. Oracle如何定位硬解析高的语句?

    2024-06-08 10:36:02       7 阅读
  7. Unity学习要点

    2024-06-08 10:36:02       10 阅读
  8. kali-vulhub(持续更新)

    2024-06-08 10:36:02       8 阅读
  9. 每日一题36:数据分组之科目种类数量

    2024-06-08 10:36:02       8 阅读