【戏说Spring Boot】Spring Boot监听器解析

文章首发于公众号,欢迎订阅

在这里插入图片描述

上期问题解答

Q1:谈谈你对 SpringFactoriesLoader 的理解,SpringFactoriesLoader 是如何加载工厂类的?

SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,只需要在模块的 META-INF/spring.factories 文件下通过键值对的形式写入,键是接口全限定名,值是以逗号分隔的实现类,使用SpringFactoriesLoader 来将相应的实现类注册到 Spring 容器中。

加载工厂类的过程:

  1. 查找缓存,缓存存在,直接返回
  2. 缓存不存在,读取指定资源文件
  3. 构造 Properties 对象
  4. 获取 key 对应的 value
  5. 逗号分割 value
  6. 保存结果到缓存

Q2:系统初始化器的作用(结合系统自带的初始化器)

  • DelegatingApplicationContextInitializer

这个初始化器实际上将初始化的工作委托给 context.initializer.classes 环境变量指定的初始化器,这个初始化器的优先级是最高的,因此会被第一个执行。

  • ContextIdApplicationContextInitializer

ApplicationContext 设置一个 Id,这个 Id 由 name 和 index 两个部分组成。

  • ConfigurationWarningsApplicationContextInitializer

用来对常见的由于配置错误而引起的警告进行打印报告。

  • ServerPortInfoApplicationContextInitializer

通过监听 EmbeddedServletContainerInitializedEvent 事件,来对内部服务器实际要监听的端口号进行属性设置。

  • SharedMetadataReaderFactoryContextInitializer

用来创建一个可以在 ConfigurationClassPostProcessor 和 Spring Boot 之间共享的 CachingMetadataReaderFactory

  • AutoConfigurationReportLoggingInitializer

用来将 ConditionEvaluationReport 记录的条件评估详情输出到日志,默认使用 DEBUG 级别,当有异常问题发生时会使用 INFO 级别。

Q3:系统初始化器何时调用?

prepareContext 方法中,会开始调用初始化器进行初始化操作。

Q4:如何实现自定义系统初始化器?

1、在 spring.factories 文件中定义自己的初始化器

2、在配置文件中通过 context.initializer.classes 定义自己的初始化器

3、通过 addListeners() 方法加入将自己的初始化器

Q5:自定义系统初始化器有没有什么注意事项?

注意初始化器的加载顺序。通过两种方式可以修改。

1、通过 @Order() 注解的方式

2、实现 Ordered 接口,重写 getOrder() 方法

下面开始今天的内容!

