SpringBoot - 事件机制使用详解(ApplicationEvent、ApplicationListener)

SpringBoot - 事件机制使用详解(ApplicationEvent、ApplicationListener)

Spring 事件机制使用观察者模式来传递事件和消息。我们可以使用 ApplicationEvent 类来发布事件,然后使用 ApplicationListener 接口来监听事件。当事件发生时,所有注册的 ApplicationListener 都会得到通知。事件用于在松散耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此可以在不影响发布者的情况下修改订阅者,反之亦然。下面通过样例样式事件机制的使用。

1,基本用法

(1)首先我们创建一个自定义事件类 MyEvent,该类继承自 ApplicationEvent 类。

// 自定义事件类
public class MyEvent extends ApplicationEvent {
 
  private String message;
 
  public MyEvent(Object source, String message) {
    super(source);
    this.message = message;
  }
 
  public String getMessage() {
    return message;
  }
}

(2)接着定义一个事件监听器类 MyEventListener,该类实现 ApplicationListener 接口,并注册为 Spring 的组件。只要监听器对象在 Spring 应用程序上下文中注册,它就会接收事件。当 Spring 路由一个事件时,它使用监听器的签名来确定它是否与事件匹配。

// 事件监听器
@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
  // 事件发生时执行
  @Override
  public void onApplicationEvent(MyEvent event) {
    System.out.println("接收到事件: " + event.getMessage());
    // 模拟事件处理
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
}

(3)最后我们可以使用 ApplicationContext 的 publishEvent 方法来发布事件。

@RestController
public class HelloController {
  @Autowired
  private ApplicationContext context;
 
  @GetMapping("/hello")
  public void hello() {
    System.out.println("准备发送事件");
    context.publishEvent(new MyEvent(this, "welcome to hangge.com"));
    System.out.println("事件发送完毕");
  }
}

(4)启动项目测试一下,我们访问 /hello 接口时,控制台输出如下,说明事件机制运行成功。

  • 注意:spring 事件是同步的,这意味着发布者线程将阻塞,直到所有监听都完成对事件的处理为止。

在这里插入图片描述

2,使用 @EventListener 监听事件

(1)上面样例我们通过实现 ApplicationListener 接口来定义监听器。从 Spring 4.1 开始,可以使用 @EventListener 注解的方法,以自动注册与该方法签名匹配的 ApplicationListener(监听器类同样需要注册为 Spring 的组件)。下面代码的效果同上面是一样的:

// 事件监听器
@Component
public class MyEventListener{
  // 使用注解实现事件监听
  @EventListener
  public void onApplicationEvent(MyEvent event) {
    System.out.println("接收到事件: " + event.getMessage());
    // 模拟事件处理
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
}

(2)我们可以使用 @EventListener 注解的 value 属性来指定我们要监听的事件类型。比如下面代码来监听 MyEvent 类型的事件:
如果需要同时指定多个事件类型可以这么写:@EventListener({MyEvent.class,AnotherEvent.class})。

// 事件监听器
@Component
public class MyEventListener{
  // 使用注解实现事件监听
  @EventListener(MyEvent.class)
  public void onApplicationEvent(MyEvent event) {
    System.out.println("接收到事件: " + event.getMessage());
  }
}

(3)我们可以使用 @EventListener 注解的 condition 属性来指定事件监听器的执行条件。在下面的代码中,#event.message == ‘hello’ 是一个 SpEL 表达式,表示当事件的 message 属性值为 hello 时,事件监听器才会被执行。
(1)SpEL (Spring Expression Language) 是一种强大的表达式语言,用于在运行时执行各种表达式。我们可以使用 SpEL 表达式来访问对象的属性、调用对象的方法、执行运算等。
(2)SpEL 表达式语法如下:
属性访问:使用 . 操作符访问对象的属性,例如,object.property 表示访问对象 object 的属性 property。
方法调用:使用 () 操作符调用对象的方法,例如,object.method() 表示调用对象 object 的方法 method。
运算符:SpEL 支持常用的运算符,包括算术运算符、关系运算符、逻辑运算符等。
(3)SpEL 表达式还支持一些特殊的操作符和函数,如下所示:
? 操作符:三目运算符,形如 (condition ? then : else),表示当 condition 为真时返回 then,否则返回 else。
instanceof 操作符:用于判断对象是否为某个类型,形如 object instanceof T,表示对象 object 是否为类型 T。
t() 函数:将对象转换为给定的类型,形如 t(T),表示将对象转换为类型 T。
elvis 操作符:用于判断对象是否为空,形如 object ?: defaultValue,表示如果对象不为空则返回对象,否则返回默认值。

// 事件监听器
@Component
public class MyEventListener{
  // 使用注解实现事件监听
  @EventListener(condition = "#event.message == 'hello'")
  public void onApplicationEvent(MyEvent event) {
    System.out.println("接收到事件: " + event.getMessage());
  }
}

(4)对于使用 @EventListener 注解并定义为具有返回类型的方法,Spring 会将结果作为新事件发布。在下面的示例中,第一个方法返回的 AnotherEvent 将被发布,然后由第二个方法处理。

// 事件监听器
@Component
public class MyEventListener{
  @EventListener
  public AnotherEvent listener1(MyEvent event) {
    // 处理事件
    System.out.println("事件监听器 1 接收到事件: " + event.getMessage());
    return new AnotherEvent(this, "转发" + event.getMessage());
  }
 
