1.写在前面
前面我写过spring的源码的专栏,其中有一行代码,我没有解释,就是spring中事件的监听器,也是spring源码的中比较重要的一部分。所以笔者打算今天写一篇博客来絮叨絮叨。
2.什么是事件监听器?
事件监听器可以分成三个部分:事件、事件源、监听器。
事件:事件状态对象,也就是发生了什么事件。用于监听器的相应的方法之中,作为参数,一般存在与监听器的方法之中。(伴随着事件的发生,相应的状态通常都封装在事件状态对象中。事件状态对象作为单参传递给应响应该事件的监听者方法中。发出某种特定事件的事件源的标识是:遵从规定的设计格式为事件监听者定义注册方法,并接受对指定事件监听者接口实例的引用。)
事件源:具体的事件源,比如说,你点击一个按钮,那么按钮就是事件源,要想使按钮对某些事件进行响应,你就需要注册特定的监听器(事件就是事件源中的一个状态)。这些方法都集中定义在事件监听者接口中。
监听器:对每个明确的事件的发生,都相应地定义一个明确的Java方法
实例:如果博客可以作为一个事件源,那么修改,新增,删除都是事件,监听器是对这些事件进行监听,同时做出对应的响应。
3.一个简单的需求
假设现在公司让你开发一个文件操作帮助类,定义一个文件读写方法,读取某个文件,写到某个类里面去,但是有可能会有需要记录文件读取进度条的需求,有时候调用文件读取不需要进度条,有时候需要进度条 如何实现?废话不多说我们先写一个读写的工具的类,具体的代码如下:
package com.ys.util;import com.ys.event.AEvent;import com.ys.event.BEvent;import com.ys.event.FileUploadEvent;import com.ys.listener.AAListener;import com.ys.listener.AListener;import com.ys.listener.BListener;import com.ys.listener.FileUploadListener;import com.ys.manage.ListenerManage;import java.io.*;import java.util.Map;import java.util.Scanner;//假设现在公司让你开发一个文件操作帮助类//定义一个文件读写方法 读取某个文件 写到某个类里面去//但是 有可能会有需要记录文件读取进度条的需求//有时候调用文件读取不需要进度条//有时候需要进度条 如何实现?publicclassFileUtil{publicstaticint READ_SIZE=100;publicstaticvoidfileWrite(InputStream is, OutputStream os)throws Exception{
BufferedInputStream bis=newBufferedInputStream(is);
BufferedOutputStream bos=newBufferedOutputStream(os);//文件总大小int fileSize= is.available();//一共读取了多少int readSize=0;byte[] b=newbyte[READ_SIZE];boolean f=true;while(f){//文件实在小于第一次读的时候if(fileSize< READ_SIZE){byte[] bytes=newbyte[fileSize];
bis.read(bytes);
bos.write(bytes);
readSize= fileSize;
f=false;//当你是最后一次读的时候}elseif(fileSize< readSize+ READ_SIZE){byte[] bytes=newbyte[fileSize- readSize];
readSize= fileSize;
bis.read(bytes);
bos.write(bytes);
f=false;}else{
bis.read(b);
readSize+= READ_SIZE;
bos.write(b);}}
bis.close();
bos.close();}publicstaticvoidmain(String[] args)throws Exception{
File file=newFile("/Users/king/Downloads/写我.txt");if(!file.exists()){
file.createNewFile();}fileWrite(newFileInputStream("/Users/king/Downloads/读我.txt"),newFileOutputStream(file));}}
一个简单的文件的读写的工具类就完成了,我这儿测试一下,运行结果如下:
可以看到我们的测试结果是正确的,那么我们怎么实现我们的需求呢?既然是讲的是事件监听器,那么我们是不是可以给下载文件的是添加一个监听器,在事件监听器中获取下载的进度条,于是笔者创建了一个FileListener
接口,其中定义了获取进度条的方法。具体的代码如下:
package com.ys.util;publicinterfaceFileListener{voidupdateLoad(int fileSize,int readSize);}
再书写这个接口的实现类FileListenerImpl
,具体的代码如下:
package com.ys.util;publicclassFileListenerImplimplementsFileListener{@OverridepublicvoidupdateLoad(int fileSize,int readSize){double doubleFileSize= fileSize;double result= readSize/ doubleFileSize;
System.out.println("当前文件上传进度百分比:"+(result*100)+"%");}}
然后我们去修改FileUtil这个工具类,让其实现进度条的功能,由于这儿文件下载其他可能有用到,于是我们重载这个方法,具体的代码如下:
package com.ys.util;import com.ys.event.AEvent;import com.ys.event.BEvent;import com.ys.event.FileUploadEvent;import com.ys.listener.AAListener;import com.ys.listener.AListener;import com.ys.listener.BListener;import com.ys.listener.FileUploadListener;import com.ys.manage.ListenerManage;import java.io.*;import java.util.Map;import java.util.Scanner;//假设现在公司让你开发一个文件操作帮助类//定义一个文件读写方法 读取某个文件 写到某个类里面去//但是 有可能会有需要记录文件读取进度条的需求//有时候调用文件读取不需要进度条//有时候需要进度条 如何实现?publicclassFileUtil{publicstaticint READ_SIZE=100;publicstaticvoidfileWrite(InputStream is, ObuputStream os)throws Exception{fileWrite(is, os, null)}publicstaticvoidfileWrite(InputStream is, OutputStream os,FileListener listener)throws Exception{
BufferedInputStream bis=newBufferedInputStream(is);
BufferedOutputStream bos=newBufferedOutputStream(os);//文件总大小int fileSize= is.available();//一共读取了多少int readSize=0;byte[] b=newbyte[READ_SIZE];boolean f=true;while(f){//文件实在小于第一次读的时候if(fileSize< READ_SIZE){byte[] bytes=newbyte[fileSize];
bis.read(bytes);
bos.write(bytes);
readSize= fileSize;
f=false;//当你是最后一次读的时候}elseif(fileSize< readSize+ READ_SIZE){byte[] bytes=newbyte[fileSize- readSize];
readSize= fileSize;
bis.read(bytes);
bos.write(bytes);
f=false;}else{
bis.read(b);
readSize+= READ_SIZE;
bos.write(b);}if(fileListener!= null){
fileListener.updateLoad(fileSize, readSize);}}
bis.close();
bos.close();}publicstaticvoidmain(String[] args)throws Exception{
File file=newFile("/Users/king/Downloads/写我.txt");if(!file.exists()){
file.createNewFile();}fileWrite(newFileInputStream("/Users/king/Downloads/读我.txt"),newFileOutputStream(file),newFileListenerImpl());}}
运行结果如下:
可以发现我们就实现了简单的文件上传有进度条,同时如果需要进度条加上这个监听器就行了,如果不需要就直接将这个监听器删掉就行了。但是这儿实现的事件监听器,是不符合规范,下面就写一个事件监听器吧。
4.手写事件监听器
上面提到的事件监听器模式,其中的三大要素,我们先来实现事件监听器吧,首先我们先写事件监听器的接口,具体的代码如下:
package com.ys.listener;import com.ys.event.ApplicationEvent;publicinterfaceApplicationListener< EextendsApplicationEvent>{voidonEvent(E e);}
然后我们还是实现上面的需求,创建一个FileUploadListener
类,具体的代码如下:
package com.ys.listener;import com.ys.event.FileUploadEvent;publicclassFileUploadListenerimplementsApplicationListener<FileUploadEvent>{@OverridepublicvoidonEvent(FileUploadEvent event){double i1= event.getFileSize();double d= event.getReadSize()/i1;
System.out.println("当前文件上传进度百分比:"+d*100+"%");}}
既然有了事件的监听器,那么现在需要写的是事件,我们去写一个文件上传的事件,同时也要创建一个类ApplicationEvent
,具体的代码如下:
package com.ys.event;publicclassApplicationEvent{}
文件上传的事件类FileUploadEvent
,具体的代码如下:
package com.ys.event;publicclassFileUploadEventextendsApplicationEvent{privateint fileSize;privateint readSize;publicFileUploadEvent(int fileSize,int readSize){this.fileSize= fileSize;this.readSize= readSize;}publicintgetFileSize(){return fileSize;}publicvoidsetFileSize(int fileSize){this.fileSize= fileSize;}publicintgetReadSize(){return readSize;}publicvoidsetReadSize(int readSize){this.readSize= readSize;}}
这个时候我们还需要一个事件管理器,主要是对事件和监听器的一些的管理,具体的代码如下:
package com.ys.manage;import com.ys.event.ApplicationEvent;import com.ys.listener.ApplicationListener;import java.lang.reflect.ParameterizedType;import java.util.ArrayList;import java.util.List;//事件管理器publicclassListenerManage{//保存所有的监听器static List<ApplicationListener<?>> list=newArrayList<>();//添加监听器 //如果要做得优雅一点 可以考虑扫描项目//定义注解publicstaticvoidaddListener(ApplicationListener listener){
list.add(listener);}//判断一下有哪些人对这个事件感兴趣publicstaticvoidpushEvent(ApplicationEvent event){for(ApplicationListener applicationListener: list){//拿泛型
ClasstClass=(Class)((ParameterizedType) applicationListener.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];//判断一下泛型// tClass.isAssignableFrom()if(tClass.equals(event.getClass())){
applicationListener.onEvent(event);}}}}
这个时候再去实现下我们刚刚的需求,具体的代码如下:
package com.ys.util;import com.ys.event.FileUploadEvent;import com.ys.listener.FileUploadListener;import com.ys.manage.ListenerManage;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;publicclassNewFileUtil{publicstaticint READ_SIZE=100;publicstaticvoidfileWrite(InputStream is, OutputStream os)throws Exception{
BufferedInputStream bis=newBufferedInputStream(is);
BufferedOutputStream bos=newBufferedOutputStream(os);//文件总大小int fileSize= is.available();//一共读取了多少int readSize=0;byte[] b=newbyte[READ_SIZE];boolean f=true;while(f){//文件实在小于第一次读的时候if(fileSize< READ_SIZE){byte[] bytes=newbyte[fileSize];
bis.read(bytes);
bos.write(bytes);
readSize= fileSize;
f=false;//当你是最后一次读的时候}elseif(fileSize< readSize+ READ_SIZE){byte[] bytes=newbyte[fileSize- readSize];
readSize= fileSize;
bis.read(bytes);
bos.write(bytes);
f=false;}else{
bis.read(b);
readSize+= READ_SIZE;
bos.write(b);}//添加文件上传的事件
ListenerManage.pushEvent(newFileUploadEvent(fileSize, readSize));}
bis.close();
bos.close();}publicstaticvoidmain(String[] args)throws Exception{//添加文件上传的监听器
ListenerManage.addListener(newFileUploadListener());
File file=newFile("/Users/king/Downloads/写我.txt");if(!file.exists()){
file.createNewFile();}fileWrite(newFileInputStream("/Users/king/Downloads/读我.txt"),newFileOutputStream(file));}}
然后运行结果如下:
可以发现我们也实现上面的需求,这个实现比上面的实现要规范很多。相信通过这个例子,大家对事件管理器有了一定的认识。那么SpringBoot怎么使用的事件管理器呢?
5.SpringBoot中使用事件管理器
主要是两个方式,一种是基于Bean的,一种是基于注解的。
1.基于Bean的
废话不多说,直接上代码,pom文件如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
定义一个自定义事件,继承ApplicationEvent
类,具体的代码如下:
package com.example.springbootevent.event;import org.springframework.context.ApplicationEvent;publicclassMyApplicationEventextendsApplicationEvent{publicMyApplicationEvent(Object source){super(source);}}
定义一个事件监听器MyApplicationListener
实现ApplicationListener
接口,具体的代码如下:
package com.example.springbootevent.listener;import com.example.springbootevent.event.MyApplicationEvent;import org.springframework.context.ApplicationListener;import org.springframework.stereotype.Component;@ComponentpublicclassMyApplicationListenerimplementsApplicationListener<MyApplicationEvent>{@OverridepublicvoidonApplicationEvent(MyApplicationEvent event){
System.out.println("接收到事件:"+ event.getClass());}}
主类测试:
package com.example.springbootevent;import com.example.springbootevent.event.MyApplicationEvent;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplicationpublicclassSpringBootEventApplication{