Spring Boot应用程序事件教程 - reflectoring


如果要“监听”事件,我们可以在事件发生源处编写“监听器”来监听事件,但会将事件源与侦听器的逻辑紧密耦合。我们可以根据需要动态注册和注销某些事件的侦听器。对于同一事件,我们也可以有多个侦听器。本教程概述了如何发布和监听自定义事件,并解释了Spring Boot的内置事件。

事件与直接方法调用
事件和直接方法调用都适合于不同的情况。对于方法调用,这就像断言一样,无论发送和接收模块的状态如何,他们都需要知道此事件的发生。
另一方面,对于事件,我们只是说发生了一个事件,并且通知了哪些模块不是我们关心的问题。当我们想将处理传递给另一个线程时,最好使用事件(例如:在完成某些任务时发送电子邮件)。同样,事件对于测试驱动的开发非常有用。

事件用于在松耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此我们可以修改订阅者而不影响发布者,反之亦然。让我们看看如何在Spring Boot应用程序中创建,发布和收听自定义事件。

1. 创建一个 ApplicationEvent
我们可以使用Spring Framework的事件发布机制来发布应用程序事件。
让我们创建一个UserCreatedEvent通过扩展调用的自定义事件ApplicationEvent:

class UserCreatedEvent extends ApplicationEvent {
  private String name;

  UserCreatedEvent(Object source, String name) {
    super(source);
    this.name = name;
  }
  ...
}

source对象是事件发生时可以初始化和传递的参数,传递道super()方法。
从Spring 4.2开始,我们还可以将对象直接发布为事件,而无需扩展ApplicationEvent:

class UserRemovedEvent {
  private String name;

  UserRemovedEvent(String name) {
    this.name = name;
  }
  ...
}

2.发布一个 ApplicationEvent
我们使用ApplicationEventPublisher接口来发布事件:

@Component
class Publisher {
  
  private final ApplicationEventPublisher publisher;
    
    Publisher(ApplicationEventPublisher publisher) {
      this.publisher = publisher;
    }

  void publishEvent(final String name) {
    // Publishing event created by extending ApplicationEvent
    publisher.publishEvent(new UserCreatedEvent(this, name));
   
// Publishing an object as an event
    publisher.publishEvent(new UserRemovedEvent(name));
  }
}

当我们发布的对象不是ApplicationEvent时,Spring会自动用PayloadApplicationEvent包装它

3. 监听事件
现在我们知道如何创建和发布自定义事件,让我们看看如何监听事件。一个事件可以有多个侦听器根据应用程序需求执行不同的工作。
有两种定义侦听器的方法。我们可以使用@EventListener注释或实现ApplicationListener接口。无论哪种情况,监听器类都必须由Spring管理。
从Spring 4.1开始,现在可以简单地注释托管bean的方法,@EventListener以自动注册ApplicationListener与该方法的签名匹配的方法:

@Component
class UserRemovedListener {

  @EventListener
  ReturnedEvent handleUserRemovedEvent(UserRemovedEvent event) {
    // handle UserRemovedEvent ...
    return new ReturnedEvent();
  }

  @EventListener
  void handleReturnedEvent(ReturnedEvent event) {
       
// handle ReturnedEvent ...
  }
  ...
}

启用注释驱动的配置时,不需要其他配置。我们的方法可以监听多个事件,或者如果我们想完全不使用任何参数来定义它,那么事件类型也可以在注释本身上指定。范例:@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})。

对于带有注释@EventListener的方法的返回类型如定义为非void,Spring会将结果作为新事件发布给我们。在上面的示例中,ReturnedEvent第一种方法返回的结果将被发布,然后由第二种方法处理。
如果指定SpEL,Spring仅在某些情况下允许触发我们的侦听器condition:

@Component
class UserRemovedListener {

  @EventListener(condition = "event.name eq 'reflectoring'")
  void handleConditionalListener(UserRemovedEvent event) {
   
// handle UserRemovedEvent
  }
}

仅当表达式的计算结果为true,或包含以下字符串之一时:“true”, “on”, “yes”, 或“1”.方法参数通过其名称公开。条件表达式还公开了一个引用了raw ApplicationEvent(root.event)和实际方法参数的“根”变量(root.args)
在以上示例中,UserRemovedEvent仅当event.name的值为时'reflectoring',才会触发侦听器。

侦听事件的另一种方法是实现ApplicationListener接口:

@Component
class UserCreatedListener implements ApplicationListener<UserCreatedEvent> {

  @Override
  public void onApplicationEvent(UserCreatedEvent event) {
    // handle UserCreatedEvent
  }
}

只要侦听器对象在Spring应用程序上下文中注册,它就会接收事件。当Spring路由一个事件时,它使用侦听器的签名来确定它是否与事件匹配。

异步事件监听器
默认情况下,spring事件是同步的,这意味着发布者线程将阻塞,直到所有侦听器都完成对事件的处理为止。
要使事件侦听器以异步模式运行,我们要做的就是@Async在该侦听器上使用注释:

@Component
class AsyncListener {

