6 Star 72 Fork 28

JustryDeng / notebook

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
[03]事件监听(基于SpringBoot示例).md 15.71 KB
一键复制 编辑 原始数据 按行查看 历史
邓沙利文 提交于 2022-08-03 17:43 . 优化author

事件监听(基于SpringBoot示例)


概述

事件的发布与监听从属于观察者模式;和MQ相比,事件的发布与监听偏向于处理“体系内”的某些逻辑。事件的发布与监听总体分为以下几个步骤:

步骤 相关事宜
1 定义事件
2 定义(用于处理某种事件的)监听器
3 注册监听器
4 发布事件(,监听到了该事件的监听器自动进行相关逻辑处理)

详细(示例)说明

第一步:通过继承ApplicationEvent来自定义事件

import org.springframework.context.ApplicationEvent;

/**
 * 自定义事件
 *
 * 注:继承ApplicationEvent即可。
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @date 2019/11/19 6:36
 */
public class MyEvent extends ApplicationEvent {


    /**
     * 构造器
     *
     * @param source
     *            该事件的相关数据
     *
     * @date 2019/11/19 6:40
     */
    public MyEvent(Object source) {
        super(source);
    }
}

注:构造器的参数为该事件的相关数据对象,监听器可以获取到该数据对象,进而进行相关逻辑处理。

注:我们一般这么用:

public class MyEvent extends ApplicationEvent {
    
    /** 事件相关的数据 */
    @Getter
    private final EventDataDTO eventDataDTO;
    
    public MyEvent(EventDataDTO source) {
        super(source);
        this.eventDataDTO = source;
    }
}

第二步:自定义监听器

  • 方式一(推荐): 通过实现ApplicationListener来自定义监听器,其中E为此监听器要监听的事件。
     /**
      * 自定义监听器
      *
      * 注:实现ApplicationListener<E extends ApplicationEvent>即可,
      *    其中E为此监听器要监听的事件。
      *
      * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
      * @date 2019/11/19 6:44
      */
     public class MyListenerOne implements ApplicationListener<MyEvent> {
     
         /**
          * 编写处理事件的逻辑
          *
          * @param event
          *            当前事件对象
          */
         @Override
         public void onApplicationEvent(MyEvent event) {
             /// 当前事件对象携带的数据
             /// Object source = event.getSource();
             System.out.println(
                     "线程-【" + Thread.currentThread().getName() + "】 => "
                     + "监听器-【MyListenerOne】 => "
                     + "监听到的事件-【" + event + "】"
             );
     
         }
     }
    注:需要重写onApplicationEvent方法来自定义相关事件的处理逻辑。
  • 方式二: 在某个方法上使用@EventListener注解即可。
     import org.springframework.context.event.EventListener;
     
     /**
      * 自定义监听器
      *
      * 注:在某个方法上使用@EventListener注解即可。
      *    追注一: 这个方法必须满足: 最多能有一个参数。
      *    追注二:    若只是监听一种事件,那么这个方法的参数类型应为该事
      *           件对象类;P.S.:该事件的子类事件,也属于该事件,也会被监听到。
      *              若要监听多种事件,那么可以通过@EventListener注解
      *           的classes属性指定多个事件,且保证这个方法无参;
      *
      *
      *
      * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
      * @date 2019/11/19 6:44
      */
     public class MyListenerTwo {
     
         /**
          * 编写处理事件的逻辑
          *
          * @param event
          *            当前事件对象
          */
         @EventListener
         public void abc(MyEvent event) {
             /// 当前事件对象携带的数据
             /// Object source = event.getSource();
             System.out.println(
                     "线程-【" + Thread.currentThread().getName() + "】 => "
                             + "监听器-【MyListenerTwo】 => "
                             + "监听到的事件-【" + event + "】"
             );
         }
     
     }
    注:在某个方法上使用@EventListener注解即可。 注:被@EventListener注解方法必须满足: 最多能有一个参数。 注:若只是监听一种事件,那么这个方法的参数类型应为该事件对象类;        P.S.:该事件的子类事件,也属于该事件,也会被监听到。 注:若要监听多种事件,那么可以通过@EventListener注解的classes属性指定多个事件,        且保证这个方法无参。 提示:还可通过设置@EventListener注解的condition属性来对事件进行选择性处理(P.S.:当然用代码也能做到)。

第三步:注册监听器

所谓注册监听器,其实质就是将监听器进行IOC处理,让容器管理监听器的生命周期。 在SpringBoot中,有以下方式可以达到这些效果:

