上一次简单的分享了一下 Spring Boot 的源码导入教程,那么源码导入成功了,最重要的自然就是要阅读源码,今天简单的浅析一下在 Spring Boot 的整个生命周期当中,扮演重要角色的 SpringApplicationRunListener,换句话说,了解了 Spring Boot 的事件机制,也就全面的了解 SpringApplication.run 的生命周期。
注意:本文的源码分析均在 Spring Boot 2.7.x 分支下进行。
事件机制在 Spring Boot 中的运转机制
Spring Boot 的事件机制或者说 Spring 的 Event 事件模型都是基于观察者设计模式而实现,关于观察者设计模式的浅析可以参考文末「相关文章阅读」了解。
无论看啥,我们基本都要从 Spring Boot 的 main 运行方法看起,我们都知道 Spring Boot 运行下面这行代码来启动。
1 |
SpringApplication.run(Main.class, args); |
我们点进去源码,看到 run 方法实际的执行如下:
1 2 3 |
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } |
我们可以看到,在实际的 run 方法中,先是调用了 SpringApplication 构造,然后在执行 run 方法,SpringApplication 构造注释如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@SuppressWarnings({ "unchecked", "rawtypes" }) public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { // 赋值成员变量 resourceLoader this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); // 赋值成员变量 primarySources this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 推断 Web 应用类型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = new ArrayList<>( getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); // 加载并初始化 ApplicationContextInitializer 及相关实现类 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 加载并初始化 ApplicationListener 及相关实现类 // EventPublishingRunListener 已经实例化,在后续的事件发送中,能够触发对应的监听器的执行 // 发送 ApplicationStartingEvent 事件,触发对应的监听器的执行 // 基于观察者模式(Observer)实现,在 ApplicationContext 管理 Bean 生命周期的过程中,会将一些改变定义为事件(ApplicationEvent) // 通过 ApplicationListener 监听 ApplicationEvent,时间发布之后,ApplicationListener 用来对时间做出具体操作 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 推断 main 方法 this.mainApplicationClass = deduceMainApplicationClass(); } |
我们可以看到,SpringApplication 构造中通过 setListeners 将 ApplicationListener 进行初始化,与 ApplicationContextInitializer 相同,都是通过 SpringFactoriesLoader 获取 META-INF/spring.factories 中的对应配置,然后进行初始化(实例化),然后将结果集合添加到 SpringApplication 成员变量 org.springframework.boot.SpringApplication#listeners 中。说人话就是将 Spring Boot 系统定义的所有监听器(spring.factories 中配置的)都加载进来进行实例化,到时候实现事件监听。
再来看一下 run 方法的源码注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public ConfigurableApplicationContext run(String... args) { // 统计启动时长 long startTime = System.nanoTime(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; // 配置 headless 属性 configureHeadlessProperty(); // 获得 SpringApplicationRunListener 数组 // 可以理解为 SpringApplicationRunListeners 是 SpringApplicationRunListener 的容器 // 将 SpringApplicationRunListener 集合赋值到 listeners 成员变量 // SpringApplicationRunListener 的注册配置位于 spring.factories 文件。 SpringApplicationRunListeners listeners = getRunListeners(args); // 监听启动,即遍历数组中的每个元素并执行 listeners.starting(bootstrapContext, this.mainApplicationClass); try { // 创建 ApplicationArguments 对象 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 加载属性配置(application.yml 等) ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); // 打印 SpringBoot 专属 Banner Banner printedBanner = printBanner(environment); // 创建容器 context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 准备容器,组件对象之间进行关联 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 初始化容器 refreshContext(context); // 容器初始化的默认实现 afterRefresh(context, applicationArguments); Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); // 打印启动日志 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); } // 通知监听器容器已经启动完成 listeners.started(context, timeTakenToStartup); // 调用 ApplicationRunner 和 CommandLineRunner 的运行方法 callRunners(context, applicationArguments); } catch (Throwable ex) { // 异常处理 handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); // 监听器就绪 listeners.ready(context, timeTakenToReady); } catch (Throwable ex) { // 异常处理 handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; } |
在这个 run 方法中,我们可以看到,一开始,也是通过读取 spring.factories 配置文件的方式,实例化 SpringApplicationRunListener,并且将结果集合放在了 org.springframework.boot.SpringApplicationRunListeners#listeners 当中。这个 SpringApplicationRunListener 监听接口可以理解为 SpringBoot 生命周期事件机制的核心接口了,里面按照 Spring Boot 整个生命周期定义了一系列的方法,通过实现这些方法在启动各个流程的时候就可以加入指定的业务逻辑处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/** * SpringApplication 的 run 方法监听器,该接口提供一系列的方法,通过实现这些方法,可以在启动的各个流程和不同时期加入指定的业务逻辑处理。 * */ public interface SpringApplicationRunListener { /** * 当 run 方法第一次被执行时,会被立即调用,用于早期的初始化工作 */ default void starting(ConfigurableBootstrapContext bootstrapContext) { } /** * 当 environment 准备完成,在 ApplicationContext 创建之前,该方法被调用 */ default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { } /** * 当 ApplicationContext 构建完成,资源还未被加载时,该方法被调用 */ default void contextPrepared(ConfigurableApplicationContext context) { } /** * 当 ApplicationContext 加载完成,未被刷新之前,该方法被调用 */ default void contextLoaded(ConfigurableApplicationContext context) { } /** * 当 ApplicationContext 刷新并启动之后,ApplicationRunner 和 CommandLineRunner 未被调用之前,该方法被调用 */ default void started(ConfigurableApplicationContext context, Duration timeTaken) { started(context); } /** * 当所有工作准备就绪,run 方法执行完成之前,该方法被调用 */ default void ready(ConfigurableApplicationContext context, Duration timeTaken) { running(context); } /** * 应用程序出现错误,该方法被调用 */ default void failed(ConfigurableApplicationContext context, Throwable exception) { } } |
来个鱼骨头简单地感受一下
SpringApplicationRunListener 在 Spring Boot 中有一个默认的实现类 EventPublishingRunListener,它也就是 spring.factories 文件中配置的要实例化的类。
也就是说在默认情况下,Spring Boot 在初始化时触发的事件都是 EventPublishingRunListener 来实现的,在 EventPublishingRunListener 类中的构造方法,我们可以看到熟悉的 SpringApplication、args 以及一个事件广播器SimpleApplicationEventMulticaster,就是通过这个事件广播器将事件传递给所有的监听器。
1 2 3 4 5 6 7 8 |
public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } |
更多细节内容可以自己捋一遍源码,走一下这个过程。
Spring Boot 中默认的事件机制
上面简单的介绍了一下 Spring Boot 的事件机制基本原理,这里再来具体说一下在 Spring Boot 在启动的整个生命周期中都有哪些核心事件以及事件对应的监听都干了些什么,更好帮助我们了解 Spring Boot 都做了什么内容。
其实对应上面提到的 SpringApplicationRunListener 的七个声明的方法,自然对应的也就有七个对应的事件,Spring Boot 中所有的事件 Event 都继承自 SpringApplicationEvent,SpringApplicationEvent 继承自 Spring 的 ApplicationEvent,保证事件与 SpringApplication 的关联。
ApplicationStartingEvent
应用开始启动中事件,默认情况下,Spring Boot 中有三个监听器监听该事件。
LoggingApplicationListener
这个监听玩转的是 Spring Boot 的日志系统,在 LoggingApplicationListener 未启动之前,默认使用 LogBack 日志体系,这个监听实在启动中从系统中查找 LoggingSystem,并且进行初始化。源码可看 org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationStartingEvent。
BackgroundPreinitializer
此监听在该事件中不生效,详解见下方。
DelegatingApplicationListener
实际上,在应用启动中这个阶段,这个监听没干啥,在 org.springframework.boot.context.config.DelegatingApplicationListener#onApplicationEvent 中判断了当前事件是否是「ApplicationEnvironmentPreparedEvent」才真正生效。
ApplicationEnvironmentPreparedEvent
环境准备完成事件,默认情况下,Spring Boot 中有六个监听器监听该事件。
EnvironmentPostProcessorApplicationListener
就是加载配置文件,application.yml、application.properties。
AnsiOutputApplicationListener
让日志文件或者是控制台支持彩色输出,是日志可读性提高。
LoggingApplicationListener
上面是 LoggingSystem 初始化,这次是调用 org.springframework.boot.context.logging.LoggingApplicationListener#initialize 进行日志初始化操作,包括参数设置、日志文件、日志级别等。
BackgroundPreinitializer
Spring Boot 对于一些比较耗时的任务会使用一个后台线程提前触发它们开始进行初始化操作,即「预初始化」,在源码中 org.springframework.boot.autoconfigure.BackgroundPreinitializer#performPreinitialization 我们可以看到,预初始化包含了 ConversionServiceInitializer(转换器、格式化)、ValidationInitializer(校验)、MessageConverterInitializer(HTTP 请求和响应)、JacksonInitializer(Jackson 配置)、CharsetInitializer(字符集 )。
DelegatingApplicationListener
在上面提到,这个监听在此次事件中才正式生效,主要作用是读取配置文件中的「context.listener.classes」属性,也就是说通过这个监听初始化配置该属性的监听器,然后把「ApplicationStartedEvent」这个事件广播给他们,从而实现一些其他业务逻辑处理。
FileEncodingApplicationListener
检测编码。
ApplicationContextInitializedEvent
该事件下默认的监听都没干什么,但是你要了解前面的 run 方法生命周期过程中,这里都做了些什么。
BackgroundPreinitializer
没干啥。
DelegatingApplicationListener
没干啥。
ApplicationPreparedEvent
上下文已经实例化好事件。
EnvironmentPostProcessorApplicationListener
执行 org.springframework.boot.env.EnvironmentPostProcessorApplicationListener#finish 环境加载完成业务。
LoggingApplicationListener
本次是将 LoggingSystem 注册到 Bean 工厂当中。
BackgroundPreinitializer
啥也没干。
DelegatingApplicationListener
啥也没干。
ApplicationStartedEvent
应用成功启动事件,当然 BackgroundPreinitializer、DelegatingApplicationListener 两个监听也没什么动作,该周期最重要的事情其实是启动 Spring 容器,实例化单例 Bean。
ApplicationReadyEvent
应用已准备好事件。
SpringApplicationAdminMXBeanRegistrar
Spring Boot 有一个远程管理 Spring Boot 的功能,SpringApplicationAdminMXBean 就是 JMX 中的 MBean,也就是 Jconsole 中看到的那个,而这个监听器就是负责将 ready 属性从 false 变为 true,通知 Spring Boot 已经启动好了。
BackgroundPreinitializer
在此监听中,对于 ApplicationReadyEvent 事件有一个 CountDownLatch 的 await 阻塞,我们都知道在 CountDownLatch 中 await 可以让主线程阻塞等待,然后 countDown 减到 0 的时候,主线程执行,这里的目的就是在应用已经准备好之后,让预热也执行完毕,虽然预初始化的东西耗时,咱们让他提前启动,但是应用最终启动好了,预热的东西也得启动好,所以这里会进行一下阻塞,让计数器为 0 在执行后续的主线程操作。
DelegatingApplicationListener
啥也没干。
ApplicationFailedEvent
EnvironmentPostProcessorApplicationListener
调用 org.springframework.boot.env.EnvironmentPostProcessorApplicationListener#finish 将系统日志归档。
LoggingApplicationListener
org.springframework.boot.context.logging.LoggingApplicationListener#cleanupLoggingSystem,执行日志清理工作
BackgroundPreinitializer
同「ApplicationReadyEvent」事件,都是执行 preinitializationComplete.await();
DelegatingApplicationListener
啥也没干。
ConditionEvaluationReportLoggingListener
输出错误日志。
好了,经过上面的分析,其实我们发现有一个问题,就是在整个事件的生命周期中,有很多「啥也没干」的事件监听也参与了事件触发,老四个人觉得算是多带来了一些无效的处理而带来系统消耗。
相关文章阅读
- 《IntelliJ IDEA 导入 Spring Boot 源码教程》
- 《浅析设计模式第十四章之观察者模式》
- 《浅析Java反射系列相关基础知识(上)之类的加载以及反射的基本应用》
- 《阿里巴巴Java开发手册第六章-二方库依赖篇》
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击「给你买杜蕾斯」),也可以加入本站封闭式交流论坛「DownHub」开启新世界的大门,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。