四、Spring监听器原理
1. Spring监听器模型
前面我们讲了观察者模式的模型,它的模型主要是由 观察者实体 和 主题实体 构成,而Spring的监听器模式则结合了Spring本身的特征,也就是容器化。在Spring中,监听器实体全部放在ApplicationContext中,事件也是通过ApplicationContext来进行发布,具体模型如下:
我们不难看到,虽说是通过ApplicationContext发布的事件,但其并不是自己进行事件的发布,而是引入了一个处理器—— EventMulticaster,直译就是事件多播器,它负责在大量的监听器中,针对每一个要广播的事件,找到事件对应的监听器,然后调用该监听器的响应方法,图中就是调用了监听器1、3、6。
PS: 只有在某类事件第一次广播时,EventMulticaster才会去做遍历所有监听器的事,当它针对该类事件广播过一次后,就会把对应监听器保存起来了,最后会形成一个缓存Map,下一次就能直接找到这些监听器
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
2. @EventListener原理
直接实现监听器接口,然后注册成Bean,这种方式比较好理解,因为我们自己写的实现类就是监听器。但是使用 @EventListener 时,监听器又是怎么产生呢?我们以上面的【自定义事件与监听器Demo】为例,来看一下关键代码。
我们知道,在生成流程中,会对每个Bean都使用PostProcessor来进行加工,而其中就有这么一个类EventListenerMethodProcessor,这个类会在Bean实例化后进行一系列操作
(PS: 首先,不了解Bean生成过程的同学,可以先去看看另一篇文章:SpringBean生成流程详解 )
private void processBean(final String beanName, final Class<?> targetType) { ......省略前面代码 // Non-empty set of methods ConfigurableApplicationContext context = this.applicationContext; Assert.state(context != null, "No ApplicationContext set"); List<EventListenerFactory> factories = this.eventListenerFactories; Assert.state(factories != null, "EventListenerFactory List not initialized"); // 遍历该Bean中有EventListener注解的方法,此例中即methodA、methodB for (Method method : annotatedMethods.keySet()) { // 遍历监听器工厂,这类工厂是专门用来创建监听器的,此处起作用的是默认工厂DefaultEventListenerFactory for (EventListenerFactory factory : factories) { // DefaultEventListenerFactory是永远返回true的 if (factory.supportsMethod(method)) { Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName)); // 利用该Bean名、Bean类型、方法来创建监听器 ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse); if (applicationListener instanceof ApplicationListenerMethodAdapter) { ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator); } // 把监听器存入容器 context.addApplicationListener(applicationListener); break; } } } ......省略后面代码 }
如上,遍历Bean每个带@EventListener注解的方法,然后利用DefaultEventListenerFactory开始创建监听器,实际上这些监听器类型都是一个适配器类——ApplicationListenerMethodAdapter,只是因为这些监听器具体的参数不一样,所以可以监听不同的事件,做不同的响应
public class DefaultEventListenerFactory implements EventListenerFactory, Ordered { // 省略其余代码 @Override public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) { // 可以看到,每次都是返回一个新对象,所以我们在MyListener里的两个方法都加了@EventListener,其实就会返回两个监听器 return new ApplicationListenerMethodAdapter(beanName, type, method); } }
最后效果如图,成功的创建了两个监听器
3. @EventListener错误尝试
知道了@EventListener的原理,我们其实可以做一些猜测,如下:
methodA是正常的用法;
methodB方法的修饰符是private;
methodC则是监听的ContextRefreshedEvent,但下面方法的入参却是ContextClosedEvent;
后两者都有问题:
可以看到,编译器直接黄底提示了methodB的@EventListener注解,其实从前面我们已经猜到,因为最后我们的调用是由监听器ApplicationListenerMethodAdapter对象直接调用的方法ABC,所以方法必须可被其他对象调用,即public
而后者会在执行广播响应事件时报参数非法异常也是意料之中。
五、同步与异步
通过模型,我们不难看出,事件的发布其实由业务线程来发起,那么哪些监听器的触发呢,是仍由业务线程一个个同步地去通知监听器,还是有专门的线程接手,收到事件后,再转手通知监听器们?
1. 默认同步通知
其实,因为spring默认的多播器没有设置执行器,所以默认采用的是第一种情况,即哪个线程发起的事件,则由哪个线程去通知监听器们,关键代码如下所示
// SimpleApplicationEventMulticaster.java 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); } } } @Nullable protected Executor getTaskExecutor() { return this.taskExecutor; }
我们可以看到,对于每个监听器的调用是同步还是异步,取决于多播器内部是否含有一个执行器,如果有则交给执行器去执行,如果没有,只能让来源线程一一去通知了。
2. 异步通知设置
两种方式,一种是在多播器创建时内置一个线程池,使多播器能够调用自身的线程池去执行事件传播。另一种是不再多播器上做文章,而是在每个监视器的响应方法上标注异步@Async,毫无疑问,第一种才是正道,我们来看看如何做到。其实有多种方法,我们这里说两种。
第一种,直接自定义一个多播器,然后顶替掉Spring自动创建的多播器
@Configuration public class EventConfig { @Bean("taskExecutor") public Executor getExecutor() { ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(2000)); return executor; } // 这其实就是spring-boot自动配置的雏形,所谓的自动配置其实就是通过各种配置类,顶替原有的简单配置 @Bean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME) public ApplicationEventMulticaster initEventMulticaster(@Qualifier("taskExecutor") Executor taskExecutor) { SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster(); simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor); return simpleApplicationEventMulticaster; } }
第二种,为现成的多播器设置设置一个线程池
@Component public class WindowsCheck implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SimpleApplicationEventMulticaster caster = (SimpleApplicationEventMulticaster)applicationContext .getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME); ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(2000)); caster.setTaskExecutor(executor); } }
当然,这里推荐第一种,第二种方法会在spring启动初期的一些事件上,仍采用同步的方式。直至被注入一个线程池后,其才能使用线程池来响应事件。而第一种方法则是官方暴露的位置,让我们去构建自己的多播器。
六、总结
我们可以看到,一个Spring监听器内容其实并不少,而且用到了观察者模式,工厂模式(EventListenerFactory),适配器模式(ApplicationListenerMethodAdapter)。除了这些设计模式,还需要对Spring的基础有些了解,比如Bean生成过程(PostProcessor),不过,相信你看完了本篇,已经对Spring监听器用法及原理已经有了相当的理解了,只需要在后续开发实践中,注意相互印证即可。