SpringBoot源码解读与原理分析(十八)启动SpringApplication逻辑分析

文章目录

    • 6.2 启动SpringApplication
      • 6.2.1 前置准备
        • 6.2.1.1 计时器对象的使用
        • 6.2.1.2 awt的设置
        • 6.2.1.3 对比SpringBoot 2.0.x-2.2.x
        • 6.2.1.4 对比SpringBoot 2.4.x
      • 6.2.2 获取SpringApplicationRunListeners
        • 6.2.2.1 EventPublishingRunListener
        • 6.2.2.2 与其他版本的对比
      • 6.2.3 准备运行时环境
        • 6.2.3.1 创建运行时环境
        • 6.2.3.2 配置运行时环境
        • 6.2.3.3 Environment与SpringApplication的绑定

在SpringApplication的构造方法执行完毕后,SpringApplication对象的创建工作就完成了,下一步会执行SpringApplication的 run方法,从而真正启动SpringBoot应用。

6.2 启动SpringApplication

整个run方法可以分为13步,本节梳理一下前面3步:

代码清单1SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
    //6.2.1 前置准备
    //6.2.1.1 计时器对象的使用
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    //6.2.1.2 awt的设置
    configureHeadlessProperty();
    //6.2.2 获取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        //6.2.3 准备运行时环境
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        
        //...
        
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    
    try {
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

6.2.1 前置准备

6.2.1.1 计时器对象的使用
代码清单2SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
    //6.2.1 前置准备
    //6.2.1.1 计时器对象的使用
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    //...
    try {
        // ...
        stopWatch.stop();
        // ...
    } catch (Throwable ex) {
        // ...
    }
    // ...
}

由 代码清单2 可知,run方法的第一步前置准备中首先会初始化一个StopWatch对象。

Simple stop watch, allowing for timing of a number of tasks, exposing total running time and running time for each named task.
简单的秒表,允许计时多个任务,暴露总运行时间和每个命名任务的运行时间。

This class is normally used to verify performance during proof-of-concept work and in development, rather than as part of production applications.
这个类常用于在概念验证和开发过程中验证性能,而不是作为生产应用的一部分。

由javadoc可知,StopWatch是一个用于验证性能的秒表。换言之,StopWatch用来监控SpringBoot应用启动的时间。在run方法刚启动时,执行stopWatch.start()开始计时,当后面的try块即将执行完毕,执行stopWatch.stop()停止计时,由此计算出SpringBoot应用启动的大体耗时。

6.2.1.2 awt的设置
代码清单3SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
    //6.2.1 前置准备
    // ...
    //6.2.1.2 awt的设置
    configureHeadlessProperty();
    // ...
}

由 代码清单3 可知,前置准备中会接着执行configureHeadlessProperty方法。

代码清单4SpringApplication.java

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
        System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

由 代码清单4 可知,该方法从System获取了一个配置属性 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS ,然后又把获取到的值重新设置回System。

这一步的逻辑看起来很诡异,但从JDK的System类源码可以看出这样做的目的。

代码清单5System.java

public static String getProperty(String key) {
    // ...
    // 从Properties中取值,如果没有取到则返回null
    return props.getProperty(key);
}

public static String getProperty(String key, String def) {
    // ...
    // 从Properties中取值,如果没有取到则返回默认值
    return props.getProperty(key, def);
}

public static String setProperty(String key, String value) {
    // ...
    // 设置key和value
    return (String) props.setProperty(key, value);
}

由 代码清单4、5 可知,configureHeadlessProperty方法调用的是返回默认值的那一个getProperty方法,这样做是为了给配置属性 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 设置一个默认值。

而常量 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 的值是“java.awt.headless”,是指显示器缺失,因此configureHeadlessProperty方法的真正作用是:设置该配置属性后,在启动SpringBoot应用时即使没有检测到显示器也允许其继续启动。(部署应用的服务器大多没有显示器,也可以运行服务)

6.2.1.3 对比SpringBoot 2.0.x-2.2.x

SpringBoot 2.0到2.2版本中(本文源码版本是2.3.11),在前置准备阶段还有一行代码:

代码清单6SpringApplication.java

// SpringBoot 2.0.x-2.2.x
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    // 创建异常分析器集合
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
		
    // ...
}

由 代码清单6 可知,异常分析器集合的泛型是SpringBootExceptionReporter类,它是一个用于支持SpringApplication启动错误报告的接口,本身也是利用SPI机制加载的。

