深入理解SpringBoot中的事件监听器

2022-07-24 09:28:51

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{
  • 作者:了不起的盖茨比。
  • 原文链接:https://blog.csdn.net/qq_36434742/article/details/116229863
    更新时间:2022-07-24 09:28:51