您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

SpringBoot运行流程源码分析:run方法流程及监听器

时间:2020-10-29 09:34:10  来源:  作者:

SpringBoot运行流程源码分析

上一章中我们分析了 SpringApplication 类实例化的源代码,在此过程中完成了基本配置文件的加载和实例化。当 SpringApplication 对象被创建之后, 通过调用其 run 方法来进行SpringBoot 的启动和运行,至此正式开启了 SpringApplication 的生命周期。
本章介绍的内容同样是 Spring Boot 运行的核心流程之一,我们将会围绕 SpringApplicationRunListeners、ApplicationArguments、ConfigurableEnvironment 以及 应用上下文信息等部分展开讲解。

run方法核心流程

在分析和学习整个 run 方法的源代码及操作之前,我们先通过图 4-1 所示的流程图来看一下SpringApplication 调用的 run 方法处理的核心操作都包含哪些。然后,后面的章节我们再逐步细化分析每个过程中的源代码实现。

SpringBoot运行流程源码分析:run方法流程及监听器

 

上面的流程图可以看出,SpringApplication 在 run 方法中重 点做了以下操作。

.获取监听器和参数配置。

.打印 Banner 信息。

.创建并初始化容器。

监听器发送通知。

当然,除了核心操作,run 方法运行过程中还涉及启动时长统计、异常报告、启动日志、异常处理等辅助操作。

对照流程图,我们再来整体看一下入口 run 方法的源代码,核心部分的功能已通过注释的形式进行说明。

public ConfigurableApplicationContext run(String... args) {
//创建 stopwatch 对象, 用于统 i 计 run 方法启动时长
StopWatch stopWatch = new StopWatch();
//启动统计
stopwatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> except ionReporters = new Arraylis
t<>();
//配置 headless 属性
configureHeadlessProperty();
//获得 SpringAppl icat ionRunL istener 数组
//该数组封装 FSpringAppl icat ionRunL isteners 对象的 L isteners 中
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动监听,遍历 SpringAppl icat ionRunL istener 数组每个元素,并执行
listeners .starting();
try {创建 Appl icat ionArguments 对象
ApplicationArguments applicationArguments = new DefaultApplicationArgum
ents(
args);
//加载属性配置,包括所有的配置属性(如: appl icat ion. properties 中和外部的属
性配置)
Conf igurableEnvironment environment = prepareEnvironment(listeners,
applicationArg
uments);
configureIgnoreBeanInfo( environment);
//打 Banner
Banner printedBanner = printBanner ( environment);
//创建容器
context = createApplicationContext();
//异常报告器
exceptionReporters = getSpringFactoriesInstances(
Spr ingBootExcept ionReporter .class ,
new Class[] { ConfigurableApplicat ionContext.class }, context);
//准备容器,组件对象之间进行关联
prepareContext(context, environment, listeners, applicationArguments, p
rintedBanner);
// 初始化容器
refreshContext(context);
//初始化操作之后执行,默认实现为空
afterRefresh( context, applicationArguments);
//停止时长统计
stopWatch. stop();
//打印启动日志
if (this. logStartupInfo) {
new StartupInfoLogger(this . mainApplicat ionClass)
.logStarted(getApplicationLog(), stopwatch);
//通知监昕器:容器启动完成
listeners . started( context);
//调用 Appl icat ionRunner 和 CommandL ineRunner 的运行方法。
callRunners (context, applicat ionArguments);
} catch (Throwable ex)//异常处理
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
try {
//通知监听器:容器正在运行
listeners . running( context);
} catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
return context;
}

在整体了解了整个 run 方法运行流程及核心代码后,下面我们针对具体的程进行讲解。

SpringApplicationRunListener 监听器

监听器的配置与加载

让我们忽略 Spring Boot 计 时和统计的辅助功能,直接来看 SpringApplicationRunListeners获取和使用 SpringApplicationRunL isteners可以理解为一个 SpringApplicationRunListener的容器,它将 SpringApplicationRunListener 的集合以构造方法传入, 并赋值给其 listeners成员变量,然后提供了针对 listeners 成员变量的各种遍历操作方法,比如,遍历集合并调用对应的 starting、started、 running 等方法。

SpringApplicationRunListeners 的构建很简单,图 4-1 中调用的 getRunListeners 方法也只是调用了它的构造方法。SpringApplication 中 getRunListeners 方法代码如下。

