SpringBoot详解执行过程


Posted in Java/Android onJuly 15, 2022

每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。

问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?

下面我们查看run()方法内部的源码,核心代码具体如下:

@SpringBootApplication
public class SpringbootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
}

从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下

1.SpringApplication实例的初始化创建

查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
    // 把项目启动类.class设置为属性存储起来
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 判断当前webApplicationType应用的类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 设置初始化器(Initializer),最后会调用这些初始化器
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置监听器(Listener)
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 用于推断并设置项目main()方法启动的主程序启动类
        this.mainApplicationClass = this.deduceMainApplicationClass();

从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。

(1)this.webApplicationType = WebApplicationType.deduceFromClasspath()

用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)

(2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))

用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的应用初始化器类ApplicationContextInitializer。

(3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))

用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样,也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的监听器类ApplicationListener。

(4)this.mainApplicationClass = this.deduceMainApplicationClass()

用于推断并设置项目main()方法启动的主程序启动类

2.项目的初始化启动

分析完(new SpringApplication(primarySources)).run(args)源码前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码具体如下:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
    // 第一步:获取并启动监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();
        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    // 第二步:根据SpringApplicationRunListeners以及参数来准备环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
    // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
            Banner printedBanner = this.printBanner(environment);
    // 第三步:创建Spring容器
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, new Object[]{context});
    // 第四步:Spring容器前置处理
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    // 第五步:刷新容器
            this.refreshContext(context);
    // 第六步:Spring容器后置处理
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
    // 第七步:发出结束执行的事件
            listeners.started(context);
    // 返回容器
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }
        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners) null);
            throw new IllegalStateException(var9);
        }
    }

从上述源码可以看出,项目初始化启动过程大致包括以下部分:

第一步:获取并启动监听器

this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。

第二步:根据SpringApplicationRunListeners以及参数来准备环境

this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境进行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境

第三步:创建Spring容器

根据webApplicationType进行判断, 确定容器类型,如果该类型为SERVLET类型,会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置

第四步:Spring容器前置处理

这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础

第五步:刷新容器

开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭

第六步:Spring容器后置处理

扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

第七步:发出结束执行的事件

获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的

publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。

第八步:执行Runners

用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立即执行这些特定程序

下面,通过一个Spring Boot执行流程图,让大家更清晰的知道Spring Boot的整体执行流程和主要启动阶段:

SpringBoot详解执行过程

到此这篇关于SpringBoot详解执行过程的文章就介绍到这了,更多相关SpringBoot执行过程内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

Java/Android 相关文章推荐
详解Java实现设计模式之责任链模式
Jun 23 Java/Android
SpringBoot SpringEL表达式的使用
Jul 25 Java/Android
logback 实现给变量指定默认值
Aug 30 Java/Android
spring cloud 配置中心native配置方式
Sep 25 Java/Android
Spring Cloud 中@FeignClient注解中的contextId属性详解
Sep 25 Java/Android
关于maven依赖 ${xxx.version}报错问题
Jan 18 Java/Android
JVM之方法返回地址详解
Feb 28 Java/Android
Java练习之潜艇小游戏的实现
Mar 16 Java/Android
Android超详细讲解组件ScrollView的使用
Mar 31 Java/Android
零基础学java之带返回值的方法的定义和调用
Apr 10 Java/Android
Android Studio 计算器开发
May 20 Java/Android
HttpClient实现文件上传功能
Aug 14 Java/Android
spring 项目实现限流方法示例
SpringBoot详解整合Redis缓存方法
Jul 15 #Java/Android
maven 解包依赖项中的文件的解决方法
Jul 15 #Java/Android
SpringBoot详解自定义Stater的应用
Jul 15 #Java/Android
MyBatis XPathParser解析器使用范例详解
Jul 15 #Java/Android
SpringBoot接入钉钉自定义机器人预警通知
Jul 15 #Java/Android
SpringBoot深入分析讲解监听器模式下
Jul 15 #Java/Android
You might like
《PHP边学边教》(04.编写简易的通讯录――视频教程1)
2006/12/13 PHP
set_include_path在win和linux下的区别
2008/01/10 PHP
PHP 判断常量,变量和函数是否存在
2009/04/26 PHP
php5.3 不支持 session_register() 此函数已启用的解决方法
2013/11/12 PHP
php导出中文内容excel文件类实例
2015/07/06 PHP
Yii框架使用魔术方法实现跨文件调用功能示例
2017/05/20 PHP
PHP设计模式之装饰器模式定义与用法详解
2018/04/02 PHP
jQuery使用手册之三 CSS操作
2007/03/24 Javascript
CSS JavaScript 实现菜单功能 改进版
2008/12/09 Javascript
javascript中验证大写字母、数字和中文
2014/01/15 Javascript
原生js配合cookie制作保存路径的拖拽
2015/12/29 Javascript
JS对HTML表格进行增删改操作
2016/08/22 Javascript
Canvas实现放射线动画效果
2017/02/15 Javascript
Angular中$broadcast和$emit的使用方法详解
2017/05/22 Javascript
Vue 2.0入门基础知识之内部指令详解
2017/10/15 Javascript
在 Linux/Unix 中不重启 Vim 而重新加载 .vimrc 文件的流程
2018/03/21 Javascript
JS使用JSON.parse(),JSON.stringify()实现对对象的深拷贝功能分析
2019/03/06 Javascript
Vue 利用指令实现禁止反复发送请求的两种方法
2019/09/15 Javascript
[03:48]2014DOTA2 TI专访71DK夺冠不靠小组赛高排名
2014/07/11 DOTA
[01:20]DOTA2 齐天大圣至宝动态展示
2016/12/13 DOTA
用python删除java文件头上版权信息的方法
2014/07/31 Python
python在windows下实现ping操作并接收返回信息的方法
2015/03/20 Python
python实现大转盘抽奖效果
2019/01/22 Python
用python打印菱形的实操方法和代码
2019/06/25 Python
详解python实现交叉验证法与留出法
2019/07/11 Python
python UDP(udp)协议发送和接收的实例
2019/07/22 Python
css3的transition效果和transfor效果示例介绍
2013/10/30 HTML / CSS
地球上最先进的胡子和头发修剪器:Bevel
2018/01/23 全球购物
具有防紫外线功能的高性能钓鱼服装:Hook&Tackle
2018/08/16 全球购物
银河香水:Galaxy Perfume
2019/03/25 全球购物
豪华复古化妆:Besame Cosmetics
2019/09/06 全球购物
美术国培研修感言
2014/02/12 职场文书
组织鉴定材料
2014/06/02 职场文书
2014年幼儿园工作总结
2014/11/10 职场文书
2014财务年终工作总结
2014/12/08 职场文书
php引用传递
2021/04/01 PHP