Spring 事件

转自:https://www.baeldung.com/spring-events

1.概述

在本文中,我们将讨论如何在Spring中使用事件

事件是框架中最容易被忽视的功能之一,但也是挺有用的功能之一。 而且,与Spring中的许多其他事情一样,事件发布是ApplicationContext提供的功能之一。

有一些简单的准则可以遵循:

  • 该事件应扩展ApplicationEvent
  • 发布器应该注入一个ApplicationEventPublisher对象
  • 侦听器应实现ApplicationListener接口

2.自定义事件

Spring允许我们创建和发布自定义事件,这些事件在默认情况下是同步的。 这具有一些优势-例如,侦听器能够参与发布器的事务上下文中。

2.1. 一个简单的应用事件

我们创建一个简单的事件类–只是一个占位符来存储事件数据。 在这例子中,事件类用String类型的属性字段来存消息:

1
2
3
4
5
6
7
8
9
10
11
public class CustomSpringEvent extends ApplicationEvent {
private String message;

public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}

2.2. 发布器

现在,我们来创建该事件的发布器。 发布器构造事件对象,并将其发布给正在收听的监听器。

要发布事件,发布器可以简单地注入ApplicationEventPublisher并使用publishEvent() API:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class CustomSpringEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;

public void publishCustomEvent(final String message) {
System.out.println("Publishing custom event. ");
CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
applicationEventPublisher.publishEvent(customSpringEvent);
}
}

或者,发布器类可以实现ApplicationEventPublisherAware接口-这还将在应用启动时注入事件发布器。 通常,使用@Autowire来注入发布器会更简单。

2.3.监听器

最后,让我们创建监听器。

侦听器的唯一要求是要一个bean并实现ApplicationListener接口:

1
2
3
4
5
6
7
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
System.out.println("Received spring custom event - " + event.getMessage());
}
}

注意,我们的自定义监听器是如何通过自定义事件的泛型类型参数的,这使onApplicationEvent()方法的类型安全。 这也避免了事件类实例的类型检查并强制转换。

而且,正如已经讨论的,默认情况下,spring事件是同步的,doStuffAndPublishAnEvent()方法将阻塞,直到所有监听器完成对事件的处理为止。

3.创建异步事件

在一些情况下,同步发布事件并不是我们真正想要的,我们可能需要异步处理事件。

你可以通过在配置中创建带执行器的ApplicationEventMulticaster bean来将其打开。 在这为了演示我们用了SimpleAsyncTaskExecutor执行器:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster =
new SimpleApplicationEventMulticaster();

eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}

事件、发布器和监听器的实现与以前相同–但是现在,监听器将在单独的线程中异步处理事件

4.现有框架事件

Spring本身可以发布各种事件。 例如,ApplicationContext会触发各种框架事件。 例如。 ContextRefreshedEventContextStartedEventRequestHandledEvent等。

这些事件为应用程序开发人员提供了一个可以挂接到应用和上下文的生命周期的选项,并在需要时添加自己的自定义逻辑。

这是一个监听器器监听上下文刷新的简单示例:

1
2
3
4
5
6
7
public class ContextRefreshedListener 
implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent cse) {
System.out.println("Handling context re-freshed event. ");
}
}

5.注释驱动的事件监听器

从Spring 4.2开始,事件监听器不必是实现ApplicationListener接口的bean,可以通过@EventListener注解,使任何一个bean的公开方法被注册成监听器:

1
2
3
4
5
6
7
@Component
public class AnnotationDrivenEventListener {
@EventListener
public void handleContextStart(ContextStartedEvent cse) {
System.out.println("Handling context started event.");
}
}

和以前一样,方法签名声明其使用的事件类型。

默认情况下,监听器被同步调用。 但是,我们可以通过添加@Async批注轻松地使其异步。 但是,我们必须记住要在应用程序中启用异步支持。

6.泛型支持

还可以使用事件类型中的泛型信息来分派事件。

6.1. 通用申请事件

让我们创建一个通用事件类型。 在我们的示例中,事件类包含任何内容和成功状态指示符:

1
2
3
4
5
6
7
8
9
10
public class GenericSpringEvent<T> {
private T what;
protected boolean success;

public GenericSpringEvent(T what, boolean success) {
this.what = what;
this.success = success;
}
// ... standard getters
}

注意GenericSpringEventCustomSpringEvent之间的区别。 现在,我们可以灵活地发布任何任意事件,并且不再需要从ApplicationEvent扩展它。

6.2. 监听器

现在,让我们为该事件创建一个监听器。 我们可以像以前一样通过实现ApplicationListener接口来定义监听器:

1
2
3
4
5
6
7
8
@Component
public class GenericSpringEventListener
implements ApplicationListener<GenericSpringEvent<String>> {
@Override
public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
System.out.println("Received spring generic event - " + event.getWhat());
}
}

但不幸的是,此定义要求我们从ApplicationEvent类继承GenericSpringEvent。 因此,对于本教程,让我们利用前面讨论的由注释驱动的事件监听器。

通过在@EventListener注释上定义布尔SpEL表达式,使事件监听器有条件监听。 在这种情况下,只有成功的StringGenericSpringEvent会触发事件监听器:

1
2
3
4
5
6
7
@Component
public class AnnotationDrivenEventListener {
@EventListener(condition = "##event.success")
public void handleSuccessful(GenericSpringEvent<String> event) {
System.out.println("Handling generic event (conditional).");
}
}

Spring Expression Language(SpEL)是一种功能强大的表达语言。

6.3. 发布器

事件发布器与上述事件发布器相似。 但是由于类型擦除,为了能过滤我们的目标时间类型,我们要发布的事件类型需要带上泛型参数。 例如,class GenericStringSpringEvent extends GenericSpringEvent

还有一种发布事件的替代方法。 如果我们从带有@EventListener注释的方法中返回非null值作为结果,则Spring 框架会将结果作为新事件发送给我们。 此外,作为事件处理的结果,我们可以通过将它们返回到一个集合中来发布多个新事件。

7.事务绑定事件

从Spring 4.2开始,该框架提供了一个新的@TransactionalEventListener注解,它是@EventListener的扩展,它允许将事件的监听器绑定到事务的某个阶段。 可以绑定到以下事务阶段:

  • AFTER_COMMIT(默认值),当事务成功完成时触发
  • AFTER_ROLLBACK – 当事务已回滚时触发
  • AFTER_COMPLETION –当事务已完成时触发(AFTER_COMMIT和AFTER_ROLLBACK的别名)
  • BEFORE_COMMIT 当事务即将提交时(提交之前)触发

这是事务性事件侦听器的快速示例:

1
2
3
4
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(CustomSpringEvent event) {
System.out.println("Handling event inside a transaction BEFORE COMMIT.");
}

仅当正在进行的事件处理器里存在事务并且即将要提交时,才会调用此监听器。

而且,如果没有事务在运行,则根本不会发送该事件,除非我们通过将fallbackExecution属性设置为true来重写。

8.结论

在本快速教程中,我们介绍了在Spring中处理事件的基础知识-创建一个简单的自定义事件,将其发布,然后在监听器中进行处理。

我们还简要介绍了如何在配置中启用事件的异步处理。

然后,我们了解了Spring 4.2中引入的改进,例如注释驱动的监听器,更好的泛型支持以及绑定到事务阶段的事件。

与往常一样,可以在Github上获得本文中提供的代码(地址)。 这是一个基于Maven的项目,因此应该很容易直接导入和运行。