1. 概述
事件是框架中最容易被忽視的功能之一,但同時(shí)也是一個(gè)很有用的功能。像Spring其他特性一樣卦睹,事件發(fā)布是ApplicationContext
提供的功能之一金句。
事件通知是一個(gè)很有用的功能逗物,使用事件機(jī)制可以將互相耦合的代碼進(jìn)行解耦搬卒,方便功能的新增或修改。
2. 自定義事件
Spring允許創(chuàng)建和發(fā)布自定義事件翎卓,默認(rèn)情況下契邀,事件都是同步執(zhí)行的。這樣有很多好處失暴,比如事件的監(jiān)聽(tīng)器和發(fā)布者在同一個(gè)事務(wù)內(nèi)坯门,能夠很方便的處理一些業(yè)務(wù)。
2.1. 一個(gè)簡(jiǎn)單的Application Event
創(chuàng)建一個(gè)簡(jiǎn)單的事件類(lèi)逗扒,使用一個(gè)String
變量來(lái)存儲(chǔ)事件數(shù)據(jù)古戴。
public class CustomSpringEvent extends ApplicationEvent {
@Getter
private String message;
public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
}
2.2. 事件發(fā)布者
現(xiàn)在來(lái)創(chuàng)建事件發(fā)布者。發(fā)布者創(chuàng)建事件對(duì)象矩肩,并把事件發(fā)送給所有的監(jiān)聽(tīng)器现恼。
要發(fā)布事件,發(fā)布者可以簡(jiǎn)單的注入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);
}
}
或者黍檩,發(fā)布者也可以實(shí)現(xiàn)ApplicationEventPublisherAware
接口述暂。通常情況下使用@Autowired
注入會(huì)更簡(jiǎn)單。
2.3. 事件監(jiān)聽(tīng)器
最后建炫,來(lái)創(chuàng)建事件監(jiān)聽(tīng)器。
監(jiān)聽(tīng)器的唯一要求是一個(gè)bean并實(shí)現(xiàn)ApplicationListener
接口:
@Slf4j
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
log.info("Received spring custom event - {}", event.getMessage());
}
}
上面已經(jīng)說(shuō)過(guò)疼蛾,默認(rèn)情況下事件都是同步執(zhí)行的肛跌,在所有的監(jiān)聽(tīng)器完成對(duì)事件的處理之前,doStuffAndPublishAnEvent()
方法會(huì)一直堵塞察郁。
如果需要指定監(jiān)聽(tīng)器的執(zhí)行順序衍慎,可以實(shí)現(xiàn)
Ordered
接口設(shè)置每個(gè)執(zhí)行器的優(yōu)先級(jí)。
3. 創(chuàng)建異步事件
在某些情況下皮钠,同步處理事件并不是我們想要的效果稳捆,我們可能需要異步處理事件。
在AbstractApplicationContext
中存在一個(gè)ApplicationEventMulticaster
對(duì)事件進(jìn)行廣播麦轰,默認(rèn)情況下框架初始化了一個(gè)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
是如何進(jìn)行事件廣播的呢乔夯?
@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
,那么事件就會(huì)進(jìn)行異步處理款侵,否則就是同步末荐。
所以需要異步的處理事件,那么就需要手動(dòng)創(chuàng)建一個(gè)name為applicationEventMulticaster
的bean新锈,然后為它設(shè)置一個(gè)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;
}
}
事件、發(fā)布者和監(jiān)聽(tīng)器和前面定義相同,這樣監(jiān)聽(tīng)器就會(huì)在單獨(dú)的線程中異步處理事件块请。
但是娜氏,這樣配置的話,所有發(fā)布的事件都會(huì)以異步的方式進(jìn)行處理墩新,顯然太簡(jiǎn)單粗暴了贸弥,下面介紹一種更加友好的方式。
首先抖棘,刪除剛剛的AsynchronousSpringEventsConfig
配置類(lèi)茂腥,然后在Application主類(lèi)上加上@EnableAsync
注解,來(lái)讓程序支持異步方法的調(diào)用切省,最后在監(jiān)聽(tīng)器的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. 基于注解的事件監(jiān)聽(tīng)器
從Spring4.2開(kāi)始,事件監(jiān)聽(tīng)器不再需要實(shí)現(xiàn)ApplicationListener
接口朝捆,可以通過(guò)@EventListener
注解在一個(gè)bean的任意public方法上注冊(cè):
@Slf4j
@Component
public class AnnotationDrivenContextStartedListener {
@EventListener
public void handleContextStartedEvent(ContextStartedEvent event) {
log.info("Handling context started event.");
}
}
如果方法需要監(jiān)聽(tīng)多個(gè)事件或者你不想在方法上定義參數(shù)般渡,那么你可以在注解里設(shè)置事件類(lèi)型,例如:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
注解的condition
屬性可以定義一個(gè)SpEL表達(dá)式來(lái)對(duì)事件進(jìn)行過(guò)濾芙盘,表達(dá)式匹配才能調(diào)用特定事件的監(jiān)聽(tīng)方法驯用。
@EventListener(condition = "event.message == 'message'")
public void handleCustomSpringEvent(CustomSpringEvent event) {
log.info("Received spring custom event - {}", event.getMessage());
}
如果處理完成一個(gè)事件后需要發(fā)布一個(gè)事件,那么你可以在方法上返回相應(yīng)的事件儒老,例如:
@EventListener
public ReturnEvent handleCustomSpringEvent(CustomSpringEvent event) {
// 首先處理CustomSpringEvent事件
// 處理完成后蝴乔,發(fā)布ReturnEvent事件
}
如果需要返回多個(gè)事件,那么可以返回事件的集合驮樊。
4.1 異步監(jiān)聽(tīng)器
監(jiān)聽(tīng)器的異步處理可以通過(guò)@Async
注解來(lái)實(shí)現(xiàn)薇正。
@Async
@EventListener
public void handleCustomSpringEvent(CustomSpringEvent event) {
// CustomSpringEvent會(huì)在一個(gè)獨(dú)立的線程中進(jìn)行處理
}
當(dāng)使用異步事件監(jiān)聽(tīng)時(shí),需要注意以下限制:
- 如果異步事件監(jiān)聽(tīng)器拋出
Exception
囚衔,不會(huì)將其傳播到調(diào)用方挖腰。可以查看AsyncUncaughtExceptionHandler
來(lái)獲取更多詳細(xì)信息练湿。 - 異步事件監(jiān)聽(tīng)方法無(wú)法通過(guò)返回值來(lái)發(fā)布后續(xù)事件猴仑。如果你確實(shí)需要發(fā)布后續(xù)事件,可以注入
ApplicationEventPublisher
來(lái)手動(dòng)發(fā)布肥哎。
4.2 排序監(jiān)聽(tīng)器
如果需要指定監(jiān)聽(tīng)器的執(zhí)行順序辽俗,可以在方法上使用@Order
注解。
@Order(18)
@EventListener
public void handleCustomSpringEvent(CustomSpringEvent event) {
// ...
}
5. 泛型事件
不是所有的事件都必須繼承ApplicationEvent
篡诽,ApplicationEventPublisher
中有2種方式進(jìn)行事件的發(fā)布榆苞。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
通過(guò)查看實(shí)現(xiàn)類(lèi)代碼可以發(fā)現(xiàn),發(fā)送的Object類(lèi)型的事件最后會(huì)被包裝為一個(gè)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();
}
}
所以我們可以發(fā)布任何Object事件。
@Data
public class ObjectEvent {
private String message;
public ObjectEvent(String message) {
this.message = message;
}
}
// 發(fā)送事件
applicationEventPublisher.publishEvent(new ObjectEvent(message));
// 監(jiān)聽(tīng)事件
@EventListener
public void handleObjectEvent(ObjectEvent event){
log.info(event.getMessage());
}
6. 事務(wù)綁定事件
很多時(shí)候,只有事務(wù)提交之后我們才會(huì)發(fā)布相應(yīng)的事件處理其他邏輯赊琳,比如用戶注冊(cè)之后街夭,發(fā)送郵件或者短信。從Spring 4.2開(kāi)始躏筏,框架提供了一個(gè)很方便的注解來(lái)實(shí)現(xiàn)此功能(4.2之前也可以通過(guò)自己寫(xiě)代碼實(shí)現(xiàn))板丽,即@TransactionalEventListener
。
@TransactionalEventListener
是對(duì)@EventListener
的一個(gè)擴(kuò)展趁尼,允許將事件的監(jiān)聽(tīng)器綁定到事務(wù)的某個(gè)階段埃碱。可以綁定到以下事務(wù)階段:
- AFTER_COMMIT (默認(rèn))酥泞,事務(wù)提交后
- AFTER_ROLLBACK 砚殿,事務(wù)回滾后
- AFTER_COMPLETION ,事務(wù)完成芝囤,包括提交后和回滾后
- BEFORE_COMMIT 似炎,事務(wù)提交前
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleTransactionalEvent(TransactionalEvent event) {
log.info("Handling event inside a transaction BEFORE COMMIT.");
}
只有當(dāng)上下文存在事務(wù),并且事務(wù)提交前悯姊,才會(huì)調(diào)用此監(jiān)聽(tīng)器的方法羡藐。
默認(rèn)情況下,如果上下文不存在事務(wù)悯许,則根本不會(huì)發(fā)送事件仆嗦,我們可以通過(guò)設(shè)置@TransactionalEventListener
的fallbackExecution
為true來(lái)實(shí)現(xiàn)。