说说Spring事件发布机制

前言

在最近分析和写的SpringBoot源码分析(面试官:你说说Springboot的启动过程吧(5万字分析启动过程))中,给自己留了几个扩展内容,其中一个是Spring的事件机制,在分析源码的过程中,也是大量使用了事件机制,在我分析的这篇博客中,有不少地方都运用了事件发布机制,所以本文的目的是从SpringBoot中学习到事件的发布流程,并自己写一个事件发布用于以后得业务。

一、 使用到事件发布机制的源码

前言中提到的源码分析文章中SpringBoot源码分析中使用到的事件发布机制的小节有:

  • A.5、发布启动的事件并由应用程序启动监听器进行处理
  • A.6.4 监听环境准备
  • B.2.3 发布上下文准备的事件
  • B.2.4 发布引导程序关闭的事件
  • B.2.8 发布上下文加载事件并处理事件
  • “B.3.12、最后一步:发布相应的事件” 中的 “3)为生命周期处理器进行广播更新”
  • “B.3.12、最后一步:发布相应的事件” 中的 “4)发布最终的事件”
  • B.6、发布上下文开始的事件
  • B.8.2、发布失败的事件给监听器
  • B.9、发布准备就绪的事件

二、Springboot启动过程中用到的部分事件

事件Event 说明
ApplicationStartingEvent 一旦SpringApplication启动,事件就会尽早发布 - 在Environment或ApplicationContext可用之前,但在 ApplicationListener注册之后。事件的来源是SpringApplication本身,但要注意在这个早期阶段过多地使用其内部状态,因为它可能会在生命周期的后期被修改
ApplicationEnvironmentPreparedEvent 当SpringApplication启动时,且Environment首先可用于检查和修改
ApplicationContextInitializedEvent 当SpringApplication启动并准备ApplicationContext并且已调用 ApplicationContextInitializers 时,但在加载任何 Bean 定义之前,发布此事件
BootstrapContextClosedEvent 引导程序在关闭时发布此事件
ApplicationPreparedEvent 事件发布为SpringApplication启动时,并且ApplicationContext已完全准备好,但未刷新。Bean的定义将被加载,并且Enviroment已准备好在此阶段使用
ServletWebServerInitializedEvent 在WebServer准备好之后发布此事件,对于获取运行服务的本地端口是有用的
ContextStartedEvent 启动应用程序上下文时引发的事件。
ContextRefreshedEvent 当ApplicationContext已经初始完成或已经更新完成后发布此事件
ApplicationStartedEvent 一旦应用程序上下文刷新后,而ApplicationRunner和CommandLineRunner被调用之前就发布此事件
ApplicationFailedEvent 启动失败时由SpringApplication发布的事件
ApplicationReadyEvent 尽可能晚地发布事件,以指示应用程序已准备好为请求提供服务。事件源是SpringApplication本身,但请注意不要修改其内部状态,因为届时所有初始化步骤都将完成
ContextStoppedEvent 当容器停止时发布,即调用stop()方法, 即所有的Lifecycle bean都已显式接收到了stop信号 , 关闭的容器可以通过start()方法重启
ContextClosedEvent 当容器关闭时发布,即调用close方法, 关闭意味着所有的单例bean都已被销毁.关闭的容器不能被重启或refresh
SpringApplicationEvent 与SpringApplication相关的ApplicationEvent的基类

我看了一下以上这些事件源所在的包的分布:
类似Application***Event事件,以及BootstrapContextClosedEventServletWebServerInitializedEvent都是在Spring-boot包下的:
在这里插入图片描述
其中SpringApplicationEvent是这些Application***Event事件的父类,EventPublishingRunListener是集中了各个事件的发布的方法。
而类似Context***Event事件则是在spring-context包下的:
在这里插入图片描述
其中ApplicationContextEvent是类似Context***Event事件的父类。
SpringApplicationEventApplicationContextEvent又都继承了ApplicationEventEventObjectApplicationEvent的父类。

