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

解道jdon 2021-05-04 12:34:42
spring 程序 boot 应用 应用程序


如果要“监听”事件,我们可以在事件发生源处编写“监听器”来监听事件,但会将事件源与侦听器的逻辑紧密耦合。我们可以根据需要动态注册和注销某些事件的侦听器。对于同一事件,我们也可以有多个侦听器。本教程概述了如何发布和监听自定义事件,并解释了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上找到示例代码。

版权声明
本文为[解道jdon]所创,转载请带上原文链接,感谢
https://www.jdon.com/53971

  1. Spring 3中异步方法调用
  2. AOP相关讨论
  3. jf能支持的表现层目前只有struts 1.x么?
  4. 在j2ee中实现一般java对象数据库的方法。
  5. FTP connecting windows and Linux
  6. Decorator design pattern - gene zeiniss
  7. Asynchronous method call in spring 3
  8. Discussion on AOP
  9. Is struts 1. X the only presentation layer supported by JF?
  10. The method of realizing general Java object database in J2EE.
  11. PDF转HTML工具——用springboot包装pdf2htmlEX命令行工具
  12. Pdf to HTML tool -- Wrapping pdf2htmlex command line tool with springboot
  13. MySQL 的 in 查询不走索引?我拿什么拯救你!
  14. MySQL in query does not go index? What can I do to save you!
  15. PDF转HTML工具——用springboot包装pdf2htmlEX命令行工具
  16. Pdf to HTML tool -- Wrapping pdf2htmlex command line tool with springboot
  17. Java小白入门必学!最全数据类型和运算符笔记,附实例
  18. Java Xiaobai introduction must learn! Notes on the most complete data types and operators, with examples
  19. Spring MVC请求与响应
  20. Spring MVC request and response
  21. Java 11已经不再完全免费,不要陷入Oracle的Java 11陷阱
  22. Vue.js比jQuery更容易学习
  23. 启动/删除Docker容器时出现问题 - 如何修复
  24. eclipse run on server时出现了错误信息.求急!!
  25. 请教高手一个关于lunce的问题:java.io.IOException: Cannot rename ...\segments.new
  26. Java 11 is no longer completely free. Don't fall into the Java 11 trap of Oracle
  27. Vue. JS is easier to learn than jQuery
  28. Problem starting / deleting docker container - how to fix it
  29. There is an error message in eclipse run on server!!
  30. Ask a question about lunce: java.io.ioexception: cannot rename... \ segments.new
  31. 从零搭建Spring Boot脚手架(2):集成mybatis
  32. 从零搭建Spring Boot脚手架(4):手写Mybatis通用Mapper
  33. 只知道java反射,宁知道内省吗?
  34. Build spring boot scaffold from scratch (2): integrate mybatis
  35. Build spring boot scaffold from scratch (4): handwritten mybatis general mapper
  36. Do you prefer introspection to reflection?
  37. ASP调用SDK微信分享好友、朋友圈
  38. ASP calls SDK wechat to share friends and circle of friends
  39. BAT 必问的 MySQL 面试题你都会吗?
  40. Do you know all the MySQL interview questions that bat must ask?
  41. ASP调用SDK微信分享好友、朋友圈
  42. ASP calls SDK wechat to share friends and circle of friends
  43. SpringCloud(六)Bus消息总线
  44. 详解JavaScript中的正则表达式
  45. Springcloud (6) bus message bus
  46. Explain regular expressions in JavaScript
  47. Java 响应式关系数据库连接了解一下
  48. Java14它真的来了, 真是尾气都吃不到了
  49. 视频:使用Docker搭建RabbitMQ环境
  50. Java responsive relational database connection
  51. Java14 it's really coming. I can't eat the exhaust
  52. Video: building rabbitmq environment with docker
  53. SpringCloud(六)Bus消息总线
  54. 详解JavaScript中的正则表达式
  55. Springcloud (6) bus message bus
  56. Explain regular expressions in JavaScript
  57. Docker实战:用docker-compose搭建Laravel开发环境
  58. Docker: building laravel development environment with docker compose
  59. 求助,JAVA如何获取系统当前所有进程
  60. 有人用过JMeter或用HttpUnit写过测试吗????