在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:
只要监听器泛型的实际类型和发布的事件类型一样或是它的父类型,则该监听器将被成功匹配。
而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;
}
}