在最近分析和写的SpringBoot源码分析(面试官:你说说Springboot的启动过程吧(5万字分析启动过程))中,给自己留了几个扩展内容,其中一个是Spring的事件机制,在分析源码的过程中,也是大量使用了事件机制,在我分析的这篇博客中,有不少地方都运用了事件发布机制,所以本文的目的是从SpringBoot中学习到事件的发布流程,并自己写一个事件发布用于以后得业务。
前言中提到的源码分析文章中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
事件,以及BootstrapContextClosedEvent
和ServletWebServerInitializedEvent
都是在Spring-boot包下的:
其中SpringApplicationEvent
是这些Application***Event
事件的父类,EventPublishingRunListener
是集中了各个事件的发布的方法。
而类似Context***Event
事件则是在spring-context包下的:
其中ApplicationContextEvent
是类似Context***Event
事件的父类。
而SpringApplicationEvent
和ApplicationContextEvent
又都继承了ApplicationEvent
,EventObject
是ApplicationEvent
的父类。
我们拿ApplicationStartingEvent
事件来说,这个事件其实并不是只被一个监听器监听:
同时被RestartApplicationListener
和LoggingApplicationListener
监听:
RestartApplicationListener
类实现了ApplicationListener
重写了onApplicationEvent
方法:
LoggingApplicationListener
类实现了GenericApplicationListener
,但这个GenericApplicationListener
接口继承了SmartApplicationListener
接口,而SmartApplicationListener
接口又继承了ApplicationListener
LoggingApplicationListener
类重写了onApplicationEvent
方法:
可以看到Springboot中的监听器进行了多事件源的监听,在根据instanceof
关键字进行判断,对不同的事件源进行处理。
我们再看一个事件:ApplicationEnvironmentPreparedEvent
:
这个事件被7个监听器进行监听和分别处理,这些监听器毫无疑问也都间接或直接实现或继承了ApplicationListener
这里罗列一下部分事件对应的监听器:
事件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中的事件和监听器的关系,其实我们在自己的实现中也可以一个事件被多个监听器监听,只是我们在业务实现中一搬都是一对一的关系。
自定义事件发布机制说明:
HomeworkController
:
HomeworkService
:
直接用postman或者ApiFox调用Post接口:http://localhost:8081/homework/show
就可以看到控制台打印的日志了。
这里就是HomeworkService
中HomeworkEvent
的事件内容被HomeworkListener
接收到之后打印的信息日志了。
上午不知道咋操作的,想要重新启动Springboot发现端口总是被占用,哪怕我改了application.properties文件中的端口,改啥端口,啥端口就被占用,后面用下面的命令找到了被占用的端口,把这个端口杀掉就行。
sudo lsof -i tcp:8081
从列表中找到PID对应的进程id,执行kill pid
就可以杀死这个该死的占用的进程。
看着上面控制台日志和我们直接在service中打印的一样,没有啥区别,但实际上这只是个例子,如果像Springboot中一样,有很多的事件,如果不采用事件发布机制,那发布事件之前的代码逻辑处理就很多,就使得原本复杂的逻辑更复杂了。
事件发布机制有点像我们列的待处理的工作清单一样,清单列好,再一件件的去完成,这样更加清晰明了,或者更专业的点的名称叫「解耦」,将业务和业务进行解耦。
其实在SpringBoot源码分析(面试官:你说说Springboot的启动过程吧(5万字分析启动过程))中的A.5、发布启动的事件并由应用程序启动监听器进行处理和B.6、发布上下文开始的事件都多多少少有提到过
这里结合我们自己的例子对源码进行分析,启动Springboot之后,发送Post请求,通过debug模式进入EventPublishHelperImpl
中的applicationContext.publishEvent(event);
的方法中一步步进入到AbstractApplicationContext
中的publishEvent
方法:
这个方法里面有3个if块,第一个if块必然走的是if分支,即HomeworkEvent
是ApplicationEvent
的实例,第二个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
方法了,所以分析下来其实并不难,但这种思想的来源却是需要多年的积累才能够进行提炼和实现的,这种思想其实就是设计模式中的观察者模式。感谢这些大佬的贡献,我们才能站在巨人的肩膀上走的更快。。。
---------------------你知道的越多,不知道的越多----------------------