如果要“监听”事件,我们可以在事件发生源处编写“监听器”来监听事件,但会将事件源与侦听器的逻辑紧密耦合。我们可以根据需要动态注册和注销某些事件的侦听器。对于同一事件,我们也可以有多个侦听器。本教程概述了如何发布和监听自定义事件,并解释了Spring Boot的内置事件。
事件与直接方法调用
事件和直接方法调用都适合于不同的情况。对于方法调用,这就像断言一样,无论发送和接收模块的状态如何,他们都需要知道此事件的发生。
另一方面,对于事件,我们只是说发生了一个事件,并且通知了哪些模块不是我们关心的问题。当我们想将处理传递给另一个线程时,最好使用事件(例如:在完成某些任务时发送电子邮件)。同样,事件对于测试驱动的开发非常有用。
事件用于在松耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此我们可以修改订阅者而不影响发布者,反之亦然。让我们看看如何在Spring Boot应用程序中创建,发布和收听自定义事件。
1. 创建一个 ApplicationEvent
我们可以使用Spring Framework的事件发布机制来发布应用程序事件。
让我们创建一个UserCreatedEvent通过扩展调用的自定义事件ApplicationEvent:
<b>class</b> UserCreatedEvent <b>extends</b> ApplicationEvent { <b>private</b> String name; UserCreatedEvent(Object source, String name) { <b>super</b>(source); <b>this</b>.name = name; } ... }
source对象是事件发生时可以初始化和传递的参数,传递道super()方法。
从Spring 4.2开始,我们还可以将对象直接发布为事件,而无需扩展ApplicationEvent:
<b>class</b> UserRemovedEvent { <b>private</b> String name; UserRemovedEvent(String name) { <b>this</b>.name = name; } ... }
2.发布一个 ApplicationEvent
我们使用ApplicationEventPublisher接口来发布事件:
@Component <b>class</b> Publisher { <b>private</b> <b>final</b> ApplicationEventPublisher publisher; Publisher(ApplicationEventPublisher publisher) { <b>this</b>.publisher = publisher; } <b>void</b> publishEvent(<b>final</b> String name) { <font><i>// Publishing event created by extending ApplicationEvent</i></font><font> publisher.publishEvent(<b>new</b> UserCreatedEvent(<b>this</b>, name)); </font><font><i>// Publishing an object as an event</i></font><font> publisher.publishEvent(<b>new</b> UserRemovedEvent(name)); } } </font>
当我们发布的对象不是ApplicationEvent时,Spring会自动用PayloadApplicationEvent包装它
3. 监听事件
现在我们知道如何创建和发布自定义事件,让我们看看如何监听事件。一个事件可以有多个侦听器根据应用程序需求执行不同的工作。
有两种定义侦听器的方法。我们可以使用@EventListener注释或实现ApplicationListener接口。无论哪种情况,监听器类都必须由Spring管理。
从Spring 4.1开始,现在可以简单地注释托管bean的方法,@EventListener以自动注册ApplicationListener与该方法的签名匹配的方法:
@Component <b>class</b> UserRemovedListener { @EventListener ReturnedEvent handleUserRemovedEvent(UserRemovedEvent event) { <font><i>// handle UserRemovedEvent ...</i></font><font> <b>return</b> <b>new</b> ReturnedEvent(); } @EventListener <b>void</b> handleReturnedEvent(ReturnedEvent event) { </font><font><i>// handle ReturnedEvent ...</i></font><font> } ... } </font>
启用注释驱动的配置时,不需要其他配置。我们的方法可以监听多个事件,或者如果我们想完全不使用任何参数来定义它,那么事件类型也可以在注释本身上指定。范例:@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})。
对于带有注释@EventListener的方法的返回类型如定义为非void,Spring会将结果作为新事件发布给我们。在上面的示例中,ReturnedEvent第一种方法返回的结果将被发布,然后由第二种方法处理。
如果指定SpEL,Spring仅在某些情况下允许触发我们的侦听器condition:
@Component <b>class</b> UserRemovedListener { @EventListener(condition = <font>"#event.name eq 'reflectoring'"</font><font>) <b>void</b> handleConditionalListener(UserRemovedEvent event) { </font><font><i>// handle UserRemovedEvent</i></font><font> } } </font>
仅当表达式的计算结果为true,或包含以下字符串之一时:“true”, “on”, “yes”, 或“1”.方法参数通过其名称公开。条件表达式还公开了一个引用了raw ApplicationEvent(#root.event)和实际方法参数的“根”变量(#root.args)
在以上示例中,UserRemovedEvent仅当#event.name的值为时'reflectoring',才会触发侦听器。
侦听事件的另一种方法是实现ApplicationListener接口:
@Component <b>class</b> UserCreatedListener implements ApplicationListener<UserCreatedEvent> { @Override <b>public</b> <b>void</b> onApplicationEvent(UserCreatedEvent event) { <font><i>// handle UserCreatedEvent</i></font><font> } } </font>
只要侦听器对象在Spring应用程序上下文中注册,它就会接收事件。当Spring路由一个事件时,它使用侦听器的签名来确定它是否与事件匹配。
异步事件监听器
默认情况下,spring事件是同步的,这意味着发布者线程将阻塞,直到所有侦听器都完成对事件的处理为止。
要使事件侦听器以异步模式运行,我们要做的就是@Async在该侦听器上使用注释:
@Component <b>class</b> AsyncListener { @Async @EventListener <b>void</b> handleAsyncEvent(String event) { <font><i>// handle event</i></font><font> } } </font>
为了使@Async注释生效,我们还必须注释一个@Configuration类,使用@EnableAsync注释SpringBootApplication类。
上面的代码示例还显示,我们可以将String用作事件。使用风险自负。最好使用特定于我们用例的数据类型,以免与其他事件冲突。
事务绑定事件
Spring允许我们将事件侦听器绑定到当前事务的某个阶段。当当前事务的结果对侦听器很重要时,这使事件可以更灵活地使用。
当我们使用注释我们的方法时@TransactionalEventListener,我们得到了一个扩展的事件监听器,该监听器知道事务:
@Component <b>class</b> UserRemovedListener { @TransactionalEventListener(phase=TransactionPhase.AFTER_COMPLETION) <b>void</b> handleAfterUserRemoved(UserRemovedEvent event) { <font><i>// handle UserRemovedEvent</i></font><font> } } </font>
UserRemovedListener 仅在当前事务完成时才调用。
我们可以将侦听器绑定到事务的以下阶段:
Spring Boot的应用程序事件
以上是Spring事件,Spring Boot提供了几个预定义ApplicationEvent的,这些预定义绑定到SpringApplication生命周期。
在ApplicationContext创建之前会触发一些事件,因此我们无法将这些事件注册为@Bean。我们可以通过手动添加侦听器来注册这些事件的侦听器:
@SpringBootApplication <b>public</b> <b>class</b> EventsDemoApplication { <b>public</b> <b>static</b> <b>void</b> main(String[] args) { SpringApplication springApplication = <b>new</b> SpringApplication(EventsDemoApplication.<b>class</b>); springApplication.addListeners(<b>new</b> SpringBuiltInEventsListener()); springApplication.run(args); } }
通过将META-INF/spring.factories文件添加到我们的项目中,我们还可以注册侦听器,而不管如何创建应用的。并通过以下org.springframework.context.ApplicationListener键引用侦听器:
org.springframework.context.ApplicationListener= com.reflectoring.eventdemo.SpringBuiltInEventsListener
<b>class</b> SpringBuiltInEventsListener implements ApplicationListener<SpringApplicationEvent>{ @Override <b>public</b> <b>void</b> onApplicationEvent(SpringApplicationEvent event) { <font><i>// handle event</i></font><font> } } </font>
一旦确保正确注册了事件监听器,我们就可以监听所有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上 找到示例代码。