静下心来的个人专栏
上一篇

Spring 中的自定义事件和通用事件

广告
选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。
大纲

Spring 中的自定义事件和通用事件

简介

与 Spring 框架提供的许多功能一样,事件是 Spring 中 ApplicationContext 提供的最有用的功能之一。 Spring 允许您同步和异步地发布/订阅事件。

Spring 中有几个标准事件,如 ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent 和 ServletRequestHandledEvent。Spring 还允许您根据应用程序的需要创建自定义事件类。我们将首先学习创建自定义事件,然后在另一篇文章中探索标准事件列表。

自定义事件可以同步创建,也可以异步创建。异步创建方式可能看起来很复杂,但由于执行的非阻塞性质,提供了更好的性能。

同步自定义 Spring Event

同步发布/订阅事件只需遵循几个简单的规则。

1、事件类应该扩展 ApplicationEvent。

2、发布者必须使用 ApplicationEventPublisher。

3、事件监听器应该实现 ApplicationListener。

创建自定义事件类

自定义事件类必须扩展 ApplicationEvent 抽象类。我们将创建一个自定义 UserEvent 类,如下所示。

package com.jsbd.events;
import org.springframework.context.ApplicationEvent;
import java.util.StringJoiner;
public class UserEvent extends ApplicationEvent {
  //Custom property
  private String message;
  //Custom property
  private Integer eventId;
  public UserEvent(Object source) {
    super(source);
  }
  public UserEvent(Object source, String message,
                   Integer eventId) {
    super(source);
    this.message = message;
    this.eventId = eventId;
  }
  public String getMessage() {
    return this.message;
  }
  public Integer getEventId() {
    return this.eventId;
  }
  @Override
  public String toString() {
    return new StringJoiner(", ", UserEvent.class.getSimpleName() + "[", "]")
        .add("message='" + message + "'")
        .add("source=" + source)
        .add("eventId=" + eventId)
        .toString();
  }
}

创建事件监听器类

我们将通过实现 ApplicationListener 接口及其 onApplicationEvent 方法来创建一个 EventListener。如您所见,此方法接受 UserEvent 作为参数。每当发布 userEvent 时,都会执行此方法。

package com.jsbd.events;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class EventListener implements ApplicationListener<UserEvent> {
  @Override
  public void onApplicationEvent(UserEvent event) {
    System.out.println("======= UserEvent Listener =======");
    System.out.println(event);
  }
}

创建一个事件发布者类

Spring 框架已经为我们提供了 ApplicationEventPublisher,我们将在我们的自定义事件发布者类中使用 @Autowired 自动装配它。

@Component
public class EventPublisher {
  @Autowired //Use setter based injection
  private ApplicationEventPublisher applicationEventPublisher;
  public void publishEvent(String message, Integer eventId) {
    applicationEventPublisher.publishEvent(
        new UserEvent(this, message, eventId)
    );
  }
}

发布事件并测试示例

现在,我们将编写一个简单的 JUnit 测试用例来发布事件并检查侦听器是否接收到它。我还添加了运行此示例所需的配置。