三、Springboot中的监听器

我们拿ApplicationStartingEvent事件来说,这个事件其实并不是只被一个监听器监听:
在这里插入图片描述
同时被RestartApplicationListenerLoggingApplicationListener监听:
RestartApplicationListener类实现了ApplicationListener<ApplicationEvent>重写了onApplicationEvent方法:
在这里插入图片描述
LoggingApplicationListener类实现了GenericApplicationListener,但这个GenericApplicationListener接口继承了SmartApplicationListener接口,而SmartApplicationListener接口又继承了ApplicationListener<ApplicationEvent>
在这里插入图片描述
LoggingApplicationListener类重写了onApplicationEvent方法:
在这里插入图片描述
可以看到Springboot中的监听器进行了多事件源的监听,在根据instanceof关键字进行判断,对不同的事件源进行处理。
我们再看一个事件:ApplicationEnvironmentPreparedEvent:
在这里插入图片描述
这个事件被7个监听器进行监听和分别处理,这些监听器毫无疑问也都间接或直接实现或继承了ApplicationListener<ApplicationEnvironmentPreparedEvent>

这里罗列一下部分事件对应的监听器:

事件Event 事件的监听器
ApplicationStartingEvent RestartApplicationListener、LoggingApplicationListener
ApplicationEnvironmentPreparedEvent RemoteUrlPropertyExtractor、SpringBootContextLoader、FileEncodingApplicationListener、AnsiOutputApplicationListener、BackgroundPreinitializer
ApplicationContextInitializedEvent -
BootstrapContextClosedEvent -
ApplicationPreparedEvent RestartApplicationListener、LoggingApplicationListener
ServletWebServerInitializedEvent -
ContextStartedEvent -
ContextRefreshedEvent ClearCachesApplicationListener、ScheduledAnnotationBeanPostProcessor、SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean、 ParentContextApplicationContextInitializer.EventPublisher
ApplicationStartedEvent -
ApplicationFailedEvent RestartApplicationListener、BackgroundPreinitializer
ApplicationReadyEvent RestartApplicationListener、BackgroundPreinitializer、ConditionEvaluationDeltaLoggingListener、SpringApplicationAdminMXBeanRegistrar
ContextStoppedEvent -
ContextClosedEvent SpringApplicationShutdownHook.ApplicationContextClosedListener 、ParentContextCloserApplicationListener.ContextCloserListener、LoggingApplicationListener
SpringApplicationEvent BackgroundPreinitializer、ApplicationPidFileWriter

有些事件我没有找到对应的监听器,这个我在分析源码的时候也发现了,目前我还没有找到答案,知道答案的大佬还望不吝赐教哦~

四、自定义事件源,事件监听器和事件发布器

结合Springboot中的事件和监听器的关系,其实我们在自己的实现中也可以一个事件被多个监听器监听,只是我们在业务实现中一搬都是一对一的关系。

4.1 目录结构

在这里插入图片描述

自定义事件发布机制说明:

  • 这个事件发布还是基于SpringBoot源码分析(面试官:你说说Springboot的启动过程吧(5万字分析启动过程))文章中的项目来写的。
  • 关于事件发布的内容我都写在了event包下,包括事件监听器(listener包),事件源(source包)和事件发布帮助器(helper包)。
  • controller和service和util包就是模拟了我们日常开发中常用三层分包模式,当然我这里没有涉及到dao层,我这里的事件就是源码分析的文章中最后给自己留家庭作业的事件,对应的监听器也是监听的家庭作业的监听器。
  • 下面的代码我都是截图的,为了防止正在看的你直接复制而不是自己亲自动手敲一遍,毕竟眼过千遍不如手过一遍嘛。

4.2 事件源

在这里插入图片描述

4.3 事件监听器

在这里插入图片描述

4.4 事件帮助器

4.4.1 事件发布帮助接口

在这里插入图片描述

4.4.2 事件发布帮助接口实现类

在这里插入图片描述

