Spring事件釋出與監聽

ckxllf發表於2019-12-12

  一、事件監聽相關概念介紹

  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再寫一個事件釋出與監聽的例子。 鄭州人流醫院

  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.呼叫監聽器方法

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2668233/,如需轉載,請註明出處,否則將追究法律責任。

相關文章