监听器的注册方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A9aez7fM-1587606340211)(http://img.feichaoyu.com/Emoticon/Snipaste_2020-04-19_14-47-32.jpg)]

好了,今天我们来说说 Spring Boot 的监听器,其实上一次给大家留了个坑,还记得下面这个构造方法吗?

image-20200420151347571

当时我们猜测 Listener 是否也和 Initializer 一样,事实上,注册监听器的过程是一样的,所以说用法和初始化器一模一样,但是我们今天的重点不是监听器的注册过程,而是监听器的实现机制。

不过我觉得还是有必要列举一下监听器的使用。

1、定义在 spring.factories 文件中,被 SpringFactoriesLoader 发现注册(工厂加载机制)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXwTzKeL-1587606340219)(http://img.feichaoyu.com/Typora/image-20200420152957763.png)]

注意到上面的红框,这里你必须填一个 ApplicationListener 的实现类,告诉 Spring 我只需要监听这一个事件,否则所有内置事件都会被监听到。

配置工厂类

image-20200420153244114

2、SpringApplication 初始化完成后手动添加

image-20200420153740235

image-20200420153813014

3、定义成环境变量,被 DelegatingApplicationListener 所发现注册(默认优先级最高)

在配置文件中加入下面一行

context.listener.classes=com.feichaoyu.springboot.initializer.ThirdInitializer

image-20200420154359892

上面的实现类都是实现 ApplicationListener 接口,不过还有一个接口 SmartApplicationListener

听这名字就知道很牛逼

image-20200420160608508

实现 SmartApplicationListener 接口的监听器可以同时监听多个你感兴趣的事件,而实现 ApplicationListener 接口只能监听一个事件。

好了,监听器的使用就介绍到这了,下面开始重头戏了。

引入监听器模型

有请我们的监听器模型登场。

监听器模型有四要素:

  • 事件
  • 监听器
  • 广播器
  • 事件触发机制

我们通过用户下单的例子来说明这四个要素,这个例子非常重要,是你看懂 Spring Boot 监听器模型的关键。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvnMMRSy-1587606340230)(http://img.feichaoyu.com/Emoticon/48bbbe046804ea78ac27d448010e1728.jpg)]

1、定义事件

image-20200422210852059

一旦有用户下单,则有一个短信事件,会给用户发送短信。还有一个积分事件,会给用户增加积分。

2、定义事件监听器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cK1ZCvm7-1587606340233)(http://img.feichaoyu.com/Typora/image-20200422210921651.png)]

短信监听器只监听短信事件,积分监听器只监听积分事件,而对其他事件不感兴趣。

3、定义事件广播器

image-20200422210949453

image-20200422211006632

image-20200422211018168

首先,在 EventMulticaster 中定义了三个方法,分别是广播任务、添加监听器、移除监听器。

接着,AbstractEventMulticaster 实现了 EventMulticaster 接口,重写了其中的三个方法,同时新增了两个方法,分别是广播前置任务和后置任务。在 multicastEvent 方法中,我们可以看到模板方法的使用,需要子类实现抽象父类。

最后,OrderEventMulticaster 继承了 AbstractEventMulticaster,重写了两个父类的方法。

事件广播器中包含所有的监听器,会对所有监听器遍历,让监听器找到自己感兴趣的事件。

4、事件触发机制

image-20200422211033262

我们还需要把监听器接口的三个实现类、广播器的接口的两个实现类通过 @Component 加入到 Spring 中,我上面没加,大家注意一下。

然后把 AbstractEventMulticasterlisteners 变量加上 @Autowired,让所有监听器的实现类注入进来。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hPAPqp7e-1587606340239)(http://img.feichaoyu.com/Typora/image-20200420221606205.png)]

到此,这个例子写完了。

可以测试一下,

image-20200421153101558

最终输出结果如下:

image-20200420215907819

那么在 Spring Boot 中,监听器的实现会不会也和上面的例子相似,为了证明我们的猜测,不妨去代码中找一下相关的类和接口呗,走你!

Spring Boot 监听器模型解析

我们同样也分四要素讨论。

1、事件

我们打开一个之前使用过的 ApplicationStartedEvent 事件类,查看 UML 图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-721gYaUq-1587606340243)(http://img.feichaoyu.com/Typora/image-20200421151327821.png)]

从图中我们看出,SpringApplicationEvent 就相当于我们的 OrderEventApplicationStartedEvent 相当于 MessageEventCreditEvent

2、监听器

image-20200421152913729

这个接口就相当于我们的 OrderListener。我们之前通过实现该接口,自定义了一些监听器,Spring Boot 也内置了很多监听器,比如 DelegatingApplicationListener

3、广播器

广播器的层次结构和我们的也是一样的,可以对比来看

image-20200421154049172

4、事件触发机制

和我们的 OrderRunListener 类似,EventPublishingRunListener 起到了相同的作用。具体我们下面分析。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U6o4NEDW-1587606340247)(http://img.feichaoyu.com/Typora/image-20200421214139945.png)]

好了,下面我们开始研究代码。

我们直接定位到这里

image-20200421212910950

run 方法点进去,由于方法太长,我分两次截取

image-20200421213143136

image-20200421213244867

注意到我所有的红框地方,也就是和监听器打交道的地方都列出来了,接下来我们就来分析一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XFfSw3jK-1587606340252)(http://img.feichaoyu.com/Typora/image-20200421213657836.png)]

进去看下

image-20200421213750272

知道为什么要有两个参数了吧,反射构造实例时需要传入的,和类型刚好对上了!

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); 这行代码很显然就是加载所有实现了 SpringApplicationRunListener 接口的类,不过Spring Boot 中默认只有一个实现类 EventPublishingRunListener

这里我们关注一下 SpringApplicationRunListeners,这个类是 SpringApplicationRunListener 的集合,从它的定义中也不难发现。

image-20200421214949406

它将获取到所有的实现了 SpringApplicationRunListener 接口的类。

OK,我们回到 run 方法,继续向下看

image-20200421215141816

兄弟,我要进去了

image-20200421215333165

