事件的发布与监听从属于观察者模式;和MQ相比,事件的发布与监听偏向于处理“体系内”的某些逻辑。事件的发布与监听总体分为以下几个步骤:
步骤 | 相关事宜 |
---|---|
1 | 定义事件 |
2 | 定义(用于处理某种事件的)监听器 |
3 | 注册监听器 |
4 | 发布事件(,监听到了该事件的监听器自动进行相关逻辑处理) |
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 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 + "】"
);
}
}
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 + "】"
);
}
}
所谓注册监听器,其实质就是将监听器进行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对应的监听器逻辑,最后才能返回响应。
如果需要异步监听,那么需要开启异步功能(见下文示例),所谓异步监听即:业务逻辑与监听器的逻辑不在同一个线程上,处理监听器逻辑的事会被线程池中的某些线程异步并发着做。
开启异步功能:
第三步(可选): 自定义配置线程池执行器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中事件的发布与监听学习完毕!
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。