@[toc]
所有调试均使用SpringBoot 2.4.5版本。
ApplicationListener事件监听机制其实是由Spring提供的,应用内部的事件驱动机制。也就是Pub/Sub发布订阅机制在应用内部的实现。一般主要是用于监控应用内部的一些运行状况,在应用开发中也可以使用。
具体的实现机制可以到Spring中去探究,这里就来简单理解下SpringBoot对这个事件驱动机制做了哪些封装。
一、事件监听器使用
1、自己实现一个事件监听器
首先创建一个自定义的事件监听器:
1 | java复制代码public class MyApplicationListener implements ApplicationListener<ApplicationEvent> { |
然后还是在项目的spring.factories中配置监听器
1 | properties复制代码org.springframework.context.ApplicationListener=\ |
然后配置启动类。在启动类中发布一个自己的事件。
1 | java复制代码@SpringBootApplication |
正常启动SpringBoot应用,就能打印出启动过程中的关键事件的日志。这里把关键的事件日志给整理出来:
1 | dart复制代码======>MyApplicationListener: org.springframework.boot.context.event.ApplicationStartingEvent[source=org.springframework.boot.SpringApplication@60c6f5b] |
这里就打印出了SpringBoot应用启动过程中的多个内部事件。实际上这多个内部事件也就对应了启动过程的各个阶段,是梳理SpringBoot启动流程非常好的入口。
Spring事件机制的其他细节这里就不多说了,大家可以自行了解。我们这里还是专注于SpringBoot的部分。
2、事件监听器的其他配置方式:
这个事件监听机制是Spring非常重要的一个机制,有非常多的配置方式。除了上面提到的基于spring.factories文件配置的方式,还有其他几种配置方式。
2.1 SpringApplication.addListener
跟之前的Initializer一样,这个事件监听器也可以在SpringApplication中直接添加。
1 | java复制代码@SpringBootApplication |
2.2 基于注解添加
基于注解,将MyApplicationListener配置到Spring的IOC容器中。
1 | java复制代码@Configuration |
2.3 在SpringBoot的配置文件中配置
另外还一种方式,可以在SpringBoot的配置文件application.properties中配置
1 | properties复制代码context.listener.classes=com.roy.applicationListener.MyApplicationListener |
这几种方式都可以配置事件监听器。另外,其实在上一章节ApplicationContextInitializer中也能看到,在SpringBoot内部也在应用初始化中扩展出了很多通过application添加事件监听器的扩展。
二、核心机制解读
事件机制的使用方式很多,不同的配置方式也有不同的加载流程。我们这里还是只解读SpringBoot中如何通过spring.factories文件来加载事件监听器的。
SpringBoot中对于监听器的处理,也跟ApplicationContextInitializer的处理流程是差不多的。首先在SpringApplication的构造方法中加载所有的监听器:
1 | java复制代码public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { |
然后在SpringApplication的run方法中启动所有监听器:
1 | java复制代码public ConfigurableApplicationContext run(String... args) { |
首先在注册SpringApplicationRunListener时,就会解析spring.factories,读取其中的org.springframework.boot.SpringApplicationRunListener配置。
1 | properties复制代码# Run Listeners |
然后从发布事件的地方往下调试,可以看到SpringBoot事件发布的核心对象EventPublishingRunListener。通过其中的initialMulticaster组件来发布不同的事件。 而他实现事件监听的方式就是在发布事件时,实时调用一下已经注册的所有对应事件的监听器。
1 | java复制代码public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { |
调用Spring中的SimpleApplicationEventMulticaster组件发布事件。
1 | java复制代码public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { |
这其中initialMulticaster对象已经是Spring-Context包中的内容。所以这里就完成了SpringBoot中事务监听机制的梳理。
这个事件机制也是SpringBoot使用过程中非常好的功能扩展点,因为应用启动过程中,这些事件都已经默认发布了,可以叠加自己想要的应用初始化工作。这些关键事件的发布顺序也是非常重要的。例如,如果你的扩展功能需要用到Spring的IOC容器,那就只能去监听ContextRefreshedEvent之后的几个内部事件。
三、SpringBoot中的核心实现
接下来梳理SpringBoot当中通过spring.factories默认注册的事务监听器
1 | properties复制代码#spirng-boot.jar |
那接下来同样是梳理几个有代表性的事务监听器逻辑。
例如BackgroundPreinitializer,他会将几个比较耗时的初始化工作提前到应用启动过程中加载,并且单独启动一个线程来加快加载速度。
1 | java复制代码@Order(LoggingApplicationListener.DEFAULT_ORDER + 1) |
接下来其他几个事件监听器的功能也简单总结下。有兴趣的建议自行调试下代码,这样才能形成自己的理解。
- org.sf.boot.ClearCachesApplicationListener:应用上下文加载完成后对缓存做清除工作,响应事件ContextRefreshedEvent
- org.sf.boot.builder.ParentContextCloserApplicationListener:监听双亲应用上下文的关闭事件并往自己的孩子应用上下文中传播,相关事件ParentContextAvailableEvent/ContextClosedEvent
- org.sf.boot.context.FileEncodingApplicationListener:如果系统文件编码和环境变量中指定的不同则终止应用启动。
具体的方法是比较系统属性file.encoding和环境变量spring.mandatory-file-encoding是否相等(大小写不敏感)。 - org.sf.boot.context.config.AnsiOutputApplicationListener:根据spring.output.ansi.enabled参数配置AnsiOutput
- org.sf.boot.context.config.DelegatingApplicationListener:监听到事件后转发给环境变量context.listener.classes指定的那些事件监听器
- org.sf.boot.context.logging.LoggingApplicationListener 配置LoggingSystem。使用logging.config环境变量指定的配置或者缺省配置
- org.springframework.boot.env.EnvironmentPostProcessorApplicationListener: 加载spring.factories文件中配置的EnvironmentPostProcessor。
- org.sf.boot.liquibase.LiquibaseServiceLocatorApplicationListener 使用一个可以和Spring Boot可执行jar包配合工作的版本替换liquibase ServiceLocator
org.sf.boot.context.config.ConfigFileApplicationListener : 指定SpringBoot的配置文件地址。 但是在2.4.5版本已经移除。
最后,有没有觉得EnvironmentPostProcessorApplicationListener这个事件监听器挺有意思的?SpringBoot直接将spring.factories中EnvironmentPostProcessor机制的加载工作交给了事件监听器,接下来我们就趁热打铁,来继续看下EnvironmentPostProcessor 的处理机制。
四、EnvironmentPostProcessor使用
EnvironmentPostProcessor是在环境信息加载完成后进行一些补充处理。例如下面一个示例可以在SpringBoot读取完application.properties后,补充读取另一个配置文件:
1 | java复制代码//@Component |
使用的方式同样可以配置到spring.factories文件中,或者通过@Component注解加入到IOC容器中。
1 | properties复制代码org.springframework.boot.env.EnvironmentPostProcessor=\ |
四、EnvironmentPostProcessor 加载机制
EnvironmentPostProcessor 是通过EnvironmentPostProcessorApplicationListener监听Spring事件来对运行环境进行补充的后续处理。先来看下他监听了哪些事件。
1 | java复制代码public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered { |
从这几个事件当中可以看出,在他的子处理器中是不能引用IOC容器的。
然后再往下继续调试他的onApplicationEnvironmentPreparedEvent方法,找下他解析spring.factories,加载EnvironmentPostProcessor 实现的机制。这个机制就不再是使用 SpringFactoriesLoader 了。
1 | java复制代码//处理EnvironmentPostProcessor的方式。 |
一路往下调试,会跟踪到ReflectionEnvironmentPostProcessorsFactory的getEnvironmentPostProcessors方法。
1 | java复制代码public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory, |
这个parameters就是给这一组实例添加一些默认的参数类。后面各个子类可以创建包含了这些参数的构造方法来进行实例化。 有没有发现,这就是一个简单的IOC属性注入吗?学IOC有比这个更经典的入手案例吗?
具体实例化的方式在Instantiator这个类中
1 | java复制代码private T instantiate(Class<?> type) throws Exception { |
五、SpringBoot中注册的EnvironmentPostProcessor实现
接下来还是梳理下SpringBoot在spring.factories当中注册的EnvironmentPostProcessor实现:
1 | properties复制代码#spirng-boot.jar当中 |
这些实现类具体是干什么的,这个我暂时还看不太懂,应该跟具体的一些细节场景有关了。网上也暂时没有找到相关的资料。就不再去具体分析了。
不得不吐槽下,网上的帖子千篇一律只讲了怎么用EnvironmentPostProcesser读取额外的配置文件,但是这些机制还真没有人来分析过。
接下来简单跟踪了一下ConfigDataEnvironmentPostProcessor这个实现。发现他的实现过程中又引入了spring.factories中的org.springframework.boot.context.config.ConfigDataLoader功能机制。又是一个庞大复杂的功能机制,目前我也没太弄明白他的具体功能。以后了解到了再补上把。
本文转载自: 掘金