返回 导航

SpringBoot / Cloud

hangge.com

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

作者:hangge | 2023-02-03 09:00
    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)最后我们可以使用 ApplicationContextpublishEvent 方法来发布事件。
@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 注册监听器。我们对项目启动类做如下修改:
注意:对于ApplicationStartedEventApplicationReadyEventApplicationFailedEvent 这些在 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);
  }
}
@SpringBootApplication
public class RmiserverApplication {

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

(4)启动项目,可以看到控制台输出如下:
评论

全部评论(0)

回到顶部