@Configuration
@ComponentScan("com.jsbd.events")
public class AppConfig {
}
@SpringJUnitConfig(AppConfig.class)
class EventPublisherTest {
  @Autowired
  EventPublisher eventPublisher;
  @Test
  public void sendSynchronousEvent() {
    eventPublisher.publishEvent("User registered", 101);
  }

输出

======= UserEvent Listener =======
UserEvent[message='User registered', source=com.jsbd.events.EventPublisher@abf688e, eventId=101]

使用@EventListener 订阅事件

从 Spring 4.2 开始,可以只使用 @EventListener 注释来注释方法,并将自定义事件作为方法参数。 Spring 会将方法标记为指定事件 UserEvent 的侦听器。

@Component
public class AnnEventListener {
  @EventListener
   public void genericListener(UserEvent userEvent) {
      System.out.println("\n===== General UserEvent Listener =====");
      System.out.println(userEvent);
  }
}

要测试这个方法,我们可以使用我们的 EventPublisher 实例发送一个事件,如前所示。侦听器无需任何额外配置即可工作。

使用 @async 的异步事件监听器

同步发布-订阅本质上是阻塞的,会对应用程序性能产生不良影响。为了摆脱这种情况,Spring 使用 @async 注解提供了异步事件监听器。

第一步是通过使用@EnableAsync 注释配置来启用异步处理,如下所示。

@Configuration
@ComponentScan("com.jsbd.events")
@EnableAsync
public class AppConfig {
  //other configs if needed
}

然后,第二步是使用@async 和监听器,如下所示

@Component
public class AnnEventListener {
 //Async Event listener
  @EventListener
  @Async
  public void asyncListener(UserEvent userEvent) {
    System.out.println("===== Async UserEvent Listener =====");
    System.out.println(userEvent);
  }
}

或者,如果您希望为 ApplicationContext 全局启用异步消息处理,请使用以下配置。您将不需要使用 @async 注释。

@Bean
@Scope("singleton")
ApplicationEventMulticaster eventMulticaster() {
    SimpleApplicationEventMulticaster eventMulticaster 
                                            = new SimpleApplicationEventMulticaster();
    eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
    //optionally set the ErrorHandler
    eventMulticaster.setErrorHandler(TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER);
    return eventMulticaster;
}

使用 SpEL 进行运行时事件过滤

有时,您可能希望过滤掉传递给侦听器的事件。 Spring 允许您定义基于 SpEL 的表达式,基于该表达式将匹配的值传递给事件侦听器。仅将 eventId ==120 的 UserEvents 传送到此侦听器。

@Component
public class AnnEventListener {
  //Event Listener with a Spring SpEL expression
  @EventListener(condition = "#userEvent.eventId == 102")
  public void processEvent(UserEvent userEvent) {
    System.out.println("====== Conditional UserEvent Listener =====");
    System.out.println(userEvent);
  }
}

使用 @Order 对 EventListener 进行排序

当您希望在另一个事件侦听器之前调用特定事件侦听器时,可以使用 @Order 注释。请记住,具有最小值的注释具有最高的执行优先级。所以在下面的例子中,listenerA 在 listenerB 之前执行。

@Component
public class AnnEventListener {
  @EventListener
  @Order(1)
  public void listenerA(UserEvent userEvent) {
    System.out.println("\n===== UserEvent ListenerA =====");
    System.out.println(userEvent);
  }
  @EventListener
  @Order(5)
  public void listenerB(UserEvent userEvent) {
    System.out.println("\n===== UserEvent ListenerB =====");
    System.out.println(userEvent);
  }
}

通用事件

Spring 还允许您使用通用事件类来发布任何类型的事件。如下代码所示,不用为每种事件类型创建太多的类,您可以只创建一个具有更多属性的 GenericEvent 类来发送任何类型的事件。

public class GenericEvent<T> implements ResolvableTypeProvider {
  private T message;
  private Object source;
  public GenericEvent(Object source, T message) {
    this.source = source;
    this.message = message;
  }
  @Override
  public ResolvableType getResolvableType() {
    return ResolvableType.forClassWithGenerics(
        getClass(), ResolvableType.forInstance(getSource())
    );
  }
  public T getMessage() {
    return message;
  }
  public Object getSource() {
    return source;
  }
  @Override
  public String toString() {
    return new StringJoiner(", ", GenericEvent.class.getSimpleName() + "[", "]")
        .add("message=" + message)
        .add("source=" + source)
        .toString();
  }
}

创建 GenericEvent 类后,接下来可以通过使用 @EventListener 注释方法来创建侦听器。

@Component
public class GenericEventListener {
  @EventListener
  public void listenEvent(GenericEvent event) {
    System.out.println(event);
  }
}

现在,让我们发布一些事件并进行测试。

@SpringJUnitConfig(AppConfig.class)
public class GenericEventTest {
  @Autowired
  private ApplicationEventPublisher eventPublisher;
  @Test
  public void publishEvent() {
    GenericEvent event1 = new GenericEvent<>(this, new Person("John"));
    GenericEvent event2 = new GenericEvent<>(this, "Hello");
    eventPublisher.publishEvent(event1);
    eventPublisher.publishEvent(event2);
  }
}
public class Person {
  private String name;
  public Person(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return new StringJoiner(", ", Person.class.getSimpleName() + "[", "]")
        .add("name='" + name + "'")
        .toString();
  }
}

记住:

Spring 的事件机制是为同一应用程序上下文中的 Spring bean 之间的简单通信而设计的。

但是,对于更复杂的需求,您可以探索 Spring Integration 项目。

Spring 事件提供了一种在 Spring bean 之间进行通信的好方法。

版权声明:著作权归作者所有。

X

欢迎加群学习交流

联系我们