文章目录
前言
这篇文章讲的是Spring监听机制,主要分为两篇文章来讲解。
- 从源码分析,到使用Spring监听机制完成实战。
- 通过理解Spring内部的监听机制,手写一个类似的监听机制框架,再从中抽象出设计模式。
一、Spring对事件监听的处理
1. 初始化事件管理器
Spring容器启动过程中,调用了initApplicationEventMulticaster方法,从方法的命名上面可以看出是初始化事件管理器,那么Spring具体是怎么操作的呢?
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
......
} else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton("applicationEventMulticaster",this.applicationEventMulticaster);
}
如果我们没有自定义id为applicationEventMulticaster的bean,那么Spring默认实现了SimpleApplicationEventMulticaster,并把事件管理器注册到单例缓存中。
2.注册事件监听
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
遍历bean工厂中所有的bean,将实现了ApplicationListener接口的bean的bean标识,注册到ApplicationEventMulticaster中。
this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
ListenerRetriever是定义在抽象类AbstractApplicationEventMulticaster中的一组特定目标监听器的帮助类,它的实例作为事件管理器的成员,用来保存所有事件监听器及其bean标识。
3.发布事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
Spring容器启动过程中,会在上文实例化过的事件管理器中发布事件
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
invokeListener(listener, event);
}
Spring在发布事件中,总共做了两件事:
- 获取所有监听该发布事件的监听类
- 循环调用监听类的监听方法
这里值得注意的是,遍历出来的监听器需要与事件类型以及事件源进行匹配,只有匹配成功,才会触发监听,这里具体如何进行匹配的呢,请看下面
从该方法的注释上也能看出来该方法的作用:确定给定的监听器是否支持给定的事件(将监听器包装成GenericApplicationListener,因为该接口定义了supportsEventType和supportsSourceType方法,可以判断监听器是否支持传入的事件类型和事件源类型)。
4. 监听类实例化
通过前面三个步骤,Spring的事件监听基本上整体架构已经出来了,但是总感觉缺了点什么,步骤二注册事件监听的时候,只是将事件监听的bean标识注册到事件管理器中,但是bean是什么时候进行实例化的呢。
在启动容器的refresh方法中,在初始化事件管理器之前,调用了prepareBeanFactory(beanFactory),在该方法中注册了事件监听探测器的组件,负责将探测到的事件监听器bean注册到事件发布器中。
// Register early post-processor for detecting inner beans as ApplicationListeners.
5.监听事件
Spring的抽象类ApplicationEvent下面有四个抽象子类
- ContextCloseEvent(容器关闭)
- ContextRefreshedEvent(容器刷新)
- ContextStoppedEvent(容器停止)
- ContextStartedEvent(容器启动)
这四个事件都是Spring默认提供给我们的,它们都继承了ApplicationEvent,我们也可以通过继承ApplicationEvent自定义事件。
二、Spring事件监听实战
1.需求
在订单服务中,用户下单成功后,需要物流服务和库存服务进行相应的处理,采取异步解耦的方式。
2.编码
- 实例化一个上下文对象,启动Spring容器,代表订单服务运行
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(Thread.currentThread().getName() + ":订单服务开始运行……");
System.out.println(Thread.currentThread().getName() + ":创建订单完成,通知物流、库存……");
- 创建一个订单事件
public class OrderEvent extends ApplicationEvent {
public OrderEvent(Object source) {
super(source);
}
}
- 创建一个物流监听器监听订单事件
@Component
public class LogisticsListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent orderEvent) {
System.out.println(Thread.currentThread().getName() + ":物流服务开始工作……");
}
}
- 创建一个库存监听器监听订单事件
@Component
public class StoreListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent orderEvent) {
System.out.println(Thread.currentThread().getName() + ":库存服务开始工作……");
}
}
- 在订单服务中发布订单事件,所有监听该事件的监听器都能收到消息
@Test
public void test2() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(Thread.currentThread().getName() + ":订单服务开始运行……");
System.out.println(Thread.currentThread().getName() + ":创建订单完成,通知物流、库存……");
context.publishEvent(new OrderEvent(this));
}
结果:
main:订单服务开始运行……
main:创建订单完成,通知物流、库存……
main:物流服务开始工作……
main:库存服务开始工作……
3.思考
通过上面的编码,我们分开了订单服务、物流服务和库存服务,它们之间相互独立,但从结果中来分析,它们都是在主线程中运行的,说明没有实现真正的异步,到这里自然我们会想到使用多线程进行处理,那么Spring有没有提供这方面的支持呢?
4.线程池与事件监听
Spring在调用监听事件之前,会判断事件监听是否存在线程池,如果有则交由线程池处理。
- 配置线程池taskExecutor
<bean id="taskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="4"></property>
<property name="maxPoolSize" value="4"></property>
<property name="queueCapacity" value="50"></property>
<!-- 这里配置前缀名,方便测试 -->
<property name="threadNamePrefix" value="myTaskExecutor"></property>
<property name="waitForTasksToCompleteOnShutdown" value="true"></property>
</bean>
- 在监听方法上面加入@Async注解,表明该方法是异步的
结果:
main:订单服务开始运行……
main:创建订单完成,通知物流、库存……
myTaskExecutor1:物流服务开始工作……
myTaskExecutor2:库存服务开始工作……