• 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:库存服务开始工作……

  • 相关阅读:
    Eclipse打包Android项目时用到proguard.cfg后,出现的Warning:can't find referenced class问题的解决方案
    Android使用Fragment来实现TabHost的功能(解决切换Fragment状态不保存)以及各个Fragment之间的通信
    接口的作用
    Android中Service(服务)详解
    跟 Task 有关的 Intent对象中设置的Flag
    android onNewIntent
    [Android]如何导入已有的外部数据库
    android的文件操作
    MVC 数据验证
    Html辅助方法 之 Form表单标签
  • 原文地址:https://www.cnblogs.com/xyt123/p/14215651.html
Copyright © 2020-2023  润新知