SpringBootExceptionReporter默认只有一个实现类:FailureAnalyzers。关于FailureAnalyzers的设计,在 SpringBoot源码解读与原理分析(十四)SpringApplication的总体设计 4.1.1 启动失败的错误报告 中已详细梳理过。

6.2.1.4 对比SpringBoot 2.4.x

由于SpringBoot 2.4.0版本后引入了新的API:BootstrapRegistry,在前置准备阶段会创建DefaultBootstrapContext,并用所有的BootstrapRegistryInitializer。

代码清单7SpringApplication.java

// SpringBoot 2.4.x
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 创建DefaultBootstrapContext
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    //...
}

private DefaultBootstrapContext createBootstrapContext() {
    DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
    this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext));
    return bootstrapContext;
}

6.2.2 获取SpringApplicationRunListeners

代码清单8SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
    //...
    //6.2.2 获取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    //...
}

由 代码清单8 可知,run方法的第二步是获取监听器。在 SpringBoot源码解读与原理分析(十四)SpringApplication的总体设计 4.1.5 监听与回调 中已详细梳理过SpringApplicationRunListeners的设计。

这里关注一下默认加载的监听器实现。借助IDEA的Debug,可以发现默认加载的监听器实现是EventPublishingRunListener。

默认加载的监听器实现

6.2.2.1 EventPublishingRunListener
代码清单9EventPublishingRunListener.java

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    private final SpringApplication application;
    private final String[] args;
    private final SimpleApplicationEventMulticaster initialMulticaster;
    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);
        }
    }
    //...
}

从类名上理解,EventPublishingRunListener具备事件发布的能力,而发布事件必定要配合事件广播器来实现。由 代码清单9 可知,EventPublishingRunListener构造完成之后,其内部已经初始化了一个事件广播器SimpleApplicationEventMulticaster,并整合了现阶段可以获取到的所有ApplicationListener。

EventPublishingRunListener实现了SpringApplicationRunListener接口的各个方法,在每个方法中都有一个特殊的事件被广播。

代码清单10EventPublishingRunListener.java

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    @Override
    public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }
    
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster
            .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }
    
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        this.initialMulticaster
            .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
    }
    
    //...
}
6.2.2.2 与其他版本的对比

SpringBoot 1.x并没有把所有的切入时机都考虑到,只有startingenvironmentPreparedcontextPreparedcontextLoaded,另外还有一个finished

SpringBoot 2.x在原来基础上扩展了startedrunningfailed,删掉了finished

SpringBoot 2.4.x中,引入了BootstrapContext。由于这个组件会在ApplicationContext初始化之前起作用,而SpringApplicationRunListener的前两个事件触发时BootstrapContext处于有效状态,因此前两个事件把BootstrapContext作为触发入参,以便在扩展事件监听逻辑时有更多API可以获取,进而有机会做更多的事情。

代码清单11SpringApplicationRunListener.java

default void starting(ConfigurableBootstrapContext bootstrapContext) {
    starting();
}

default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
    environmentPrepared(environment);
}

6.2.3 准备运行时环境

代码清单12SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
    //6.2.1 前置准备
    //6.2.1.1 计时器对象的使用
    //6.2.1.2 awt的设置
    //6.2.2 获取SpringApplicationRunListeners
    try {
        //6.2.3 准备运行时环境
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        //...
}

获取监听器之后,第三步是准备运行时环境,对应的是Environment抽象。Environment的设计,在 SpringBoot源码解读与原理分析(十)Environment 已详细梳理过。

代码清单13SpringApplication.java

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // 创建、配置运行时环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 回调SpringApplicationRunListener的environmentPrepared方法
    listeners.environmentPrepared(environment);
    // 环境与应用绑定
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
        deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

由 代码清单13 可知,准备运行时环境主要分为3步:创建、配置运行时环境;回调SpringApplicationRunListener的environmentPrepared方法;环境与应用绑定。

6.2.3.1 创建运行时环境

在 SpringBoot源码解读与原理分析(十)Environment 3.4.2 Environment的结构与设计 中,借助IDEA生成Environment接口的上下级继承和派生关系。

Environment的结构与设计
其中有3个Environment的落地实现类,分别对应普通环境的StandardEnvironment、支持Servlet环境的StandardServletEnvironment和支持Reactive环境的StandardReactiveWebEnvironment。

