Spring事件发布与监听

灰太狼 2023-10-08 10:33 97阅读 0赞

最近算是把spring整体的又过了一遍,发现很多东西虽然用的多,但是有些思想理解的不够透彻,在此记录下,

顺便感叹下,spring源码看了大部分,这才没过多久又忘了 TnT 。

一、事件监听相关概念介绍

1、流程分析

事件:做了什么事。例如,我在写博客,写博客就是一个事件。

监听器:监听发生事件的组件。例如,我们日常生活中的火灾报警器,监听有没有发生火灾事件。

在一个完整的事件体系中,除了事件监听器以外,还应该有3个概念;

  1. 事件源:事件的产生者,任何一个event都必须有一个事件源;

  2. 事件广播器:它是事件和事件监听器之间的桥梁,负责把事件通知给事件监听器;

  3. 事件监听器注册表:就是spring框架为所有的监听器提供了一个存放的地方;

通过流程图,可以看出它们是如何各司其职的,如下:

format_png

其实通过流程图,我们很容易发现事件体系就是观察者模式的具体实现,它并没有任何的神秘之处。

2、结构分析

结构分析:

1. 事件类(ApplicaitonEvent):目前spring框架本身仅仅提供了几个事件,很多的事件都是需要自定义的。

 ApplicationEvent唯一的构造函数是ApplicaitonEvent(Object source),通过source指定事件源。 它有两个子类;

(1)ApplicationContextEvent:容器事件,也就是说事件源是ApplicationContext,框架提供了四个子类,分别代表容器启动,刷新,停止和关闭事件。

(2)RequestHandleEvent:这是一个与Web应用相关的事件,当一个请求被处理后,才会产生该事件。

一般来说,我们都是扩展ApplicationEvent来自定义事件。下面会有栗子。

format_png 1

2. 事件监听器接口(ApplicationListener)

所有的监听器都需要实现该接口,该接口只定义了一个方法:onApplicaitonEvent (E event),该方法接收事件对象,在该方法中编写事件的响应处理逻辑。

format_png 2

二、手写模拟事件发布与监听

注:想直接了解Spring事件监听与发布的,可以跳过这节,但是我建议你还是看一下。

需求:

假设现在公司让你开发一个文件操作帮助类 ,

定义一个文件读写方法 读写某个文件 写到某个类里面去 //但是 有时候可能会需要记录文件读取进度条的需求

有时候需要进度条 如何实现?

答案:我们可以采用事件发布与监听。

事件:文件上传

事件源:事件在哪里发布的,比如说我们在A类中,发布了事件。那么A类的对象就是事件源。

监听器:我们编写的FileUploadListener对这个事件进行了监听。并在监听到了当前事件之后,发布事件。

