1. 概述
事件是框架中最容易被忽视的功能之一,但同时也是一个很有用的功能。像Spring其他特性一样,事件发布是ApplicationContext
提供的功能之一。
事件通知是一个很有用的功能,使用事件机制可以将互相耦合的代码进行解耦,方便功能的新增或修改。
2. 自定义事件
Spring允许创建和发布自定义事件,默认情况下,事件都是同步执行的。这样有很多好处,比如事件的监听器和发布者在同一个事务内,能够很方便的处理一些业务。
2.1. 一个简单的Application Event
创建一个简单的事件类,使用一个String
变量来存储事件数据。
public class CustomSpringEvent extends ApplicationEvent {
@Getter
private String message;
public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
}
2.2. 事件发布者
现在来创建事件发布者。发布者创建事件对象,并把事件发送给所有的监听器。
要发布事件,发布者可以简单的注入ApplicationEventPublisher
然后使用它的publishEvent()
方法:
@Slf4j
@Component
public class CustomSpringEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void doStuffAndPublishAnEvent(final String message) {
log.info("Publishing custom event. ");
CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
applicationEventPublisher.publishEvent(customSpringEvent);
}
}
或者,发布者也可以实现ApplicationEventPublisherAware
接口。通常情况下使用@Autowired
注入会更简单。
2.3. 事件监听器
最后,来创建事件监听器。
监听器的唯一要求是一个bean并实现ApplicationListener
接口:
@Slf4j
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
log.info("Received spring custom event - {}", event.getMessage());
}
}
上面已经说过,默认情况下事件都是同步执行的,在所有的监听器完成对事件的处理之前,doStuffAndPublishAnEvent()
方法会一直堵塞。
如果需要指定监听器的执行顺序,可以实现
Ordered
接口设置每个执行器的优先级。
3. 创建异步事件
在某些情况下,同步处理事件并不是我们想要的效果,我们可能需要异步处理事件。
在AbstractApplicationContext
中存在一个ApplicationEventMulticaster
对事件进行广播,默认情况下框架初始化了一个SimpleApplicationEventMulticaster
:
/**
* Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context.
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
SimpleApplicationEventMulticaster
是如何进行事件广播的呢?
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
如果存在Executor
,那么事件就会进行异步处理,否则就是同步。
所以需要异步的处理事件,那么就需要手动创建一个name为applicationEventMulticaster
的bean,然后为它设置一个TaskExecutor
,例如:
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
事件、发布者和监听器和前面定义相同,这样监听器就会在单独的线程中异步处理事件。
但是,这样配置的话,所有发布的事件都会以异步的方式进行处理,显然太简单粗暴了,下面介绍一种更加友好的方式。
首先,删除刚刚的AsynchronousSpringEventsConfig
配置类,然后在Application主类上加上@EnableAsync
注解,来让程序支持异步方法的调用,最后在监听器的onApplicationEvent
方法上加上@Async
注解。
@EnableAsync
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Slf4j
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Async
@Override
public void onApplicationEvent(CustomSpringEvent event) {
log.info("Received spring custom event - {}", event.getMessage());
}
}
4. 基于注解的事件监听器
从Spring4.2开始,事件监听器不再需要实现ApplicationListener
接口,可以通过@EventListener
注解在一个bean的任意public方法上注册:
@Slf4j
@Component
public class AnnotationDrivenContextStartedListener {
@EventListener
public void handleContextStartedEvent(ContextStartedEvent event) {
log.info("Handling context started event.");
}
}
如果方法需要监听多个事件或者你不想在方法上定义参数,那么你可以在注解里设置事件类型,例如:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
注解的condition
属性可以定义一个SpEL表达式来对事件进行过滤,表达式匹配才能调用特定事件的监听方法。
@EventListener(condition = "event.message == 'message'")
public void handleCustomSpringEvent(CustomSpringEvent event) {
log.info("Received spring custom event - {}", event.getMessage());
}
如果处理完成一个事件后需要发布一个事件,那么你可以在方法上返回相应的事件,例如:
@EventListener
public ReturnEvent handleCustomSpringEvent(CustomSpringEvent event) {
// 首先处理CustomSpringEvent事件
// 处理完成后,发布ReturnEvent事件
}
如果需要返回多个事件,那么可以返回事件的集合。
4.1 异步监听器
监听器的异步处理可以通过@Async
注解来实现。
@Async
@EventListener
public void handleCustomSpringEvent(CustomSpringEvent event) {
// CustomSpringEvent会在一个独立的线程中进行处理
}
当使用异步事件监听时,需要注意以下限制:
- 如果异步事件监听器抛出
Exception
,不会将其传播到调用方。可以查看AsyncUncaughtExceptionHandler
来获取更多详细信息。 - 异步事件监听方法无法通过返回值来发布后续事件。如果你确实需要发布后续事件,可以注入
ApplicationEventPublisher
来手动发布。
4.2 排序监听器
如果需要指定监听器的执行顺序,可以在方法上使用@Order
注解。
@Order(18)
@EventListener
public void handleCustomSpringEvent(CustomSpringEvent event) {
// ...
}
5. 泛型事件
不是所有的事件都必须继承ApplicationEvent
,ApplicationEventPublisher
中有2种方式进行事件的发布。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
通过查看实现类代码可以发现,发送的Object类型的事件最后会被包装为一个PayloadApplicationEvent
,而PayloadApplicationEvent
继承了ApplicationEvent
。
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
所以我们可以发布任何Object事件。
@Data
public class ObjectEvent {
private String message;
public ObjectEvent(String message) {
this.message = message;
}
}
// 发送事件
applicationEventPublisher.publishEvent(new ObjectEvent(message));
// 监听事件
@EventListener
public void handleObjectEvent(ObjectEvent event){
log.info(event.getMessage());
}
6. 事务绑定事件
很多时候,只有事务提交之后我们才会发布相应的事件处理其他逻辑,比如用户注册之后,发送邮件或者短信。从Spring 4.2开始,框架提供了一个很方便的注解来实现此功能(4.2之前也可以通过自己写代码实现),即@TransactionalEventListener
。
@TransactionalEventListener
是对@EventListener
的一个扩展,允许将事件的监听器绑定到事务的某个阶段。可以绑定到以下事务阶段:
- AFTER_COMMIT (默认),事务提交后
- AFTER_ROLLBACK ,事务回滚后
- AFTER_COMPLETION ,事务完成,包括提交后和回滚后
- BEFORE_COMMIT ,事务提交前
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleTransactionalEvent(TransactionalEvent event) {
log.info("Handling event inside a transaction BEFORE COMMIT.");
}
只有当上下文存在事务,并且事务提交前,才会调用此监听器的方法。
默认情况下,如果上下文不存在事务,则根本不会发送事件,我们可以通过设置@TransactionalEventListener
的fallbackExecution
为true来实现。