代码清单14SpringApplication.java

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
    }
}

由 代码清单14 可知,在创建运行时环境,就是根据之前推断好的Web类型来决定使用何种Environment的落地实现。

6.2.3.2 配置运行时环境
代码清单15SpringApplication.java

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        // 获取全局共享的ApplicationConversionService实例
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
    environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    // 将命令行参数封装为PropertySource
    configurePropertySources(environment, args);
    // 编程式添加激活的profile
    configureProfiles(environment, args);
}

由 代码清单15 可知,配置运行时环境包括配置ConversionService、将命令行参数封装为PropertySource、编程式添加激活的profile。

6.2.3.3 Environment与SpringApplication的绑定
代码清单16SpringApplication.java

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    } catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}

由 代码清单16 可知,Binder类将Environment中的一些以 spring.main 开头的配置属性映射到SpringApplication中,而SpringApplication的部分属性恰好就是以 spring.main 开头的,例如spring.main.lazy-initialization=true

因此,bindToSpringApplication方法的作用就是将application.properties中的属性赋值到SpringApplication对象中。

······

本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析。

下节继续梳理run方法的剩余步骤:IOC容器的创建和初始化、IOC容器刷新后的回调。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/372742.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TP框架 之think-auth权限认证

一、安装think-auth composer require 5ini99/think-auth二、数据表 -- ---------------------------- -- think_auth_rule&#xff0c;规则表&#xff0c; -- id:主键&#xff0c;name&#xff1a;规则唯一标识, title&#xff1a;规则中文名称 status 状态&#xff1a;为1正常…

我在代码随想录|写代码Day26 |回溯算法|332. 重新安排行程 , 51. N皇后 , 37. 解数独

学习目标&#xff1a; 博主介绍: 27dCnc 专题 : 数据结构帮助小白快速入门 &#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d; ☆*: .&#xff61;. o(≧▽≦)…

私域市场如何突破?解锁高效转化的三个核心要素!

一、私域电商三要素 一是私域、二是社交、三是电商。 私域就是承载用户的地方&#xff0c;比如微信&#xff0c;然后做好私域运营。 社交就是通过内容触达用户于用户建立社交关系。 电商就是通过私域卖产品给用户。 私域电商有几个公式&#xff1a; 社交红利信息关系链互…

redis(6)

文章目录 一、redis clusterRedis Cluster 工作原理Redis cluster 基本架构Redis cluster主从架构Redis Cluster 部署架构说明部署方式介绍 原生命令手动部署原生命令实战案例&#xff1a;利用原生命令手动部署redis cluster 实战案例&#xff1a;基于Redis 5 的redis cluster部…

Nicn的刷题日常之获得月份天数

目录 1.题目描述 描述 输入描述&#xff1a; 输出描述&#xff1a; 示例1 2.解题 1.题目描述 描述 KiKi想获得某年某月有多少天&#xff0c;请帮他编程实现。输入年份和月份&#xff0c;计算这一年这个月有多少天。 输入描述&#xff1a; 多组输入&#xff0c;一行有两…

外包干了10个月,技术退步明显...

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

如何把大容量10G的文件分享给别人?整理了3个简单的方法~

如果文件过大&#xff0c;比如10G的文件发送起来简直问题重重&#xff0c;不仅费时费流量而且还很可能在发送的中途失败&#xff0c;这时候就需要借助一些压缩软件对文件进行压缩&#xff0c;下面就向大家介绍3个好用的压缩软件~ 方法一&#xff1a;使用嗨格式压缩大师压缩后发…

深入理解Java中的二叉树

目录 一、什么是二叉树? 二、二叉树的主要类型 三、二叉树的实现 四、二叉树的应用 五、关于二叉树的题目 引言: 二叉树是计算机科学中常用的一种数据结构&#xff0c;它是由节点组成的层级结构&#xff0c;每个节点最多有两个子节点。在Java编程语言中&#xff0c;二…

架构学习(三):scrapy-redis源码分析并实现自定义初始请求

scrapy-redis源码分析并实现自定义初始请求 前言关卡&#xff1a;如何自定义初始请求背景思考简单又粗暴的方式源码分析 结束 前言 通过这篇文章架构学习(二)&#xff1a;原生scrapy如何接入scrapy-redis&#xff0c;初步入局分布式&#xff0c;我们正式开启scrapy-redis分布式…