  @EventListener
  public void listener2(AnotherEvent event) {
    // 处理事件
    System.out.println("事件监听器 2 接收到事件: " + event.getMessage());
  }
}

3,使用 @Order 指定优先级

(1)当 Spring 发布一个事件时,会调用所有能处理这个事件的事件监听器方法。如果你有多个事件监听器方法,那么 Spring 会依次调用这些方法。比如下面样例,当发布一个 MyEvent 事件时,Spring 会依次调用这两个方法。

// 事件监听器
@Component
public class MyEventListener{
  @EventListener
  public void listener1(MyEvent event) {
    // 处理事件
    System.out.println("事件监听器 1 接收到事件: " + event.getMessage());
  }
 
  @EventListener
  public void listener2(MyEvent event) {
    // 处理事件
    System.out.println("事件监听器 2 接收到事件: " + event.getMessage());
  }
}

在这里插入图片描述

(2)我们可以使用 @Order 注解来指定事件监听器方法的优先级。@Order 注解可以标注在类上或方法上,表示这个类或方法的优先级。数值越小,优先级越高。

// 事件监听器
@Component
public class MyEventListener{
  @Order(2)
  @EventListener
  public void listener1(MyEvent event) {
    // 处理事件
    System.out.println("事件监听器 1 接收到事件: " + event.getMessage());
  }
 
  @Order(1)
  @EventListener
  public void listener2(MyEvent event) {
    // 处理事件
    System.out.println("事件监听器 2 接收到事件: " + event.getMessage());
  }
}

在这里插入图片描述

4,使用 @Async 实现异步事件监听

(1)从第一个样例运行结果可以看出默认 spring 事件是同步的,这意味着发布者线程将阻塞,直到所有侦听器都完成对事件的处理为止。我们可以使用 @Async 注解来标注一个事件监听器方法,表示这个方法是一个异步方法,应该在独立的线程中执行。

// 事件监听器
@Component
public class MyEventListener{
  // 使用注解实现事件监听
  @Async
  @EventListener
  public void onApplicationEvent(MyEvent event) {
    System.out.println("接收到事件: " + event.getMessage());
    // 模拟事件处理
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
}

(2)同时我们还需要在配置类(@Configuration 类之一或 @SpringBootApplication 类)中启用异步处理,才能使用 @Async 注解。

@Configuration
@EnableAsync
public class MyConfig {
    // 配置类
}

(3)最后测试一下,可看到实现了异步事件监听:
在这里插入图片描述

附:Spring Boot 内置的 Application Event

(1)Spring Boot 中包含了一些与 SpringApplication 生命周期相关的内置 Application Event,包括:

  • 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 应用程序启动失败时发布。

(2)假设我们需要监听 ApplicationStartingEvent,则首先定义一个监听器类:

// 事件监听器
public class MyEventListener implements ApplicationListener<ApplicationStartingEvent> {
  // 事件发生时执行
  @Override
  public void onApplicationEvent(ApplicationStartingEvent event) {
    // 处理事件
    System.out.println("应用程序将要启动");
  }
}

(3)由于该事件实际上是在 Application Context 创建前触发的,这时的 Bean 是不能被加载的。所以我们不能在这个监听器类上中使用 @Component 注册监听器,只能通过 SpringApplication 注册监听器。我们对项目启动类做如下修改:
注意:对于ApplicationStartedEvent、ApplicationReadyEvent、ApplicationFailedEvent 这些在 Application Context上下文已经创建完毕之后,所以可以直接使用 @Component 注册,不需要下面这个步骤。

@SpringBootApplication
public class RmiserverApplication {
 