  @Async
  @EventListener
  void handleAsyncEvent(String event) {
    // handle event
  }
}

为了使@Async注释生效,我们还必须注释一个@Configuration类,使用@EnableAsync注释SpringBootApplication类。
上面的代码示例还显示,我们可以将String用作事件。使用风险自负。最好使用特定于我们用例的数据类型,以免与其他事件冲突。

事务绑定事件
Spring允许我们将事件侦听器绑定到当前事务的某个阶段。当当前事务的结果对侦听器很重要时,这使事件可以更灵活地使用。
当我们使用注释我们的方法时@TransactionalEventListener,我们得到了一个扩展的事件监听器,该监听器知道事务:

@Component
class UserRemovedListener {

  @TransactionalEventListener(phase=TransactionPhase.AFTER_COMPLETION)
  void handleAfterUserRemoved(UserRemovedEvent event) {
    // handle UserRemovedEvent
  }
}

UserRemovedListener 仅在当前事务完成时才调用。
我们可以将侦听器绑定到事务的以下阶段:
  • AFTER_COMMIT:成功提交事务后,将处理该事件。如果事件侦听器仅在当前事务成功时才运行,则可以使用此方法。
  • AFTER_COMPLETION:在事务提交或回滚时将处理该事件。例如,我们可以使用它在事务完成后执行清理。
  • AFTER_ROLLBACK:交易回滚后,将处理该事件。
  • BEFORE_COMMIT:事件将在事务提交之前处理。例如,我们可以使用它来将事务性O / R映射会话刷新到数据库。

Spring Boot的应用程序事件
以上是Spring事件,Spring Boot提供了几个预定义ApplicationEvent的,这些预定义绑定到SpringApplication生命周期。
在ApplicationContext创建之前会触发一些事件,因此我们无法将这些事件注册为@Bean。我们可以通过手动添加侦听器来注册这些事件的侦听器:

@SpringBootApplication
public class EventsDemoApplication {

  public static void main(String[] args) {
    SpringApplication springApplication = 
        new SpringApplication(EventsDemoApplication.class);
    springApplication.addListeners(new SpringBuiltInEventsListener());
    springApplication.run(args);
  }

}

通过将META-INF/spring.factories文件添加到我们的项目中,我们还可以注册侦听器,而不管如何创建应用的。并通过以下org.springframework.context.ApplicationListener键引用侦听器:
org.springframework.context.ApplicationListener= com.reflectoring.eventdemo.SpringBuiltInEventsListener

class SpringBuiltInEventsListener 
    implements ApplicationListener<SpringApplicationEvent>{

  @Override
  public void onApplicationEvent(SpringApplicationEvent event) {
    // handle event
  }
}

一旦确保正确注册了事件监听器,我们就可以监听所有Spring Boot的SpringApplicationEvents。让我们按照它们应用程序启动期间的执行顺序来看看:

ApplicationStartingEvent
ApplicationStartingEvent在运行开始时但在任何处理之前都会触发,除了侦听器和初始化程序的注册外。

ApplicationEnvironmentPreparedEvent
当Environment在上下文中是可用的,一个ApplicationEnvironmentPreparedEvent被触发,由于此时Environment将准备就绪,因此我们可以在其他bean使用它之前对其进行检查和修改。

ApplicationContextInitializedEvent
ApplicationContext已准备就绪时,一个ApplicationContextInitializedEvent触发,ApplicationContextInitializers被称为尚未加载bean定义。在bean初始化到Spring容器之前,我们可以使用它执行任务。

ApplicationPreparedEvent
当ApllicationContext准备就绪时,一个ApplicationPreparedEvent时会触发,但不会刷新。
在准备好的Environment和bean定义将被加载。

ContextRefreshedEvent
当ApplicationContext刷新时,ContextRefreshedEvent会触发。
ContextRefreshedEvent是直接来自Spring,而不是Spring Boot,并不继承扩展SpringApplicationEvent。

WebServerInitializedEvent
如果我们使用的是Web服务器,WebServerInitializedEvent则在Web服务器准备就绪后会触发a。ServletWebServerInitializedEvent和ReactiveWebServerInitializedEvent分别是servlet和反应式变量。
WebServerInitializedEvent不是继承扩展SpringApplicationEvent。

ApplicationStartedEvent
上下文已被刷新之后,一个ApplicationStartedEvent触发,但在任何Spring boot应用程序和命令行运行都被调用前。

ApplicationReadyEvent
一个ApplicationReadyEvent触发时就表示该应用程序已准备好服务请求。
建议此时不要修改内部状态,因为所有初始化步骤都将完成。

ApplicationFailedEvent
一个ApplicationFailedEvent如果有异常,应用程序无法启动点火。在启动期间的任何时间都可能发生这种情况。我们可以使用它来执行一些任务,例如执行脚本或在启动失败时发出通知。


结论
事件被设计为在同一应用程序上下文中在Spring bean之间进行简单的通信。从Spring 4.2开始,基础结构已得到显着改进,并提供了基于注释的模型以及发布任意事件的功能。
您可以在GitHub上找到示例代码。