由于默认实现类是 EventPublishingRunListener,因此我们下一步去它里面看看。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eq7S3eEc-1587606340257)(http://img.feichaoyu.com/Typora/image-20200421215756070.png)]

用法和我们的相同,都是用过广播器去发布事件的,不过这个 initialMulticaster 是?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QNjcod2G-1587606340259)(http://img.feichaoyu.com/Emoticon/4644ecc7bbf8828d93b9ba94c513adbd.jpg)]

image-20200421215953065

搜嘎,想起你来了。

那我们继续进去看看吧

image-20200421222130451

resolveDefaultEventType 方法就是获取事件的 Class 类型。

image-20200421222525999

getApplicationListeners 这个方法值得我们 debug 一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blFVX7Gb-1587606340264)(http://img.feichaoyu.com/Emoticon/62bf7607c2358a80bdb717e4ca0817c67.jpg)]

顺带问下,大家是否好奇这个 source 是啥?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1s3Rob7-1587606340265)(http://img.feichaoyu.com/Typora/image-20200421224945819.png)]

接着我们看下 ListenerCacheKey 这个实例。

image-20200421225144546

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-84zGXW2y-1587606340268)(http://img.feichaoyu.com/Typora/image-20200421225330977.png)]

也就是根据 event 类型和 source 类型构造出来的对象。

实际上它是 AbstractApplicationEventMulticaster 的静态内部类。

image-20200421225502738

继续向下

image-20200421225948358

retrieverCache 变量的定义如下,它的键是 ListenerCacheKey,值是 ListenerRetriever

image-20200421230011961

ListenerRetriever 其实也是 AbstractApplicationEventMulticaster 的内部类,用于存储监听器。

image-20200421230302122

第一次是没有缓存的,所以缓存中获取不到监听器,接着我们继续向下走

image-20200422113141459

这是个同步方法,我们进入 retrieveApplicationListeners 方法看看。

image-20200422115746260

这里就是获取之前添加的 listeners,从 this.defaultRetriever.applicationListeners 中来,那么这个里面的监听器是什么时候添加的呢?

回去找找,不慌!

image-20200422120211155

没错,就是这里了,我们之前也说过了,通过 getSpringFactoriesInstances 这个方法会构造出 SpringApplicationRunListener 的实现类,也是说默认的 EventPublishingRunListener,然后传入参数构造出实例,同时创建一个 SimpleApplicationEventMulticaster 实例,由于监听器已经注册了,所以可以直接获取,把监听器列表加入 defaultRetriever

image-20200422122324502

OK,我们回到 retrieveApplicationListeners 方法,继续往下看

image-20200422122913812

点进去

image-20200422123158989

这里又出现一个类 GenericApplicationListenerAdapter,从名字可以看出这是一个适配器类,用于将 ApplicationListener 适配成 GenericApplicationListener

image-20200422150259564

其中 GenericApplicationListenerAdapter 构造方法中,会采用泛型解析方法 resolveDeclaredEventType 将监听器感兴趣事件解析出来交给 declaredEventType

也就是这一块内容

image-20200422150512977

然后判断该监听器是否支持 eventType 事件类型以及 sourceType 源类型。

这里我们点进去看下

image-20200422145731448

首先会判断该监听器是否实现了 SmartApplicationListener,如果是,那么会调用自己重写的 supportsEventType 方法,也就是我们之前写过的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UoWg5Egx-1587606340282)(http://img.feichaoyu.com/Typora/image-20200422150010589.png)]

只要支持就返回 true,把该监听器加入 allListeners 中,最后返回。

接着回到 getApplicationListeners 方法,将相应监听器列表放入缓存,然后返回所有对指定事件感兴趣的监听器。

image-20200422143250164

回到 multicastEvent 方法,开始触发对应监听器的监听事件方法。

image-20200422143636912

image-20200422143809788

高能来了!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X415pfxh-1587606340285)(http://img.feichaoyu.com/Typora/image-20200422143900448.png)]

至此,我们分析完了 listeners.starting() 的过程,那么其他过程几乎都是一样的。

还记得我们的 run 方法吗?

image-20200421213143136

image-20200421213244867

整个红框的方法,就是监听器的整个生命周期。

比如在 prepareEnvironment 时,调用了 listeners.environmentPrepared(environment)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Bt50hmZ-1587606340290)(http://img.feichaoyu.com/Typora/image-20200422152024376.png)]

后续流程和 starting 时是类似的。

到此我们的监听器机制也分析完了,实际上,Spring Boot 监听器模型用到了观察者设计模式

顺带列举一下 Spring Boot 监听方法与事件的对应关系:

监听方法 对应事件
starting() ApplicationStartingEvent
environmentPrepared(ConfigurableEnvironment environment) ApplicationEnvironmentPreparedEvent
contextPrepared(ConfigurableApplicationContext context) ApplicationContextInitializedEvent
contextLoaded(ConfigurableApplicationContext context) ApplicationPreparedEvent
started(ConfigurableApplicationContext context) ApplicationStartedEvent
running(ConfigurableApplicationContext context) ApplicationReadyEvent
failed(ConfigurableApplicationContext context, Throwable exception) ApplicationFailedEvent

下面又到了你们表演的时候了!

思考题

  • Spring Boot 有哪些事件,以及他们的执行顺序?
  • 介绍一下监听器的作用(结合自带的监听器)。
  • 介绍一下 Spring Boot 的事件触发机制。
  • 如何实现自定义系统监听器及注意事项?
  • 说说实现 ApplicationListener 接口和 SmartApplicationListener 接口的区别。

答案我们下期给出!


我创建了一个免费的知识星球,用于分享知识日记,欢迎加入!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TkVN8eag-1587606340294)()]

发布了10 篇原创文章 · 获赞 7 · 访问量 2369

猜你喜欢

转载自blog.csdn.net/a979331856/article/details/105700250