方式 具体操作 适用范围 能否搭配@Async注解,进行异步监听 优先级(即:当这三种方式注册的(监听同一类型事件的)监听器都同时存在时,那么一个事件发布后,哪一种方式先监听到)
在SpringBoot启动类的main方法中,使用SpringApplication的addListeners方法进行注册
提示:当在进行单元测试时,(由于不会走SpringBoot启动的类main方法,所以)此方式不生效。
实现了ApplicationListener的监听器 不能 取决于Bean被Ioc的先后顺序。可通过设置@Order优先级的方式,来达到调整监听器优先级的目的。
提示:实际开发中,考虑优先级的意义不大。
(推荐) 通过@Component或类似注解,将监听器(或监听器方法所在的)类IOC 实现了ApplicationListener的监听器以及通过@EventListener注解指定的监听器
在SpringBoot配置文件(.properties文件或.yml文件)中指定监听器(或监听器方法所在的)类
注:多个监听器,使用逗号分割即可。
实现了ApplicationListener的监听器 不能
  • 方式①示例:

    在这里插入图片描述

  • 方式②示例:

    在这里插入图片描述

    或者

    在这里插入图片描述

  • 方式③示例: 在这里插入图片描述

    注:为了美观,建议换行(在properties文件中使用\换行):

    在这里插入图片描述

第四步:发布事件,触发监听

使用实现了ApplicationEventPublisher接口的类(常用ApplicationContext)的publishEvent(ApplicationEvent event)方法发布事件。

在这里插入图片描述

注:SpringBoot启动时,返回的ConfigurableApplicationContext是ApplicationContext的子类,所以如果想在SpringBoot启动后就立马发布事件的话,可以这样写:

在这里插入图片描述

验证测试

  • 按上述示例监听逻辑,编写示例:

    在这里插入图片描述

  • 运行main方法,启动SpringBoot:

    在这里插入图片描述

  • 控制台输出:

    在这里插入图片描述 SpringBoot中事件的发布与监听初步学习完毕!

同步监听与异步监听

同步监听

按上文中的配置,实际上默认是同步监听机制。所谓同步监听,即:业务逻辑与监听器的逻辑在同一个线程上、按顺序执行。

  • 举例说明一:         假设某线程α,线程β都有发布各自的事件,那么α线程发布的事件会被α线程进行监听器逻辑处理,β线程发布的事件会被β线程进行监听器逻辑处理。

  • 举例说明二:         假设某线程β要做的总体逻辑流程是,做A => 发事件x => 做B => 发事件y => 发事件z => 返回响应,那么同步监听下是这样的: 线程β先做A,(A做完后)接着做事件x对应的监听器逻辑,(x的监听器逻辑做完后,线程β才能)接着做B,(B做完后)接着做事件y对应的监听器逻辑,(y的监听器逻辑做完后,线程β才能)接着做事件z对应的监听器逻辑,最后才能返回响应。

异步监听

如果需要异步监听,那么需要开启异步功能(见下文示例),所谓异步监听即:业务逻辑与监听器的逻辑不在同一个线程上,处理监听器逻辑的事会被线程池中的某些线程异步并发着做。

  • 举例说明:         假设某线程β要做的总体逻辑流程是,做A => 发事件x => 做B => 发事件y => 发事件z => 返回响应,那么异步监听下是这样的:线程β先做A,(A做完后)发布事件x,(线程β不管x的监听器逻辑)紧接着做B,(B做完后,线程β)接着发布事件y,(线程β不管y的监听器逻辑)紧接着发布事件z,(线程β不管z的监听器逻辑)紧接着直接返回响应。而线程β发布的事件对应的各个监听器逻辑,会由线程池中的某些线程异步并发着做

开启异步功能:

  1. 第一步:@EnableAsync启用异步功能。

在这里插入图片描述

  1. 第二步:@Async指定异步方法。

在这里插入图片描述

  1. 第三步(可选): 自定义配置线程池执行器Executor(提示:配置Executor后,在使用@Async注解时,可以通过设置其属性来指定使用哪一个Executor)。

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * 自定义线程池Executor。
     *
     * 注: 我们常用的ExecutorService就继承自Executor。
     *
     * 注:关于线程池的各个参数的介绍、各个参数的关系,可详见<linked>https://blog.csdn.net/justry_deng/article/details/89331199</linked>
     *
     * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
     * @date 2019/11/25 11:05
     */
    @Configuration
    public class SyncExecutor {
    
        /** 核心线程数 */
        private static final int CORE_POOL_SIZE = 5;
    
        /** 最大线程数 */
        private static final int MAX_POOL_SIZE = 100;
    
        /** 阻塞队列容量 */
        private static final int QUEUE_CAPACITY = 20;
    
        @Bean
        public Executor myAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(CORE_POOL_SIZE);
            executor.setMaxPoolSize(MAX_POOL_SIZE);
            executor.setQueueCapacity(QUEUE_CAPACITY);
            executor.setThreadNamePrefix("JustryDeng-Executor-");
            // 设置,当任务满额时将新任务(如果有的话),打回到原线程去执行。
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.initialize();
            return executor;
        }
    
    }

    注:@Async指定异步方法时,就可以选择使用哪一个线程池Executor了,如:

    在这里插入图片描述

同步监听与异步监听的比较

在这里插入图片描述


SpringBoot中事件的发布与监听学习完毕!

相关资料

1
https://gitee.com/JustryDeng/notebook.git
git@gitee.com:JustryDeng/notebook.git
JustryDeng
notebook
notebook
master

搜索帮助