private SpringApplicationRunListeners getRunListeners(String[] args) {
//构造 Class 数组
Class<?>[] types = new Class<?>[] { SpringApplication. class, String[].cla
/调用 SpringAppl icat ionRunL isteners 构造方法
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstan
ces
SpringApplicationRunListener .class, types, this, args));}
SpringApplicationRunListeners 构 造 方 法 的 第 二 个 参 数 便 是 SpringApplicationRunL
istener 的 集 合 , SpringApplication 中 调 用 构 造 方 法 时 该 参 数 是 通 过
getSpringFactoriesInstances 方法获取的,代码如下。
private <T> Collection<T> getSpringF actoriesInstances(Class<T> type,
Class<?>[] parameterTypes, object... args) {
//加 META- TNE/sprina. factori es 中对应监听器的配
并将结果存 F'set 中(去重)
Set<Strine> names= newLinkedHashSet<>(
个命的能直,
SpringF actoriesl oader. loadFactoryNames(type, classloader));
/文悯化监听器
List<T> instances = createSpringFactories Instances (type, parameterTypes,
classLoader, args, nam
排序
Annotat ionAwareOrderComparator .sort(instances);
eturn instances ; }

通过方法名便可得知,getSpringFactoriesInstances 是用来获取 factories 配置文件中的注册类,并进行实例化操作。

关于通过 SpringFactoriesL oader 获取 META-INF/spring.factories 中对应的配置,前面章节已经多次提到,这里不再赘述。

SpringApplicationRunListener 的注册配置位于 spring-boot 项目中的 spring.factories 文件内,Spring Boot 默认仅有- -个监听器进行了注册,关于其功能后面会专门讲到。

# RunListeners
org. springframework. boot.SpringApplicationRunListener=
org. springframework. boot. context.event.EventPublishingRunL istener
我们继续看实例化监听器的方法 createSpringFactoriesInstances 的源代码。
private <T> List<T> createSpringFactoriesInstances(Class<T> type
Class<?>[] parameterType
s, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names)
Class<?> instanceClass = ClassUtils . forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
//获取有参构造器
Constructor<?> constructor = instanceClass . getDeclaredConstructor(par
ameterTypes);
T instance = (T) BeanUtils. instant iateClass(constructor, args);
instances . add(instance);
return instances;
}

在上面的代码中,实例化监听器时需要有一-个默认的构造方法, 且构造方法的参数为Class<?>[ ] parameterTypes。我们向上追踪该参数的来源,会发现该参数的值为 Class 数组 , 数 组 的 内 容 依 次 为 SpringApplication.class 和 String[ ].class 。 也 就 是 说 ,SpringApplicationRunL istener 的实现类必须有默认的构造方法,且构造方法的参数必须依次为 SpringApplication 和 String[ ]类型。

SpringApplicationRunListener 源码解析

接口 SpringApplicationRunListener 是 SpringApplication 的 run 方法监听器。上节提到了SpringApplicationRunListener 通过 SpringFactoriesL oader 加载,并且必须声明一个公共构造函数,该函数接收 SpringApplication 实例和 String[ ]的参数,而且每次运行都会创建一个新的实例。

SpringApplicationRunListener 提供了-系列的方法,用户可以通过回调这些方法,在启动各个流程时加入指定的逻辑处理。下面我们对照源代码和注释来了解一下该接口都定义了哪些待实现的方法及功能。

public interface SpringApplicationRunL istener {
// 当 run 方法第- 次被执行时,会被立即调用,可用于非常早期的初始化工作 default void
starting(){};
//当 environment 准备完成, 在 Appl icationContext 创建之前, 该方法被调用 default void
environmentPrepared(Conf igurableEnvironment environment) {};
//当 ApplicationContext 构建完成, 资源还未被加载时, 该方法被调用 default void
contextPrepared(ConfigurableApplicationContext context) {}
// 当 Appl icat ionContext 加 载 完 成 , 未 被 刷 新 之 前 , 该 方 法 被 调 用 default void
contextLoaded(ConfigurableApplicationContext context) {};
//当 ApplicationContext 刷新并启动之后, CommandL ineRunner 和 Appl icat ionRunner 未
被调用之前, 该方法被调用
default void started(Conf igurableApplicationContext context) {};
//当所有准备工作就绪,run 方法执行完成之前, 该方法被调用
default void running(ConfigurableApplicationContext context) {};
//当应用程序出现错误时,该方法被调用
default void failed(ConfigurableApplicationContext context, Throwable exception) {};
}