C语言递归与迭代并举:双重视角下的C语言阶乘计算实现

引言 计算一个正整数的阶乘是常见的数学问题。阶乘的定义为&#xff1a;n的阶乘&#xff08;记作n!&#xff09;是所有小于及等于n的正整数的乘积。例如&#xff0c;5的阶乘&#xff08;5!&#xff09;就是54321120。下面我们将通过一个使用递归方法实现阶乘的C语言代码示例&am…

Qt|实现时间选择小功能

在软件开发过程中&#xff0c;QtDesigner系统给出的控件很多时候都无法满足炫酷的效果&#xff0c;前一段时间需要用Qt实现选择时间的小功能&#xff0c;今天为大家分享一下&#xff01; 首先看一下时间效果吧&#xff01; 如果有需要继续往下看下去哟~ 功能 1&#xff1a;开…

linux 05重定向和管道管理

01.重定向 例子&#xff1a; 关键字 > date 中的数据要写入 887.txt 02.FD 进程的句柄文件 进程的信息的传输&#xff1a; porcess 会有 0 号文件来接收键盘的信息 1 号文件 向终端 来输出信息 FD文件存储在proc文件中&#xff0c;可以看看 举个例子&#xff1a; 查看pro…

计算机网络原理基础

目录 前言&#xff1a; 1.网络发展史 2.网络通信基础 2.1IP地址 2.1.1定义 2.1.2格式 2.2端口号 2.2.1定义 2.2.2格式 2.3协议 2.3.1定义 2.3.2作用 2.3.3分层 2.4五元组 2.4.1定义 2.4.2组成 3.TCP/IP五层网络模型 3.1模型概念 3.2模型构成 3.3网络分层对应…

docker-compose部署laravel项目实战(主机nginx连接项目容器)(详细配置过程)

我用的是主机上的nginx,没有用docker安装nginx&#xff0c; 所以需要先在主机上安装nginx # 更新系统yum sudo yum update# 安装安装包sudo yum install epel-release sudo yum install wget# 安装Nginx sudo yum install nginx #启动 sudo systemctl start nginx #开机自启动…

C语言笔试题之反转链表(头插法)

实例要求&#xff1a; 1、给定单链表的头节点 head &#xff1b;2、请反转链表&#xff1b;3、最后返回反转后的链表&#xff1b; 案例展示&#xff1a; 实例分析&#xff1a; 1、入参合理性检查&#xff0c;即head ! NULL || head->next ! NULL&#xff1b;2、while循环…

Vue3中使用element-plus菜单折叠后文字不消失

今天使用element-plus中国的导航菜单时&#xff0c;发现菜单栏折叠起来时文字不会自动消失&#xff0c;因为element-plus中内置了菜单折叠文字自动消失的&#xff0c;使用collapsetrue/false即可&#xff0c;但在实际使用中出现了一下问题&#xff1a; 折叠以后文字并没有消失&…

普通编程,机器学习与深度学习

普通编程&#xff1a;基于人手动设置规则&#xff0c;由输入产生输出经典机器学习&#xff1a;人手工指定需要的特征&#xff0c;通过一些数学原理对特征与输出的匹配模式进行学习&#xff0c;也就是更新相应的参数&#xff0c;从而使数学表达式能够更好的根据给定的特征得到准…

vue2父组件向子组件传值时,子组件同时接收多个数据类型,控制台报警的问题

最近项目遇到一个问题,就是我的父组件向子组件(公共组件)传值时,子组件同时接收多个数据类型,控制台报警的问题,如下图,子组件明明写了可同时接收字符串,整型和布尔值,但控制台依旧报警: 仔细检查父组件,发现父组件是这样写的: <common-tabletooltip :content=…

Vue3开发环境搭建和工程结构(一)

一、NVM和Node.js安装 NVM 是 Node Version Manager&#xff08;Node 版本管理工具&#xff09;的缩写&#xff0c;是一个命令行工具&#xff0c;用于管理和切换到不同版本的 Node.js。 1、前往 nvm-windows 仓库&#xff0c;然后单击立即下载 2、下载最新版本 3 、按照安装向…

【数据结构与算法】之排序系列-20240204

这里写目录标题 一、977. 有序数组的平方二、1051. 高度检查器三、1122. 数组的相对排序四、1200. 最小绝对差五、1331. 数组序号转换 一、977. 有序数组的平方 简单 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求…