在实际开发中,业务代码与辅助代码的解耦是一个热点话题,如:通过AOP记录入参出参、使用事件监听记录错误信息等是一个不错的选择。
概述:
事件的发布与监听从属于观察者模式;和MQ相比,事件的发布与监听偏向于处理“体系内”的某些逻辑。事件的发布与监听总体分为以下几个步骤:
步骤 | 相关事宜 |
---|---|
1 | 定义事件 |
2 | 定义(用于处理某种事件的)监听器 |
3 | 注册监听器 |
4 | 发布事件(,监听到了该事件的监听器自动进行相关逻辑处理) |
详细(示例)说明:
第一步:通过继承ApplicationEvent来自定义事件。
import org.springframework.context.ApplicationEvent;
/**
* 自定义事件
*
* 注:继承ApplicationEvent即可。
*
* @author JustryDeng
* @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为此监听器要监听的事件。
注:需要重写onApplicationEvent方法来自定义相关事件的处理逻辑。/** * 自定义监听器 * * 注:实现ApplicationListener<E extends ApplicationEvent>即可, * 其中E为此监听器要监听的事件。 * * @author JustryDeng * @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 + "】" ); } }
- 方式二: 在某个方法上使用@EventListener注解即可。
注:在某个方法上使用@EventListener注解即可。import org.springframework.context.event.EventListener; /** * 自定义监听器 * * 注:在某个方法上使用@EventListener注解即可。 * 追注一: 这个方法必须满足: 最多能有一个参数。 * 追注二: 若只是监听一种事件,那么这个方法的参数类型应为该事 * 件对象类;P.S.:该事件的子类事件,也属于该事件,也会被监听到。 * 若要监听多种事件,那么可以通过@EventListener注解 * 的classes属性指定多个事件,且保证这个方法无参; * * * * @author JustryDeng * @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注解方法必须满足: 最多能有一个参数。
注:若只是监听一种事件,那么这个方法的参数类型应为该事件对象类;
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的监听器逻辑)紧接着直接返回响应。而线程β发布的事件对应的各个监听器逻辑,会由线程池中的某些线程异步并发着做。
开启异步功能:
- 第一步:@EnableAsync启用异步功能。
- 第二步:@Async指定异步方法。
- 第三步(可选): 自定义配置线程池执行器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 JustryDeng * @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中事件的发布与监听学习完毕!
^_^ 如有不当之处,欢迎指正
^_^ 测试代码托管链接
https://github.com/JustryDeng…Abc_EventListener_Demo
^_^ 本文已经被收录进《程序员成长笔记》 ,笔者JustryDeng