编程

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

417 2023-10-07 01:14: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 tubring.cn"));

    System.out.println("事件发送完毕");

  }

}

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

准备发送事件
接收到事件: welcome to ...
事件发送完毕

注意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());

  }

}
准备发送事件
事件监听器 1 接收到事件: ...
事件监听器 2 接收到事件: ...
事件发送完毕

(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());
  }
}
准备发送事件
事件监听器 2 接收到事件: ...
事件监听器 1 接收到事件: ...
事件发送完毕

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)最后测试一下,可看到实现了异步事件监听:

准备发送事件
事件发送完毕
接收到事件:welcome to ...