代码编写:

  1. /**
  2. * @ClassName ApplicationEvent
  3. * @Description
  4. * @Author EvanWang
  5. * @Version 1.0.0
  6. * @Date 2019/12/9 20:29
  7. */
  8. public class ApplicationEvent {
  9. }
  10. /**
  11. * @ClassName ApplicationListener
  12. * @Description
  13. * @Author EvanWang
  14. * @Version 1.0.0
  15. * @Date 2019/12/9 20:29
  16. */
  17. public interface ApplicationListener<E extends ApplicationEvent> {
  18. void onEvent(E e);
  19. }
  20. /**
  21. * @ClassName ListenerManage
  22. * @Description
  23. * @Author EvanWang
  24. * @Version 1.0.0
  25. * @Date 2019/12/9 20:44
  26. */
  27. //事件管理器
  28. public class ListenerManage {
  29. //保存所有的监听器
  30. static List<ApplicationListener<ApplicationEvent>> list = new ArrayList<>();
  31. //添加监听器 注:如果要做的更加优雅,应该做成扫描全局,通过扫描将所有的监听器放入管理器的容器列表,这里为了方便演示就不做复杂了。
  32. //springboot是从spring的BeanFactory中获取listener
  33. public static void addListener(ApplicationListener listener) {
  34. list.add(listener);
  35. }
  36. //判断一下 有哪些监听器 监听了这个事件
  37. public static void publishEvent(ApplicationEvent event) {
  38. for (ApplicationListener<ApplicationEvent> applicationListener : list) {
  39. //获取ApplicationListener的泛型
  40. Class typeParameter = (Class) ((ParameterizedType) applicationListener.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
  41. if (typeParameter.equals(event.getClass())) {
  42. applicationListener.onEvent(event);
  43. }
  44. }
  45. }
  46. }
  47. /**
  48. * @ClassName FileUploadEvent
  49. * @Description
  50. * @Author EvanWang
  51. * @Version 1.0.0
  52. * @Date 2019/12/9 21:37
  53. */
  54. public class FileUploadEvent extends ApplicationEvent {
  55. private int fileSize;
  56. private int readSize;
  57. public FileUploadEvent(int fileSize, int readSize) {
  58. this.fileSize = fileSize;
  59. this.readSize = readSize;
  60. }
  61. public int getFileSize() {
  62. return fileSize;
  63. }
  64. public void setFileSize(int fileSize) {
  65. this.fileSize = fileSize;
  66. }
  67. public int getReadSize() {
  68. return readSize;
  69. }
  70. public void setReadSize(int readSize) {
  71. this.readSize = readSize;
  72. }
  73. }
  74. /**
  75. * @ClassName FileUploadListener
  76. * @Description
  77. * @Author EvanWang
  78. * @Version 1.0.0
  79. * @Date 2019/12/9 21:38
  80. */
  81. public class FileUploadListener implements ApplicationListener<FileUploadEvent> {
  82. @Override
  83. public void onEvent(FileUploadEvent fileUploadEvent) {
  84. double molecule = fileUploadEvent.getFileSize();
  85. double denominator = fileUploadEvent.getReadSize();
  86. System.out.println("当前文件上传进度百分比:" + (denominator / molecule * 100 + "%"));
  87. }
  88. }
  89. /**
  90. * @ClassName FileUtil
  91. * @Description
  92. * @Author EvanWang
  93. * @Version 1.0.0
  94. * @Date 2019/12/9 17:06
  95. */
  96. public class FileUtil {
  97. public static int READ_SIZE = 100;
  98. public static void fileWrite(InputStream is, OutputStream os) throws Exception {
  99. fileWrite(is, os, null);
  100. }
  101. public static void fileWrite(InputStream is, OutputStream os, FileListener fileListener) throws Exception {
  102. BufferedInputStream bis = new BufferedInputStream(is);
  103. BufferedOutputStream bos = new BufferedOutputStream(os);
  104. /**
  105. * 如果是网络请求最好不要用这个方法拿fileSize,因为这个方法会产生阻塞。最好传一个File对象进来。
  106. * 这里作为演示,就不去处理细节了。
  107. */
  108. //文件总大小
  109. int fileSize = is.available();
  110. //一共读取了多少
  111. int readSize = 0;
  112. byte[] readedBytes = new byte[READ_SIZE];
  113. //控制是否退出
  114. boolean exit = true;
  115. while (exit) {
  116. //文件小于第一次读的大小的时候
  117. if (fileSize < READ_SIZE) {
  118. byte[] fileBytes = new byte[fileSize];
  119. //将缓冲区中的数据写入到字节数组fileBytes中
  120. bis.read(fileBytes);
  121. //向文件写入fileBytes数组的内容
  122. bos.write(fileBytes);
  123. readSize = fileSize;
  124. exit = false;
  125. //当你是最后一次读的时候
  126. } else if (fileSize < readSize + READ_SIZE) {
  127. byte[] bytes = new byte[fileSize - readSize];
  128. readSize = fileSize;
  129. bis.read(bytes);
  130. bos.write(bytes);
  131. exit = false;
  132. } else {
  133. bis.read(readedBytes);
  134. readSize += READ_SIZE;
  135. bos.write(readedBytes);
  136. }
  137. //发布事件
  138. ListenerManage.publishEvent(new FileUploadEvent(fileSize, readSize));
  139. if (fileListener != null) {
  140. fileListener.updateLoad(fileSize, readSize);
  141. }
  142. }
  143. bis.close();
  144. bos.close();
  145. }
  146. }
  147. /**
  148. * @ClassName FileReadTest
  149. * @Description
  150. * @Author EvanWang
  151. * @Version 1.0.0
  152. * @Date 2019/12/9 18:26
  153. */
  154. public class FileReadTest {
  155. public static void main(String[] args) throws Exception {
  156. ListenerManage.addListener(new FileUploadListener());
  157. //这里根据实际情况去设置读写的文件
  158. File file = new File("F:\\测试写出.txt");
  159. if (!file.exists()) {
  160. file.createNewFile();
  161. }
  162. //如果需要做进度条功能,再添加一个fileListener参数
  163. fileWrite(new FileInputStream(new File("F:\\明天要做的事.txt")), new FileOutputStream(file));
  164. }
  165. }

运行结果:

  1. 当前文件上传进度百分比:14.245014245014245%
  2. 当前文件上传进度百分比:28.49002849002849%
  3. 当前文件上传进度百分比:42.73504273504273%
  4. 当前文件上传进度百分比:56.98005698005698%
  5. 当前文件上传进度百分比:71.22507122507122%
  6. 当前文件上传进度百分比:85.47008547008546%
  7. 当前文件上传进度百分比:99.71509971509973%
  8. 当前文件上传进度百分比:100.0%

三、Spring的事件发布与监听

我们在上面手动模拟了Spring的事件发布与监听,如果看懂了上面的例子,我们再使用Spring写一个事件发布与监听的例子。

  1. package com.evan.spring.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. /**
  4. * @ClassName Appconfig
  5. * @Description
  6. * @Author EvanWang
  7. * @Version 1.0.0
  8. * @Date 2019/12/10 16:04
  9. */
  10. @ComponentScan("com")
  11. public class AppConfig {
  12. }
  13. package com.evan.spring.event;
  14. import org.springframework.context.ApplicationContext;
  15. import org.springframework.context.event.ApplicationContextEvent;
  16. import org.springframework.context.event.ContextStartedEvent;
  17. /**
  18. * @ClassName MyEvent
  19. * @Description
  20. * @Author EvanWang
  21. * @Version 1.0.0
  22. * @Date 2019/12/10 15:39
  23. */
  24. public class WriteBlogEvent extends ApplicationContextEvent {
  25. String name;
  26. String address;
  27. public WriteBlogEvent(ApplicationContext source, String name, String address) {
  28. super(source);
  29. this.name = name;
  30. this.address = address;
  31. }
  32. public String getName() {
  33. return name;
  34. }
  35. public String getAddress() {
  36. return address;
  37. }
  38. }

Spring的事件监听可以基于注解或实现接口。对于同一个事件,如果两个都存在,相当于多个监听器监听一个事件。

两个监听器内的方法都会执行。

  1. package com.evan.spring.listener;
  2. import com.evan.spring.event.WriteBlogEvent;
  3. import org.springframework.context.ApplicationListener;
  4. import org.springframework.stereotype.Component;
  5. /**
  6. * @ClassName WriteBlogListener
  7. * @Description
  8. * @Author EvanWang
  9. * @Version 1.0.0
  10. * @Date 2019/12/10 15:47
  11. */
  12. @Component
  13. public class WriteBlogListener implements ApplicationListener<WriteBlogEvent> {
  14. @Override
  15. public void onApplicationEvent(WriteBlogEvent writeBlogEvent) {
  16. String name = writeBlogEvent.getName();
  17. String address = writeBlogEvent.getAddress();
  18. System.out.println("基于实现接口:" + name + "在" + address + "写了一篇博客");
  19. }
  20. }
  21. package com.evan.spring.listener;
  22. import com.evan.spring.event.WriteBlogEvent;
  23. import org.springframework.context.event.EventListener;
  24. import org.springframework.stereotype.Component;
  25. /**
  26. * @ClassName WriteBlogListenerAnnotation
  27. * @Description
  28. * @Author EvanWang
  29. * @Version 1.0.0
  30. * @Date 2019/12/10 16:30
  31. */
  32. @Component
  33. public class WriteBlogListenerAnnotation {
  34. @EventListener
  35. public void annotationListen(WriteBlogEvent writeBlogEvent) {
  36. String name = writeBlogEvent.getName();
  37. String address = writeBlogEvent.getAddress();
  38. System.out.println("基于注解:" + name + "在" + address + "写了一篇博客");
  39. }
  40. }
  41. package com.evan.spring.test;
  42. import com.evan.spring.config.AppConfig;
  43. import com.evan.spring.event.WriteBlogEvent;
  44. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  45. /**
  46. * @ClassName EventTest
  47. * @Description
  48. * @Author EvanWang
  49. * @Version 1.0.0
  50. * @Date 2019/12/10 15:56
  51. */
  52. public class EventTest {
  53. public static void main(String[] args) {
  54. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
  55. WriteBlogEvent writeBlogEvent = new WriteBlogEvent(ac, "Evan", "家里");
  56. ac.publishEvent(writeBlogEvent);
  57. }
  58. }

运行结果:

  1. 基于注解:Evan在家里写了一篇博客
  2. 基于实现接口:Evan在家里写了一篇博客

四、总结

  1. 1spring 如何得知有哪些监听器?
  2. 通过2个步骤:1.Bean工厂拿到所有ApplicationListener类型的Bean.
  3. 2.扫描所有带@EventListener
  4. 2spring如何发布事件?
  5. 大逻辑上通过2个步骤: 1.判断是否有监听器对该事件感兴趣
  6. 2.调用监听器方法

文章还有很多不足,欢迎抱着友善交流的态度的朋友提出建议,如果这篇文章帮到了您,麻烦点个赞 : )

如果有什么问题不明白,可以直接留言,或者加入技术交流群,我都会及时回复。

发表评论

表情:
评论列表 (有 0 条评论,97人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring事件监听机制

    前言 Spring中的事件机制其实就是设计模式中的观察者模式,主要由以下角色构成: 1. 事件 2. 事件监听器(监听并处理事件) 3. 事件发布者(发布事件...

    相关 Spring事件发布监听

    > 最近算是把spring整体的又过了一遍,发现很多东西虽然用的多,但是有些思想理解的不够透彻,在此记录下, > > 顺便感叹下,spring源码看了大部分,这才没过多久又忘