我们通过源代码可以看出,SpringApplicationRunListener 为 run 方法提供了各个运行阶段的监听事件处理功能。需要注意的是,该版本中的接口方法定义使用了 JAVA8 的新特性,方法已采用 default 声明并实现空方法体,表示这个方法的默认实现,子类可以直接调用该方法,也可以选择重写或者不重写。

图 4-2 展示了在整个 run 方法的生命周期中 SpringApplicationRunListener 的所有方法所处的位置,该图可以帮助我们更好地学习 run 方法的运行流程。在前面 run 方法的代码中已经看到相关监听方法被调用,后续的源代码中也将涉及对应方法的调用,我们可参考此图以便理解和加深记忆。

SpringBoot运行流程源码分析:run方法流程及监听器

 

实现类 EventPublishingRunListener

EventPublishingRunL istener 是 SpringBoot 中针对 SpringApplicationRunListener 接口的唯内建实现EventPublishingRunL istener使用内置的SimpleApplicationEventMulticaster来广播在上下文刷新之前触发的事件。

默认情况下,Spring Boot在初始化过程中触发的事件也是交由EventPublishingRunListener来代理实现的。EventPublishingRunListener 的构造方法如下。

public EventPublishingRunListener(SpringApplication application, Stringa
rgs) {
this.application = application;
this.args = args;
//创建 SimpleAppl icat ionEventMulticaster/播器
this . initialMulticaster = new SimpleApplicationEventMulticaster();
//遍历 Appl icat ionL istener 并关联 S impleAppl icat ionEventMulticaster
for (ApplicationListener<?> listener : application. getListeners()) {
this. initialMulticaster . addApplicationListener(listener);
}

通过源代码可以看出,该类的构造方法符合 SpringApplicationRunListener 所需的构造方法参数要求,该方法依次传递了 SpringApplication 和 String[ ]类型。在构造方法中初始化了该类的 3 个成员变量。

-application :类 型为 SpringApplication ,是当前运行的 SpringApplication 实例。

-args:启动程序时的命令参数。

-initialMulticaster:类 型为 SimpleApplicationEventMulticaster,事件广播器。

Spring Boot 完成基本的初始化之后,会遍历 SpringApplication 的所有 ApplicationListener实 例 , 并 将 它 们 与 SimpleApplicationEventMulticaster 进 行 关 联 , 方 便SimpleApplicationEvent-Multicaster 后续将事件传递给所有的监听器。

EventPublishingRunListener 针对不同的事件提供了不同的处理方法,但它们的处理流程基本相同。

SpringBoot运行流程源码分析:run方法流程及监听器

 

下面我们根据图 4-3 所示的流程图梳理一下 整个事件的流程。

.程序启动到某个步骤后,调用 EventPublishingRunListener 的某个方法。

EventPublishingRunListener 的具体方法将 application 参数和 args 参数封装到对应的事件中。这里的事件均为 SpringApplicationEvent 的实现类。

.通过成员变量 initialMulticaster 的 multicastEvent 方法对事件进行广播,或通过该方法的ConfigurableApplicationContext 参数的 publishEvent 方法来对事件进行发布。

.对应的 ApplicationListener 被触发,执行相应的业务逻辑。

下面是 starting 方法的源代码,可对照上述流程进行理解。该方法其他功能类似,代码不再展示。

public void starting() {
this. initialMulticaster . multicastEvent(
new ApplicationStartingEvent (this. application, this.args));}

在上述源代码中你是否发现-个问题,某些方法是通过 initialMulticaster 的 multicastEvent进行事件的广播,某些方法是通过 context 参数的 publishEvent 方法来进行发布的。这是为什么呢?在解决这个疑问之前,我们先看一个比较特殊的方法 contextL oaded 的源代码。

public void contextLoaded( ConfigurableApplicationContext context) {
//遍历 application 中的所有监听器实现类
for (ApplicationL istener<?> listener : this . application. getL isteners()) {
//如果为 Appl icationContextAware,则将上:下文信息设置到该监听器内
if (listener instanceof Applicat ionContextAware) {
((ApplicationContextAware) listener). setApplicationContext( context);//将 application 中的监听器实现类全部添加到上下文中
context . addApplicationL istener(listener);
// / "播事件 Appl icationPreparedEvent
this. initialMulticaster . multicastEvent (
new ApplicationPreparedEvent(this.application, this.args, context));
}

contextLoaded 方法在发布事件之前做了两件事:第一,遍历 application 的所有监听器实现类,如果该实现类还实现了 ApplicationContextAware 接口,则将上下文信息设置到该监听器内;第二,将 application 中的监听器实现类全部添加到上下文中。最后一步才是调用事件广播。

也正是这个方法形成了不同事件广播形式的分水岭,在此方法之前执行的事件广播都是通过multicastEvent 来进行的,而该方法之后的方法则均采用 publishEvent 来执行。这是因为只有到了 contextL oaded 方法之后,上下文才算初始化完成,才可通过它的 publishEvent 方法来进行事件的发布。

自定义 SpringApplicationRunListener

上面我们一起学习了 SpringApplicationRunListener 的基本功能及实现类的源代码,现在我们自定义-个 SpringApplicationRunListener 的实现类。通过在该实现类中回调方法来处理自己的业务逻辑。

自定义实现类比较简单,可像通常实现一个接口一样,先创建类 MyApplicationRunListener,实现接口 SpringApplicationRunListener 及其方法。然后在对应的方法内实现自己的业务逻辑,以下示例代码中只简单打印方法名称。与普通接口实现唯一不同的是,这里需要指定一-个参数依次为 SpringApplication 和 String[ ]的构造方法,不然在使用时会直接报错。

public class MyApplicationRunListener implements SpringApplicationRunListen
er {
public MyApplicationRunListener ( SpringApplication application, String[]
args){
System. out . println("MyApplicationRunListener constructed function");
@Override
public void starting() {
System. out . println("starting...");
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System. out. println(" environmentPrepared...");
//在此省略掉其他方法的实现 }

当定义好实现类之后,像注册其他监听器一样, 程序在 spring.factories 中进行注册配置。如果项目中没有 spring.factories 文件,也可在 resources 目录下先创建 META-INF 目录,然后在该目录下创建文件 sprig.factories。

spring.factories 中配置格式如下。

# Run Listeners
org. springframework. boot . SpringApplicationRunListener=
com. secbro2. learn. listener . MyApplicationRunListener

启动 Spring Boot 项目,你会发现在不同阶段打印出不同的日志,这说明该实现类的方法已经被调用。



Tags:SpringBoot   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  Tags: SpringBoot  点击:(20)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  Tags: SpringBoot  点击:(25)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  Tags: SpringBoot  点击:(55)  评论:(0)  加入收藏
1. 介绍1.1 介绍今天开始我们来学习Java操作MySQL数据库的技巧,Java操作MySQL是借助JdbcTemplate这个对象来实现的。JdbcTemplate是一个多数据库集中解决方案,而我们今天只讲...【详细内容】
2021-11-05  Tags: SpringBoot  点击:(30)  评论:(0)  加入收藏
SpringBoot中的Controller注册本篇将会以Servlet为切入点,通过源码来看web容器中的Controller是如何注册到HandlerMapping中。请求来了之后,web容器是如何根据请求路径找到对...【详细内容】
2021-11-04  Tags: SpringBoot  点击:(52)  评论:(0)  加入收藏
环境:Springboot2.4.11环境配置接下来的演示都是基于如下接口进行。@RestController@RequestMapping("/exceptions")public class ExceptionsController { @GetMapping(...【详细内容】
2021-10-11  Tags: SpringBoot  点击:(41)  评论:(0)  加入收藏
SpringBoot项目默认使用logback, 已经内置了 logback 的相关jar包,会从resource包下查找logback.xml, logback 文件格式范本 可直接复制使用,有控制台 info.log error.log三个...【详细内容】
2021-10-09  Tags: SpringBoot  点击:(50)  评论:(0)  加入收藏
环境:Springboot2.4.10当应用程序启动时,Spring Boot将自动从以下位置查找并加载application.properties和application.yaml文件: 从Classpath类路径classpath的根类路径classp...【详细内容】
2021-09-26  Tags: SpringBoot  点击:(76)  评论:(0)  加入收藏
搭建基础1. Intellij IDEA 2. jdk1.8 3. maven3.6.3搭建方式(1)在线创建项目Spring Boot 官方提供的一种创建方式,在浏览器中访问如下网址: https://start.spring.io/在打开的页...【详细内容】
2021-09-14  Tags: SpringBoot  点击:(78)  评论:(0)  加入收藏
最近开发项目的时候需要用到对象的属性拷贝,以前也有用过一些复制框架,比如spring的 BeanUtils.copyProperties等方式,但总是不尽如人意,最近发现使用orika进行对象拷贝挺好用的...【详细内容】
2021-08-27  Tags: SpringBoot  点击:(231)  评论:(0)  加入收藏
▌简易百科推荐
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(6)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(11)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(14)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(18)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(26)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  小程序建站    Tags:SpringBoot   点击:(55)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条