  public static void main(String[] args) {
    SpringApplication app = new SpringApplication(RmiserverApplication.class);
    app.addListeners(new MyEventListener()); //注册监听器
    app.run(args);
  }
}

或者也可以使用使用 SpringApplicationBuilder 注册监听器:

@SpringBootApplication
public class RmiserverApplication {
 
  public static void main(String[] args) {
    new SpringApplicationBuilder().sources(RmiserverApplication.class)
            .listeners(new MyEventListener()) //注册监听器
            .run(args);
  }
}

(4)启动项目,可以看到控制台输出如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/243717.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用ThreadLocal.withIniti避免初始化为null问题

问题描述 我们在使用threadLocal的时&#xff0c;使用ThreadLocal.withInitial去初始化而不是使用new ThradLocal去初始化&#xff0c;这是为什么呢&#xff1f; 问题例子 比如说&#xff0c;假设我们想要在每个线程中维护一个独立的计数器 import java.util.concurrent.at…

Node.js创建一个简单的WebSocket接口,实现通信交互

Node.js创建一个简单的WebSocket接口&#xff0c;实现通信交互 一、为什么使用WebSocket&#xff1f; WebSocket&#xff0c;最大特点就是&#xff0c;服务器可以主动向客户端推送信息&#xff0c;客户端也可以主动向服务器发送信息&#xff0c;是真正的双向平等对话&#xf…

C++从bing采集各行业的企业官网信息

作为一名合格的销售&#xff0c;除了自己的人脉&#xff0c;还应该有新鲜的客户加入并发掘。不管哪行哪业&#xff0c;知彼知己&#xff0c;方才能做到百战百胜。今天我就用我们的专业技能&#xff0c;让销售获取更多同行业的公开企业信息&#xff0c;让业绩顺风顺水。 通常在C…

【代码随想录算法训练营-第六天】【哈希表】242,349,202,1

242.有效的字母异位词 第一遍 思考 比较简单&#xff0c;用数组就能实现了 class Solution {public boolean isAnagram(String s, String t) {int[] checkListi new int[256];int[] checkListj new int[256];for (int i 0; i < s.length(); i) {char checkChar s.ch…

修改Docker0和容器的地址

修改Docker0和容器的地址 1. 需求 默认服务器安装完Docker-ce后会给docker0分配172.17.0.1/16地址. 公司新接入一个网段正好与172.17.0.1/16冲突,此时访问这台服务器的容器时就会发生网络不可达. 2. 解决方法 修改/etc/docker/daemon.json 加入一个自定义网段 vim /etc/d…

基于单片机的智能小车 (论文+源码)

1. 系统设计 此次可编程智能小车系统的设计系统&#xff0c;结合STM32单片机&#xff0c;蓝牙模块&#xff0c;循迹模块&#xff0c;电机驱动模块来共同完成本次设计&#xff0c;实现小车的循迹避障功能和手机遥控功能&#xff0c;其整体框架如图2.1所示。其中&#xff0c;采用…

剧本杀小程序成为创业者新选择,剧本杀小程序开发

剧本杀作为现下年轻人最喜欢的新兴行业&#xff0c;发展前景非常乐观&#xff0c;即使剧本杀目前处于创新发展阶段&#xff0c;但剧本杀行业依然在快速发展中。 根据业内数据&#xff0c;预计2025年剧本杀市场规模能达到四百多亿元。市场规模的扩大自然也吸引来了不少的创业者…

利用机器学习实现客户细分的实战

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来学习一下机器学习实战中的案例&#xff1a;创建客户细分&#xff0c;在此过程中也会补充很多重要的知识点&#xff0c;欢迎大家一起前来探讨学习~ 一、导入数据 在此项目中&#xff0c;我们使用 UCI 机器学习代码库…

1+X大数据平台运维职业技能等级证书中级

该部分是选择题部分&#xff0c;实操题在主页的另一篇文章 考试名称&#xff1a;“1X”大数据平台运维职业技能等级证书&#xff08;中级&#xff09; 1X 大数据平台运维中级测试题一、单选题 以下哪种情况容易引发 HDFS 负载不均问题&#xff1f;&#xff08; C&#xff09…

定制软件开发的 5 个挑战

对于大公司来说&#xff0c;定制软件开发就像是眼中钉。无论您是要创建内部使用的工具、自动化手动流程还是推出新产品&#xff0c;从头开始构建它历来都是昂贵且危险的。花钱购买领先的现成解决方案之一&#xff0c;却不得不花费更多的时间和金钱对其进行定制来完成工作&#…

绿萝送温暖,扫雪助出行

今冬的大雪如约而至&#xff0c;给居民的出行带来诸多不便&#xff0c;为保障居民安全出行&#xff0c;绿萝志愿服务队第一时间召集志愿者参与扫雪铲冰工作。2023年12月13日&#xff0c;志愿者在房山城关街道青年北路园林所门口、星城生活区等地进行了志愿扫雪活动。 大雪把街道…

SQL自学通之函数 :对数据的进一步处理

目录 一、目标 二、汇总函数 COUNT SUM AVG MAX MIN VARIANCE STDDEV 三、日期/时间函数 ADD_MONTHS LAST_DAY MONTHS_BETWEEN NEW_TIME NEXT_DAY SYSDATE 四、数学函数 ABS CEIL 和FLOOR COS、 COSH 、SIN 、SINH、 TAN、 TANH EXP LN and LOG MOD POW…

为pixhawk4添加外置adis16470传感器

编译驱动 make px4_fmu-v5_default boardconfig在MavLink控制台 adis16470 start -S可以看到IMU3

图像特征提取-角点

角点特征 大多数人都玩过拼图游戏。首先拿到完整图像的碎片&#xff0c;然后把这些碎片以正确的方式排列起来从而重建这幅图像。如果把拼图游戏的原理写成计算机程序&#xff0c;那计算机就也会玩拼图游戏了。 在拼图时&#xff0c;我们要寻找一些唯一的特征&#xff0c;这些…

MCU平台下一种简单的文件系统设计构想

本文介绍MCU平台下一种简单的文件系统设计构想。 在使用MCU的项目中&#xff0c;经常会涉及到一些数据的存储&#xff0c;受限于硬件&#xff0c;又不太可能直接上文件系统&#xff08;如FAT文件系统&#xff09;&#xff0c;直接指定存储地址&#xff0c;数据长度对数据进行读…

HDPE硅芯管强度高,抗压抗张和抗冲击强,外层不需其它套管

HDPE硅芯管是一种高性能的管道材料&#xff0c;具有强度高、抗压抗张和抗冲击强的特点。这种管道材料采用高密度聚乙烯&#xff08;HDPE&#xff09;作为基础材料&#xff0c;并添加了硅质增强剂&#xff0c;使得管道具有优异的力学性能和耐久性。 HDPE硅芯管的强度高&#xf…

大数据CloudSim应用实践

CloudSimExampleA.java 1准备 1.1操作系统 本实验在Windows 7 或Windows 10系统运行均可。 1.2软件 cloudsim-3.0.3.zip&#xff1b; commons-math3-3.2-bin.zip&#xff1b; jdk-8u152-windows-x64.exe&#xff1b; eclipse-jee-neon-3-win32-x86_64 所需资料链接&#xff1…

现代雷达车载应用——第2章 汽车雷达系统原理 2.3节 信号模型

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.3 信号模型 雷达的发射机通常发出精心设计和定义明确的信号。然而&#xff0c;接收到的返回信号是多个分量的叠加&#xff0c;包括目标的反射、杂波…

大数据存储技术(1)—— Hadoop简介及安装配置

目录 一、Hadoop简介 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;Hadoop发展历史 &#xff08;三&#xff09;Hadoop三大发行版本 &#xff08;四&#xff09;Hadoop的优势 二、Hadoop的组成 &#xff08;一&#xff09;Hadoop1.x和Hadoop2.x的区别​…

打印机怎么扫描文件到电脑?6个步骤!轻松完成!

“在工作时我经常需要用到打印机&#xff0c;有时候需要将部分文件扫描到电脑。但是我不是很清楚应该如何操作&#xff0c;有什么方法可以让打印机快速传输文件到电脑的方法吗&#xff1f;” 在人们的工作和学习中&#xff0c;打印机成了很多用户的必备工具。人们可以用它来打印…