在Spring事件監聽機制(二)中,我們提到了在SimpleApplicationEventMulticaster類的multicastEvent()方法發送事件廣播時,有一個Executor對象,如果它不爲空,就會異步去執行發送廣播。那麼本篇文章,我們來看一下如何去異步執行發送廣播事件。
異步
一、自定義類直接繼承SimpleApplicationEventMulticaster
這裏我們爲了方便看到執行效果,我們首先自定義一個事件:
public class MessageSendEvent extends ApplicationEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public MessageSendEvent(Object source) {
super(source);
}
}
然後自定義一個發佈器,去調用廣播器發送廣播的方法:
@Component
public class EventPublisher implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void publishEvent(ApplicationEvent event){
applicationContext.publishEvent(event);
}
}
接着定義三個事件監聽者Listener,來監聽我們自定義的事件:
MailSendListener.java
@Component
public class MailSendListener implements ApplicationListener<MessageSendEvent> {
@Override
public void onApplicationEvent(MessageSendEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>>> MailSendListener>>>>>>>>>>>> "+event);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
SMSSendListener.java
@Component
public class SMSSendListener implements ApplicationListener<MessageSendEvent> {
@Override
public void onApplicationEvent(MessageSendEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>> SMSSendListener >>>>>>>>>>> "+event);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
WechatSendListener.java
@Component
public class WechatSendListener implements ApplicationListener<MessageSendEvent> {
@Override
public void onApplicationEvent(MessageSendEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>> WechatSendListener >>>>>>>>>>> "+event);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然後是自定義類,繼承SimpleApplicationEventMulticaster,並設置了Executor對象:
@Component("applicationEventMulticaster")
public class AsyncEventMulticaster extends SimpleApplicationEventMulticaster {
public AsyncEventMulticaster(){
System.out.println("設置廣播器的TaskExecutor>>>>>>>>>>>>>>>>>>>>>");
setTaskExecutor(Executors.newFixedThreadPool(3));
}
}
注意這裏設置setTaskExecutor(),儘量保證線程池的數量大於要執行的監聽器,否則可能導致線程不夠用,那樣的結果還是同步執行,會導致阻塞。
接着我們用單元測試來執行一下程序,看看結果:
@SpringBootTest(classes = {Application.class})
@RunWith(SpringRunner.class)
public class SpringEventTest {
@Autowired
private EventPublisher eventPublisher;
@Test
public void publishEvent(){
MessageSendEvent messageSendEvent = new MessageSendEvent("你好");
eventPublisher.publishEvent(messageSendEvent);
System.out.println("發送完成>>>>>>>>>>>>>>>>>>>");
}
}
2020-05-04 18:28:43>>>>>>>>>>> MailSendListener >>>>>>>>>>>>> com.practice.event.spring.MessageSendEvent[source=你好]
2020-05-04 18:28:43>>>>>>>> WechatSendListener >>>>>>>>>>>>>> com.practice.event.spring.MessageSendEvent[source=你好]
2020-05-04 18:28:43>>>>>>>>>>> SMSSendListener >>>>>>>>>>> com.practice.event.spring.MessageSendEvent[source=你好]
我們看到3個監聽器打印的執行時間是一樣的,說明我們設置的異步起作用了。
二、使用@Async註解
除了上面說的設置Executor外,還有一種方法也可以實現異步。就是在監聽器類或方法上加註解@Async,同時在springboot啓動類上加@EnableAsync註解開啓事件異步。這裏就不展示示例代碼了。大家可以自己動手試一下。
自定義註解實現同步和異步同時存在
還是回到上述提到的方案一,通過設置Executor來實現異步,這裏通過分析源碼,我們發現一個問題,就是假如executor對象存在,那麼所有的監聽器都會異步執行,若executor不存在,所有的監聽器都會同步執行。如果我需要這裏既滿足部分監聽器異步執行,另一部分監聽器同步執行,該怎麼處理呢?
這裏我們可以定義枚舉類EventTypeEnum,值爲ASYNC, //異步 SYNC; //同步
自定義註解@EventType,並設置其值默認爲SYNC,配置在監聽器的方法上,然後自定義類繼承SimpleApplicationEventMulticaster類,並重寫其中的multicastEvent(ApplicationEvent event, ResolvableType eventType)方法,加上監聽器方法上是否有註解@EventType,並且value爲EventTypeEnum.ASYNC,那麼就異步執行,否則就同步執行。當然異步執行需要的executor對象可以在類的構造器中設置。
枚舉類EventTypeEnum.java
public enum EventTypeEnum {
ASYNC, //異步
SYNC; //同步
}
自定義註解EventType.java:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EventType {
EventTypeEnum value() default EventTypeEnum.SYNC;
}
AllTypeEventMulticaster.java:
@Component("applicationEventMulticaster")
public class AllTypeEventMulticaster extends SimpleApplicationEventMulticaster {
public AllTypeEventMulticaster(){
System.out.println("初始化AllTypeEventMulticaster>>>>>>>>>");
setTaskExecutor(Executors.newFixedThreadPool(10));
}
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
System.out.println("AllTypeEventMulticaster>>>>>>>>>>>>>>>>>>>>>>");
//默認異步
EventTypeEnum defaultEventType = EventTypeEnum.SYNC;
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event,type)) {
Class<? extends ApplicationListener> cls = listener.getClass();
try {
Method onApplicationEventMethod = cls.getMethod("onApplicationEvent", ApplicationEvent.class);
if (onApplicationEventMethod.isAnnotationPresent(EventType.class)) {
EventType annotation = onApplicationEventMethod.getAnnotation(EventType.class);
defaultEventType = annotation.value();
}
Executor executor = getTaskExecutor();
if (executor != null && EventTypeEnum.ASYNC.equals(defaultEventType)) {
executor.execute(()->{
invokeListener(listener,event);
});
} else {
invokeListener(listener,event);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
return ResolvableType.forInstance(event);
}
}
將自定義註解加在監聽器方法上。
MailSendListener.java
@Component
public class MailSendListener implements ApplicationListener<MessageSendEvent> {
@Override
@EventType(value = EventTypeEnum.ASYNC)
public void onApplicationEvent(MessageSendEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>>>>> MailSendListener >>>>>>>>>>>>> "+event);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
SMSSendListener.java
@Component
public class SMSSendListener implements ApplicationListener<MessageSendEvent> {
@Override
@EventType(value = EventTypeEnum.SYNC)
public void onApplicationEvent(MessageSendEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>>>>> SMSSendListener >>>>>>>>>>> "+event);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
WechatSendListener.java
@Component
public class WechatSendListener implements ApplicationListener<MessageSendEvent> {
@Override
@EventType(value = EventTypeEnum.ASYNC)
public void onApplicationEvent(MessageSendEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>> WechatSendListener >>>>>>>>>>>>>> "+event);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行結果如下:
2020-05-04 19:00:12>>>>>>>>>>> SMSSendListener >>>>>>>>>>> com.practice.event.spring.MessageSendEvent[source=你好]
2020-05-04 19:00:12>>>>>>>>>>> MailSendListener >>>>>>>>>>>>> com.practice.event.spring.MessageSendEvent[source=你好]
發送完成>>>>>>>>>>>>>>>>>>>
2020-05-04 19:00:17>>>>>>>> WechatSendListener >>>>>>>>>>>>>> com.practice.event.spring.MessageSendEvent[source=你好]
我們可以看到,SMSSendListener 和 MailSendListener是同時執行,WechatSendListener是5秒之後才執行,因爲SMSSendListener是異步的。
@EventListener
補充,這裏其實用@EventListener註解也是可以實現監聽器的功能的,如下:
@Component
public class WechatSendListener {
@EventListener
@EventType(value = EventTypeEnum.ASYNC)
public void onApplicationEvent(MessageSendEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>> WechatSendListener >>>>>>>>>>>>>> "+event);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面在重寫multicastEvent(ApplicationEvent event, ResolvableType eventType)方法時,我們看到getApplicationListeners(event,type)它能夠獲取到我們需要的監聽MessageSendEvent事件的監聽器,那麼它是怎麼實現的呢?來看看源碼:
//獲取事件中的事件源對象
Object source = event.getSource();
//獲取事件源類型
Class<?> sourceType = (source != null ? source.getClass() : null);
//以事件類型和事件源類型爲參數構建一個cacheKey,用於從緩存map中獲取與之匹配的監聽器列表
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
//從緩存中獲取監聽器列表
return retriever.getApplicationListeners();
}
它先會從緩存中去獲取,如果沒獲取到,那麼會調用retrieveApplicationListeners()方法去獲取:
它會取出實現所有的監聽器,然後循環遍歷,判斷監聽器的事件與我們當前事件是否匹配,判斷邏輯就在supportsEvent(listener, eventType, sourceType)方法中:
這裏會對我們的監聽器進行一層適配器包裝成爲GenericApplicationListener,便於後面使用該接口中定義的方法判斷監聽器是否支持傳入的事件類型或事件源類型。
這裏都是GenericApplicationListener類型的,所以都進入了GenericApplicationListener類的方法
declaredEventType是監聽器泛型的實際類型,而eventType是發佈的事件的類型。declaredEventType.isAssignableFrom(eventType)當以下兩種情況返回true:
- declaredEventType和eventType類型相同
- declaredEventType是eventType的父類型
只要監聽器泛型的實際類型和發佈的事件類型一樣或是它的父類型,則該監聽器將被成功匹配。
而smartListener.supportsSourceType(sourceType)是對事件源類型的判斷,通常默認會直接返回true,也就是說事件源的類型通常對於判斷匹配的監聽器沒有意義。
最終這裏匹配到的監聽器 會存放到retriever.applicationListeners中,然後把cacheKey和retriever存放到retrieverCache這個緩存中,以便下次使用時直接從retrieverCache中取得所需要的監聽器。
如果我們想定義監聽器的執行順序,可以實現SmartApplicationListener接口:
@Component
public class WechatSendListener implements SmartApplicationListener {
/**
* 是否支持該事件源類型
* @param eventType
* @return
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return eventType == MessageSendEvent.class;
}
/**
* 是否支持該事件類型
* @param sourceType
* @return
*/
@Override
public boolean supportsSourceType(Class<?> sourceType) {
//spring源碼中,直接返回true,這裏我們也直接返回true
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateStr +">>>>>>>> WechatSendListener >>>>>>>>>>>>>> "+event);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 設置監聽器執行順序
* @return
*/
@Override
public int getOrder() {
return 1;
}
}