java---18

 


前言

这篇文章讲的是Spring监听机制,主要分为两篇文章来讲解。

  1. 从源码分析,到使用Spring监听机制完成实战。
  2. 通过理解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.编码

  1. 实例化一个上下文对象,启动Spring容器,代表订单服务运行
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(Thread.currentThread().getName() + ":订单服务开始运行……");
System.out.println(Thread.currentThread().getName() + ":创建订单完成,通知物流、库存……");
  1. 创建一个订单事件
public class OrderEvent extends ApplicationEvent {
    public OrderEvent(Object source) {
        super(source);
    }
}

 

  1. 创建一个物流监听器监听订单事件
@Component
public class LogisticsListener implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent orderEvent) {
        System.out.println(Thread.currentThread().getName() + ":物流服务开始工作……");
    }
}

 

 

 

  1. 创建一个库存监听器监听订单事件
@Component
public class StoreListener implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent orderEvent) {
        System.out.println(Thread.currentThread().getName() + ":库存服务开始工作……");
    }
}

 

  1. 在订单服务中发布订单事件,所有监听该事件的监听器都能收到消息
@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在调用监听事件之前,会判断事件监听是否存在线程池,如果有则交由线程池处理。

  1. 配置线程池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>
  1. 在监听方法上面加入@Async注解,表明该方法是异步的

结果:

main:订单服务开始运行……
main:创建订单完成,通知物流、库存……
myTaskExecutor1:物流服务开始工作……
myTaskExecutor2:库存服务开始工作……

posted @ 2020-12-31 11:54  ぁ晴  阅读(67)  评论(0编辑  收藏  举报