Spring事件发布与监听怎么实现
这篇文章主要讲解了"Spring事件发布与监听怎么实现",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Spring事件发布与监听怎么实现"吧!
一、事件监听相关概念介绍
1、流程分析
事件:做了什么事。例如,我在写博客,写博客就是一个事件。
监听器:监听发生事件的组件。例如,我们日常生活中的火灾报警器,监听有没有发生火灾事件。
在一个完整的事件体系中,除了事件和监听器以外,还应该有3个概念;
1. 事件源:事件的产生者,任何一个event都必须有一个事件源;
2. 事件广播器:它是事件和事件监听器之间的桥梁,负责把事件通知给事件监听器;
3. 事件监听器注册表:就是spring框架为所有的监听器提供了一个存放的地方;
通过流程图,可以看出它们是如何各司其职的,如下:
其实通过流程图,我们很容易发现事件体系就是观察者模式的具体实现,它并没有任何的神秘之处。
2、流程分析
结构分析:
1. 事件类(ApplicaitonEvent):目前spring框架本身仅仅提供了几个事件,很多的事件都是需要自定义的。
ApplicationEvent唯一的构造函数是ApplicaitonEvent(Object source),通过source指定事件源。 它有两个子类;
(1)ApplicationContextEvent:容器事件,也就是说事件源是ApplicationContext,框架提供了四个子类,分别代表容器启动,刷新,停止和关闭事件。
(2)RequestHandleEvent:这是一个与Web应用相关的事件,当一个请求被处理后,才会产生该事件。
一般来说,我们都是扩展ApplicationEvent来自定义事件。下面会有栗子。
2. 事件监听器接口(ApplicationListener)
所有的监听器都需要实现该接口,该接口只定义了一个方法:onApplicaitonEvent (E event),该方法接收事件对象,在该方法中编写事件的响应处理逻辑。
二、手写模拟事件发布与监听
注:想直接了解Spring事件监听与发布的,可以跳过这节,但是我建议你还是看一下。
需求:
假设现在公司让你开发一个文件操作帮助类 ,
定义一个文件读写方法 读写某个文件 写到某个类里面去 //但是 有时候可能会需要记录文件读取进度条的需求
有时候需要进度条 如何实现?
答案:我们可以采用事件发布与监听。
事件:文件上传
事件源:事件在哪里发布的,比如说我们在A类中,发布了事件。那么A类的对象就是事件源。
监听器:我们编写的FileUploadListener对这个事件进行了监听。并在监听到了当前事件之后,发布事件。
代码编写:
/**
* @ClassName ApplicationEvent
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/9 20:29
*/
public class ApplicationEvent {
}
/**
* @ClassName ApplicationListener
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/9 20:29
*/
public interface ApplicationListener {
void onEvent(E e);
}
/**
* @ClassName ListenerManage
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/9 20:44
*/
//事件管理器
public class ListenerManage {
//保存所有的监听器
static List> list = new ArrayList<>();
//添加监听器 注:如果要做的更加优雅,应该做成扫描全局,通过扫描将所有的监听器放入管理器的容器列表,这里为了方便演示就不做复杂了。
//springboot是从spring的BeanFactory中获取listener
public static void addListener(ApplicationListener listener) {
list.add(listener);
}
//判断一下 有哪些监听器 监听了这个事件
public static void publishEvent(ApplicationEvent event) {
for (ApplicationListener applicationListener : list) {
//获取ApplicationListener的泛型
Class typeParameter = (Class) ((ParameterizedType) applicationListener.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
if (typeParameter.equals(event.getClass())) {
applicationListener.onEvent(event);
}
}
}
}
/**
* @ClassName FileUploadEvent
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/9 21:37
*/
public class FileUploadEvent extends ApplicationEvent {
private int fileSize;
private int readSize;
public FileUploadEvent(int fileSize, int readSize) {
this.fileSize = fileSize;
this.readSize = readSize;
}
public int getFileSize() {
return fileSize;
}
public void setFileSize(int fileSize) {
this.fileSize = fileSize;
}
public int getReadSize() {
return readSize;
}
public void setReadSize(int readSize) {
this.readSize = readSize;
}
}
/**
* @ClassName FileUploadListener
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/9 21:38
*/
public class FileUploadListener implements ApplicationListener {
@Override
public void onEvent(FileUploadEvent fileUploadEvent) {
double molecule = fileUploadEvent.getFileSize();
double denominator = fileUploadEvent.getReadSize();
System.out.println("当前文件上传进度百分比:" + (denominator / molecule * 100 + "%"));
}
}
/**
* @ClassName FileUtil
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/9 17:06
*/
public class FileUtil {
public static int READ_SIZE = 100;
public static void fileWrite(InputStream is, OutputStream os) throws Exception {
fileWrite(is, os, null);
}
public static void fileWrite(InputStream is, OutputStream os, FileListener fileListener) throws Exception {
BufferedInputStream bis = new BufferedInputStream(is);
BufferedOutputStream bos = new BufferedOutputStream(os);
/**
* 如果是网络请求最好不要用这个方法拿fileSize,因为这个方法会产生阻塞。最好传一个File对象进来。
* 这里作为演示,就不去处理细节了。
*/
//文件总大小
int fileSize = is.available();
//一共读取了多少
int readSize = 0;
byte[] readedBytes = new byte[READ_SIZE];
//控制是否退出
boolean exit = true;
while (exit) {
//文件小于第一次读的大小的时候
if (fileSize < READ_SIZE) {
byte[] fileBytes = new byte[fileSize];
//将缓冲区中的数据写入到字节数组fileBytes中
bis.read(fileBytes);
//向文件写入fileBytes数组的内容
bos.write(fileBytes);
readSize = fileSize;
exit = false;
//当你是最后一次读的时候
} else if (fileSize < readSize + READ_SIZE) {
byte[] bytes = new byte[fileSize - readSize];
readSize = fileSize;
bis.read(bytes);
bos.write(bytes);
exit = false;
} else {
bis.read(readedBytes);
readSize += READ_SIZE;
bos.write(readedBytes);
}
//发布事件
ListenerManage.publishEvent(new FileUploadEvent(fileSize, readSize));
if (fileListener != null) {
fileListener.updateLoad(fileSize, readSize);
}
}
bis.close();
bos.close();
}
}
/**
* @ClassName FileReadTest
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/9 18:26
*/
public class FileReadTest {
public static void main(String[] args) throws Exception {
ListenerManage.addListener(new FileUploadListener());
//这里根据实际情况去设置读写的文件
File file = new File("F:\\测试写出.txt");
if (!file.exists()) {
file.createNewFile();
}
//如果需要做进度条功能,再添加一个fileListener参数
fileWrite(new FileInputStream(new File("F:\\明天要做的事.txt")), new FileOutputStream(file));
}
}
运行结果:
当前文件上传进度百分比:14.245014245014245%
当前文件上传进度百分比:28.49002849002849%
当前文件上传进度百分比:42.73504273504273%
当前文件上传进度百分比:56.98005698005698%
当前文件上传进度百分比:71.22507122507122%
当前文件上传进度百分比:85.47008547008546%
当前文件上传进度百分比:99.71509971509973%
当前文件上传进度百分比:100.0%
三、Spring的时间发布与监听
我们在上面手动模拟了Spring的时间发布与监听后,看如果上面的例子后,我们使用Spring再写一个事件发布与监听的例子。郑州人流医院 http://rl.zyfuke.com/
package com.evan.spring.config;
import org.springframework.context.annotation.ComponentScan;
/**
* @ClassName Appconfig
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/10 16:04
*/
@ComponentScan("com")
public class AppConfig {
}
package com.evan.spring.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextStartedEvent;
/**
* @ClassName MyEvent
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/10 15:39
*/
public class WriteBlogEvent extends ApplicationContextEvent {
String name;
String address;
public WriteBlogEvent(ApplicationContext source, String name, String address) {
super(source);
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
Spring的事件监听可以基于注解或实现接口。对于同一个事件,如果两个都存在,相当于多个监听器监听一个事件。
两个监听器内的方法都会执行。
package com.evan.spring.listener;
import com.evan.spring.event.WriteBlogEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* @ClassName WriteBlogListener
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/10 15:47
*/
@Component
public class WriteBlogListener implements ApplicationListener {
@Override
public void onApplicationEvent(WriteBlogEvent writeBlogEvent) {
String name = writeBlogEvent.getName();
String address = writeBlogEvent.getAddress();
System.out.println("基于实现接口:" + name + "在" + address + "写了一篇博客");
}
}
package com.evan.spring.listener;
import com.evan.spring.event.WriteBlogEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* @ClassName WriteBlogListenerAnnotation
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/10 16:30
*/
@Component
public class WriteBlogListenerAnnotation {
@EventListener
public void annotationListen(WriteBlogEvent writeBlogEvent) {
String name = writeBlogEvent.getName();
String address = writeBlogEvent.getAddress();
System.out.println("基于注解:" + name + "在" + address + "写了一篇博客");
}
}
package com.evan.spring.test;
import com.evan.spring.config.AppConfig;
import com.evan.spring.event.WriteBlogEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @ClassName EventTest
* @Description
* @Author EvanWang
* @Version 1.0.0
* @Date 2019/12/10 15:56
*/
public class EventTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
WriteBlogEvent writeBlogEvent = new WriteBlogEvent(ac, "Evan", "家里");
ac.publishEvent(writeBlogEvent);
}
}
运行结果:
基于注解:Evan在家里写了一篇博客
基于实现接口:Evan在家里写了一篇博客
四、总结
1、spring 如何得知有哪些监听器?
通过2个步骤:1.从Bean工厂拿到所有ApplicationListener类型的Bean.
2.扫描所有带@EventListener
2、spring如何发布事件?
大逻辑上通过2个步骤: 1.判断是否有监听器对该事件感兴趣
2.调用监听器方法
感谢各位的阅读,以上就是"Spring事件发布与监听怎么实现"的内容了,经过本文的学习后,相信大家对Spring事件发布与监听怎么实现这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!