4.4.3 抽象的事件监听器

HomeworkListener继承了这个抽象的事件监听器
在这里插入图片描述

4.4.4 事件发布入口的Controller和Service

HomeworkController:
在这里插入图片描述
HomeworkService:
在这里插入图片描述

4.4.5 获取applicationContext对象工具类

在这里插入图片描述

4.4.5 启动Springboot后发送请求

直接用postman或者ApiFox调用Post接口:http://localhost:8081/homework/show
就可以看到控制台打印的日志了。

在这里插入图片描述
这里就是HomeworkServiceHomeworkEvent的事件内容被HomeworkListener接收到之后打印的信息日志了。
上午不知道咋操作的,想要重新启动Springboot发现端口总是被占用,哪怕我改了application.properties文件中的端口,改啥端口,啥端口就被占用,后面用下面的命令找到了被占用的端口,把这个端口杀掉就行。

sudo lsof -i tcp:8081

从列表中找到PID对应的进程id,执行kill pid就可以杀死这个该死的占用的进程。

五、事件发布机制原理

5.1 自己对事件发布机制的理解

看着上面控制台日志和我们直接在service中打印的一样,没有啥区别,但实际上这只是个例子,如果像Springboot中一样,有很多的事件,如果不采用事件发布机制,那发布事件之前的代码逻辑处理就很多,就使得原本复杂的逻辑更复杂了。
事件发布机制有点像我们列的待处理的工作清单一样,清单列好,再一件件的去完成,这样更加清晰明了,或者更专业的点的名称叫「解耦」,将业务和业务进行解耦。

5.2 事件发布机制源码分析

其实在SpringBoot源码分析(面试官:你说说Springboot的启动过程吧(5万字分析启动过程))中的A.5、发布启动的事件并由应用程序启动监听器进行处理B.6、发布上下文开始的事件都多多少少有提到过

这里结合我们自己的例子对源码进行分析,启动Springboot之后,发送Post请求,通过debug模式进入EventPublishHelperImpl中的applicationContext.publishEvent(event);的方法中一步步进入到AbstractApplicationContext中的publishEvent方法:
在这里插入图片描述
这个方法里面有3个if块,第一个if块必然走的是if分支,即HomeworkEventApplicationEvent的实例,第二个if块就是上图中断点的蓝色阴影的这行,因为earlyApplicationEvents是null。
而此时的断点中这行其实是先获取多播器,既然有获取,必然有设置,设置的方法我在源码分析的文章中的B.3.8、为此上下文初始化事件广播分析过initApplicationEventMulticaster方法,其实就是返回了默认的SimpleApplicationEventMulticaster进行广播处理。
我们进入到multicastEvent方法中,似乎看到了熟悉的方法,就是在A.5、发布启动的事件并由应用程序启动监听器进行处理也提到过。
在这里插入图片描述
我们通过分析器看到getApplicationListeners(event, type)里面有三个监听器,其中就包括我们定义的HomeworkListener,接着进入到了SimpleApplicationEventMulticaster.invokeListener方法中:
在这里插入图片描述
再进入到doInvokeListener方法中:
在这里插入图片描述
try-catch块中间的listener.onApplicationEvent(event);的listener就是我们的自定义的HomeworkListener监听器,就顺利成章的先进入到这个类的父类AbstractEventListener里,先打印了我们的接收的日志,再进入this.executeEvent(event);方法,此方法是个抽象方法,就进入了实现类HomeworkListener重写的此方法中了:
在这里插入图片描述
就执行了HomeworkListener类里的this.executeEvent方法了,所以分析下来其实并不难,但这种思想的来源却是需要多年的积累才能够进行提炼和实现的,这种思想其实就是设计模式中的观察者模式。感谢这些大佬的贡献,我们才能站在巨人的肩膀上走的更快。。。

---------------------你知道的越多,不知道的越多----------------------

猜你喜欢

转载自blog.csdn.net/fhf2424045058/article/details/128318452