上期问题解答
Q1:谈谈你对 SpringFactoriesLoader
的理解,SpringFactoriesLoader
是如何加载工厂类的?
SpringFactoriesLoader
工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories
文件下通过键值对的形式写入,键是接口全限定名,值是以逗号分隔的实现类,使用SpringFactoriesLoader
来将相应的实现类注册到 Spring 容器中。加载工厂类的过程:
- 查找缓存,缓存存在,直接返回
- 缓存不存在,读取指定资源文件
- 构造 Properties 对象
- 获取 key 对应的 value
- 逗号分割 value
- 保存结果到缓存
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()
方法
下面开始今天的内容!
监听器的注册方法
好了,今天我们来说说 Spring Boot 的监听器,其实上一次给大家留了个坑,还记得下面这个构造方法吗?
当时我们猜测 Listener 是否也和 Initializer 一样,事实上,注册监听器的过程是一样的,所以说用法和初始化器一模一样,但是我们今天的重点不是监听器的注册过程,而是监听器的实现机制。
不过我觉得还是有必要列举一下监听器的使用。
1、定义在 spring.factories
文件中,被 SpringFactoriesLoader
发现注册(工厂加载机制)
注意到上面的红框,这里你必须填一个 ApplicationListener
的实现类,告诉 Spring 我只需要监听这一个事件,否则所有内置事件都会被监听到。
配置工厂类
2、SpringApplication
初始化完成后手动添加
3、定义成环境变量,被 DelegatingApplicationListener
所发现注册(默认优先级最高)
在配置文件中加入下面一行
context.listener.classes=com.feichaoyu.springboot.initializer.ThirdInitializer
上面的实现类都是实现 ApplicationListener
接口,不过还有一个接口 SmartApplicationListener
听这名字就知道很牛逼
实现 SmartApplicationListener
接口的监听器可以同时监听多个你感兴趣的事件,而实现 ApplicationListener
接口只能监听一个事件。
好了,监听器的使用就介绍到这了,下面开始重头戏了。
引入监听器模型
有请我们的监听器模型登场。
监听器模型有四要素:
- 事件
- 监听器
- 广播器
- 事件触发机制
我们通过用户下单的例子来说明这四个要素,这个例子非常重要,是你看懂 Spring Boot 监听器模型的关键。
1、定义事件
一旦有用户下单,则有一个短信事件,会给用户发送短信。还有一个积分事件,会给用户增加积分。
2、定义事件监听器
短信监听器只监听短信事件,积分监听器只监听积分事件,而对其他事件不感兴趣。
3、定义事件广播器
首先,在 EventMulticaster
中定义了三个方法,分别是广播任务、添加监听器、移除监听器。
接着,AbstractEventMulticaster
实现了 EventMulticaster
接口,重写了其中的三个方法,同时新增了两个方法,分别是广播前置任务和后置任务。在 multicastEvent
方法中,我们可以看到模板方法的使用,需要子类实现抽象父类。
最后,OrderEventMulticaster
继承了 AbstractEventMulticaster
,重写了两个父类的方法。
事件广播器中包含所有的监听器,会对所有监听器遍历,让监听器找到自己感兴趣的事件。
4、事件触发机制
我们还需要把监听器接口的三个实现类、广播器的接口的两个实现类通过 @Component
加入到 Spring 中,我上面没加,大家注意一下。
然后把 AbstractEventMulticaster
的 listeners
变量加上 @Autowired
,让所有监听器的实现类注入进来。
到此,这个例子写完了。
可以测试一下,
最终输出结果如下:
那么在 Spring Boot 中,监听器的实现会不会也和上面的例子相似,为了证明我们的猜测,不妨去代码中找一下相关的类和接口呗,走你!
Spring Boot 监听器模型解析
我们同样也分四要素讨论。
1、事件
我们打开一个之前使用过的 ApplicationStartedEvent
事件类,查看 UML 图
从图中我们看出,SpringApplicationEvent
就相当于我们的 OrderEvent
,ApplicationStartedEvent
相当于 MessageEvent
、CreditEvent
。
2、监听器
这个接口就相当于我们的 OrderListener
。我们之前通过实现该接口,自定义了一些监听器,Spring Boot 也内置了很多监听器,比如 DelegatingApplicationListener
。
3、广播器
广播器的层次结构和我们的也是一样的,可以对比来看
4、事件触发机制
和我们的 OrderRunListener
类似,EventPublishingRunListener
起到了相同的作用。具体我们下面分析。
好了,下面我们开始研究代码。
我们直接定位到这里
run 方法点进去,由于方法太长,我分两次截取
注意到我所有的红框地方,也就是和监听器打交道的地方都列出来了,接下来我们就来分析一下。
进去看下
知道为什么要有两个参数了吧,反射构造实例时需要传入的,和类型刚好对上了!
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
这行代码很显然就是加载所有实现了 SpringApplicationRunListener
接口的类,不过Spring Boot 中默认只有一个实现类 EventPublishingRunListener
。
这里我们关注一下 SpringApplicationRunListeners
,这个类是 SpringApplicationRunListener
的集合,从它的定义中也不难发现。
它将获取到所有的实现了 SpringApplicationRunListener
接口的类。
OK,我们回到 run 方法,继续向下看
兄弟,我要进去了
由于默认实现类是 EventPublishingRunListener
,因此我们下一步去它里面看看。
用法和我们的相同,都是用过广播器去发布事件的,不过这个 initialMulticaster
是?
搜嘎,想起你来了。
那我们继续进去看看吧
resolveDefaultEventType
方法就是获取事件的 Class 类型。
getApplicationListeners
这个方法值得我们 debug 一下。
顺带问下,大家是否好奇这个 source 是啥?
接着我们看下 ListenerCacheKey
这个实例。
也就是根据 event 类型和 source 类型构造出来的对象。
实际上它是 AbstractApplicationEventMulticaster
的静态内部类。
继续向下
retrieverCache
变量的定义如下,它的键是 ListenerCacheKey
,值是 ListenerRetriever
ListenerRetriever
其实也是 AbstractApplicationEventMulticaster
的内部类,用于存储监听器。
第一次是没有缓存的,所以缓存中获取不到监听器,接着我们继续向下走
这是个同步方法,我们进入 retrieveApplicationListeners
方法看看。
这里就是获取之前添加的 listeners,从 this.defaultRetriever.applicationListeners
中来,那么这个里面的监听器是什么时候添加的呢?
回去找找,不慌!
没错,就是这里了,我们之前也说过了,通过 getSpringFactoriesInstances
这个方法会构造出 SpringApplicationRunListener
的实现类,也是说默认的 EventPublishingRunListener
,然后传入参数构造出实例,同时创建一个 SimpleApplicationEventMulticaster
实例,由于监听器已经注册了,所以可以直接获取,把监听器列表加入 defaultRetriever
OK,我们回到 retrieveApplicationListeners
方法,继续往下看
点进去
这里又出现一个类 GenericApplicationListenerAdapter
,从名字可以看出这是一个适配器类,用于将 ApplicationListener
适配成 GenericApplicationListener
。
其中 GenericApplicationListenerAdapter
构造方法中,会采用泛型解析方法 resolveDeclaredEventType
将监听器感兴趣事件解析出来交给 declaredEventType
。
也就是这一块内容
然后判断该监听器是否支持 eventType
事件类型以及 sourceType
源类型。
这里我们点进去看下
首先会判断该监听器是否实现了 SmartApplicationListener
,如果是,那么会调用自己重写的 supportsEventType
方法,也就是我们之前写过的
只要支持就返回 true,把该监听器加入 allListeners
中,最后返回。
接着回到 getApplicationListeners
方法,将相应监听器列表放入缓存,然后返回所有对指定事件感兴趣的监听器。
回到 multicastEvent
方法,开始触发对应监听器的监听事件方法。
高能来了!
至此,我们分析完了 listeners.starting()
的过程,那么其他过程几乎都是一样的。
还记得我们的 run 方法吗?
整个红框的方法,就是监听器的整个生命周期。
比如在 prepareEnvironment
时,调用了 listeners.environmentPrepared(environment)
后续流程和 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
接口的区别。
答案我们下期给出!
我创建了一个免费的知识星球,用于分享知识日记,欢迎加入!