目录
前言
在很多公司可能在事件回调机制上,并发不高的情况下会使用到Spring的事件监听机制来回调,那么本帖来介绍和使用Spring事件监听机制,并且从源码的角度解读事件监听机制。
正文
Spring事件监听机制的使用
理论
好比如说,一个注册的案例,一般情况下当用户注册成功后会发送一条手机信息给用户的手机。那么对于发送信息来说就是一个事件,对于事件来说都会有一个或多个监听者来监听事件。当注册成功后就会推送这个事件。也就是说注册成功后会回调监听者的监听。
如上图所示,宏观上来说就3个角色,那么我们具体看实操代码。
实操之同步调用
// 事件代码,比如就是发送短信的业务逻辑代码
/**
* @Author liha
* @Date 2022-02-10 20:39
* 李哈YYDS
*/
@Component
public class SMSEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public SMSEvent(ApplicationContext source) {
super(source);
}
public void service(){
System.out.println("模拟发短信");
}
}
// 监听者的代码
/**
* @Author liha
* @Date 2022-02-10 20:41
* 李哈YYDS
*/
@Component
public class MyEventListenerA implements ApplicationListener<MyEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(SMSEvent event) {
System.out.println("回调MyEventListenerA");
TimeUnit.SECONDS.sleep(2);
event.service();
}
}
// 定义事件推送,也就是哪里要回调事件就在哪里推送
/**
* @Author liha
* @Date 2022-02-10 20:43
* 李哈YYDS
*/
@Component
public class MyPublisher implements ApplicationContextAware {
ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void publisherEvent(ApplicationEvent myEvent) {
System.out.println("---开始发布 Event 事件---");
applicationContext.publishEvent(myEvent);
}
}
// 在Spring中基于注解形式的编写,肯定要有配置类扫描注解位置
/**
* @Author liha
* @Date 2022-02-10 20:49
* 李哈YYDS
*/
@Configuration
@ComponentScan("com.example.test1.springEvent")
public class Config {
}
启动类
/**
* @Author liha
* @Date 2022-02-10 20:33
* 李哈YYDS
*/
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
MyPublisher myPublisher = context.getBean("myPublisher", MyPublisher.class);
SMSEvent smsEvent = context.getBean("SMSEvent", SMSEvent.class);
// EmailEvent emailEvent = context.getBean("emailEvent", EmailEvent.class);
System.out.println("模拟注册成功--------->");
myPublisher.publisherEvent(smsEvent);
// myPublisher.publisherEvent(emailEvent);
}
}
很简单的一个案例,在启动类中模拟注册成功,然后推送回调发短信事件。我们看看控制台输出。
实操之异步调用
大家再思考,假如说甲方公司某天改方案,他说我这边注册业务需要当用户注册成功不仅仅是发短信还需要发邮箱。假如说发短信需要2s,发邮箱需要2s,那么你这注册业务再这里起码都得等待4s,再并发高一点系统的QPS岂不是特别的拉胯?
所以这时候就需要引进异步回调了,代码如下
添加邮箱事件
// 发送邮箱事件
/**
* @Author liha
* @Date 2022-02-11 15:23
* 李哈YYDS
*/
@Component
public class EmailEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public EmailEvent(ApplicationContext source) {
super(source);
}
public void service(){
System.out.println("模拟发邮件");
}
}
添加监听邮箱事件
// 添加监听邮箱事件
/**
* @Author liha
* @Date 2022-02-10 20:42
* 李哈YYDS
*/
@Component
public class MyEventListenerB implements ApplicationListener<EmailEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(EmailEvent event) {
System.out.println("回调MyEventListenerB");
TimeUnit.SECONDS.sleep(2);
event.service();
}
}
启动类肯定要加上推送邮箱事件
// 启动类
/**
* @Author liha
* @Date 2022-02-10 20:33
* 李哈YYDS
*/
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
MyPublisher myPublisher = context.getBean("myPublisher", MyPublisher.class);
SMSEvent smsEvent = context.getBean("SMSEvent", SMSEvent.class);
EmailEvent emailEvent = context.getBean("emailEvent", EmailEvent.class);
System.out.println("模拟注册成功--------->");
myPublisher.publisherEvent(smsEvent);
myPublisher.publisherEvent(emailEvent);
}
}
配置类需要我们手动注入一个bean,为什么需要注入后面源码阶段会解释
// 配置类
/**
* @Author liha
* @Date 2022-02-10 20:49
* 李哈YYDS
*/
@Configuration
@ComponentScan("com.example.test1.springEvent")
public class Config {
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(){
SimpleApplicationEventMulticaster applicationEventPublisher = new SimpleApplicationEventMulticaster();
applicationEventPublisher.setTaskExecutor(new SimpleAsyncTaskExecutor());
return applicationEventPublisher;
}
}
再运行就会发现是异步调用的2个事件了。
总结
感觉没啥总结的,关注于源码解读把!
Spring事件监听机制源码解读
首先要明白不管是事件还是监听器还是事件推送器都是注入到IOC容器中的,我们是从IOC容器中一级缓存中取到的已经经历过全部生命周期的bean对象了。
再讲解一下refresh()方法中的方法对于事件监听做的事情把,因为注入到ioc容器的bean都经历过这些流程,并且对后面追源码也有铺垫工作(一些初始化工作)
其中有些事件是一些接口便于扩展,我们这里并没有使用到,我们使用的是自定义事件。
给上断点追进去。
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// 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();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
前面就是一些类型的判断,我们事件是继承了ApplicationEvent, 所以继续接着往后走,看到this.earlyApplicationEvents != null,先解释一下earlyApplicationEvents就是更早的事件,这里肯定是因为在refresh()方法中多个方法中如果有更早的事件定义已经执行完毕,并且将earlyApplicationEvents置为null了,所以肯定是进入到else代码中。getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);我们给上一个断点追进去,先看到getApplicationEventMulticaster()方法。
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
if (this.applicationEventMulticaster == null) {
throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
"call 'refresh' before multicasting events via the context: " + this);
}
return this.applicationEventMulticaster;
}
获取到applicationEventMulticaster对象,那么applicationEventMulticaster对象在哪里初始化的呢?
就是我之前复习的refresh()方法中的initApplicationEventMulticaster()方法来初始化事件传播器。
我们获取到已经初始化的applicationEventMulticaster的对象后执行multicastEvent()方法
@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);
}
}
}
这里就是核心代码了,getApplicationListeners()方法获取到监听器,监听器又是哪里被添加进去的呢?
在refresh()方法的registerListeners()方法中添加的监听器到缓存中,然后getApplicationListeners()方法具体内容我就不带小伙伴们看了,就是取缓存中的所有监听器,我们要思考,当前我们推送的smsEvent事件,而2个监听器分别是监听的sms和email,所以在getApplicationListeners()中会做一个筛选的步骤,把不是sms的监听器给筛选出去。 想追详情的同学可以先把
abstractApplicationEventMulticaster类
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
的结构弄明白,因为这是内部的缓存
当getApplicationListeners()方法获取到当前事件的监听器后就会for循环开始执行,那么重要来了
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
// executor接口
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
可以看出来是不是异步就取决是不是存在executor对象,所以我们要弄明白getTaskExecutor()方法
而 getTaskExecutor()方法返回值就是当前的一个全员变量,所以到这里是不是没办法知道这个变量怎么赋值的?
注意这里是传授看源码的一个小技巧了
能看到这两个变量在这2处被调用,我们追过去瞧瞧。
他的一个set和get方法,怎么被赋值的我们肯定是要追寻到set到方法,所以继续ctrl点击变量
可以看到3处,1处是我的配置类中,2处是我的注释里,而我的配置文件代码如下
看到这里就知道是我们通过@Bean手动注入的bean对象,而找到refresh()方法中initApplicationEventMulticaster()方法代码如下
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);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
// APPLICATION_EVENT_MULTICASTER_BEAN_NAME 常量值
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
没错就是初始化事件传播器的时候, 先是判断我们是否手动往IOC容器中注入了applicationEventMulticaster对象,而我们是通过@Configuration配置类@Bean注解手动注入。
而手动注入的时候通过setTaskExecutor()方法设置了Executor对象。
所以回到最前面for循环中的if判断,所以肯定为true,就创建线程来异步调用监听器中的方法。然后email事件也开始走同样的流程了。
而我们没有手动注入事件传播器就会通过initApplicationEventMulticaster()方法来创建,并且没有赋值Executor所以就只能同步执行。
总结
源码环节并没有往特别深处讲,但是把执行流程和异步和同步的区别讲了。
但是加入了Executor后只能是异步,而不加只能是同步,有没有异步和同步都支持呢?通过一些特定的条件来判断是异步还是同步。其实你能想到的Spring都想到了,下节仔细讲解异步和同步同时存在的情况。