前言:
最近一直在看SpringBoot的官方文档,也一直考虑要不要写个文章,本来还是挺纠结的,害怕内容有误,误人子弟,但是文档读过去,自己动手操作一遍,当时感觉理解还行,过了一段时间又忘记了(这是第二遍看文档),所以决定还是写个文章吧,就当自己的学习笔记了,如果文中出现理解上的偏差,烦请各位大佬指正。
1.SpringBoot中加载自定义Listener的方式
- 第一次接触SpringBoot的时候,觉得这个东西太简单,首先简单的是他的启动方式,就是一个main函数,其实就是一行代码:
SpringApplication.run(Class<?> cl,args);
我们自定义的listener,有些触发是在ApplicationContext refresh前,有些是在ApplicationContext refresh后(关于哪些在前,哪些在后,我们后面再讨论),我们可以改造一下我们的main函数,在服务启动的时候,把我们的listener交给Spring,Spring会按照顺序帮我们加载并启动,如下:
SpringApplication application = new SpringApplication(Class<?> cl);
application .addListeners( new ApplicationReadyListener() );
application .addListeners( new ApplicationFailedListener() );
application .run(args);
这样我们就把我们自定义的listener交给Spring去处理了,至于Spring是这么是处理的,我们看一下Spring的源码,我们首先进入run方法,如下:
![717652ad7c6e91f06f51f4c7193df6ad.png](https://i-blog.csdnimg.cn/blog_migrate/4715b8b7f43a8b67d41191b56e20c77c.jpeg)
getRunListeners()这个方法我们后面再说,先进入listeners.starting()这个方法看一下:
![65377d507641f42b57549cf8812ab12c.png](https://i-blog.csdnimg.cn/blog_migrate/bfeaca0f044867e38f63fa680aa5c232.png)
这个方法里面,就启动了我们的所有的listener,通过这种方式我们就将我们的listener交给spring管理了,我们只要关注我们listener内部的逻辑就可以了。这种方式还有一种变种,如果我们的应用程序有父子等级之分,我们就需要一种链式的方式来启动,如下:
SpringApplicationBuilder builder = new SpringApplicationBuilder();
builder.listeners(new BlogApplicationContextInitializedListener(),new BlogApplicationPreparedListener());
builder.run(args);
上述代码段,只演示如何添加listener,这种方式注册的listener当spring进行publish的时候,会在我们的父子层分别publish两个名字一样的listener,如何区分呢,我们可以在父子层分别实现ApplicationContextAware,注入对应的applicationContext,进行区分就行了。
2.如果我们想我们的listener自动注入到spring中,我们再看一下,我们上面说到的run()方法中的getRunListeners(args)方法,我们进去看一下源代码:
![69a221c7a2a2d7ed7241845f2313ce4a.png](https://i-blog.csdnimg.cn/blog_migrate/2fe384039c56da616e4e0a639c7d6dbc.png)
我们主要看一下getSpringFactoriesInstances这个方法:
![d67727d99f35f1940f288f5bbbda1b1d.png](https://i-blog.csdnimg.cn/blog_migrate/14ab17f7c11301d45c195583edc45bd5.jpeg)
这个方法有两个关键点:
- SpringFactoriesLoader.loadFactoryNames()这个方法主要的作用就是去我们的claasspath路径下找/META-INF/spring.factories这个文件中对应type下的value值,也就是我们定义的listener格式如下:
![c3bb220488420baf93a98f39cd2ce9b9.png](https://i-blog.csdnimg.cn/blog_migrate/d00c19d9aa7693e535d08c69db9bb228.jpeg)
2.这个方法找到所有的listener的类名,然后通过createSpringFactoriesInstances()这个方法创建对应的Instance,主要通过反射的机制来创建对象,listener实例创建好后,就和第一步中的方法一样了,启动所有的listener。上面介绍了三种方式可以将我们自定义的listener交给spring去管理,并正确的启动。
2.SpringBoot中的Envent
ApplicationStartingEvent
在所有的处理线程执行之前执行,除了listener的实例化和加载ApplicationEnvironmentPreparedEvent
在context被创建之前,当Environment被context使用的时候触发执行ApplicationContextInitializedEvent
在ApplicationContext准备好,并且 ApplicationContextInitializers被正确的调用了,虽然ApplicationContext已经准备好了但是Bean还没有初始化ApplicationPreparedEvent
在context刷新之前,但是BeanDefinition被加载后触发,我们都知道spring初始化Bean的第一步就是初始化对应definition,所以这个阶段Bean依旧没有初始化ApplicationStartedEvent
在context刷新后触发,这个时候Spring的容器已经被初始化好了,我们需要的Bean等都已经被正确的初始化了ApplicationReadyEvent
上面容器被正确的初始化后,接下来就是启动我们的中间件容器了,比如tomcat,Tomcat启动后就会触发此事件- 下面介绍两个事件在 ApplicationStartedEvent之后触发,在ApplicationReadyEvent之前触发
ContextRefreshedEvent
context被刷新后执行WebServerInitializedEvent
WebServer正确启动后触发,如果是web服务则是ServletWebServerInitializedEvent
,如果是reactive则是ReactiveWebServerInitializedEvent
SpringBoot会在启动的时候,按照上面的顺序触发对应的event,如果我们有注册进Spring中, 上面的event我都写了个listener,下面是我执行的结果:
![29bfe853f07f5807912d083144325b83.png](https://i-blog.csdnimg.cn/blog_migrate/def942ce0241f1a7b7580f4919a9acfd.jpeg)
![0d03b7e1c06ec9e4068cdfc8dde790de.png](https://i-blog.csdnimg.cn/blog_migrate/70da5fc399cf3dd1496f4438db6cf5f0.jpeg)
上面的截图,可以看出每个event的执行顺序和我们上面的说的执行顺序是一致的,大家可以自己写一个debug一下,按照我这个顺序看对应的源码,就能很清晰的在哪个阶段触发哪个event,这样我们如果业务有需求,就可以做一些定制化的开发,关于SpringBoot中的Event和Listener就先介绍这么多,其实挺简单的,如有不妥的地方,望指正!