源码解析Spring Boot 的启动流程

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
简介: 最近有位开发同学说面试被问到Spring Boot 的启动流程,以及被问到Spring Boot 的嵌入式Web容器是什么时候加载的。如何加载的。是怎么无缝切换的。这些问题,其实回答起来也是比较复杂的。我们今天就从 `SpringApplication.run(Application.class, args);`入口,逐渐向下看下执行流程。来试着回答一下前面这两个问题。后面关于SpringBoot 的web容器可以无缝随意切换为`jetty`,`undertow.`.这个问题的回答涉及到Spring Boot是如何设计WebServer的。我们后续专门讲解一下。
  • 👏作者简介:大家好,我是冰点,从业11年,目前在物流独角兽企业从事技术方面工作,
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:iceicepip,加我进群,大家一起学习,一起进步👀

@[TOC]

0.前言

背景:最近有位开发同学说面试被问到Spring Boot 的启动流程,以及被问到Spring Boot 的嵌入式Web容器是什么时候加载的。如何加载的。是怎么无缝切换的。这些问题,其实回答起来也是比较复杂的。我们今天就从 SpringApplication.run(Application.class, args);入口,逐渐向下看下执行流程。来试着回答一下前面这两个问题。后面关于SpringBoot 的web容器可以无缝随意切换为jetty,undertow..这个问题的回答涉及到Spring Boot是如何设计WebServer的。我们后续专门讲解一下。

1. 执行逻辑梳理

一般我们SpringBoot 应用的启动入口都是如下这种固定的写法,
在这里插入图片描述
也可以是这样

 public static void main(String[] args) {
   
   
   SpringApplication application = new SpringApplication(MyApplication.class);
   // ... customize application settings here
   application.run(args)
  }

但总之,都是使用SpringApplication 调用静态方法
此方法的注释
Static helper that can be used to run a SpringApplication from the specified source using default settings.

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   
   
        return run(new Class<?>[] {
   
    primarySource }, args);
    }

跟过来就到这,可以看到注释运行Spring应用程序,创建并刷新一个新的ApplicationContext
在这里插入图片描述

在这里插入图片描述
跟代码到这儿其实我们对于SpringBoot 的基本启动流程已经知道了。但是要解答什么时候启动的Tomcat 还需要继续分析。
在这里插入图片描述
到这儿我们就可以继续下去,发现Spring Boot 启动WebServer。此处的WebServer我就不展开了,可以点击去就三个方法start ,stop,getPort。可以看出来Spring 在设计接口的时候还是很严谨和精简。我们的核心脉络是梳理SpringBoot 启动过程,并且回答Tomcat 是如何被启动的。
在这里插入图片描述
我们可以看到WebServer 的实现目前内置的有5种。其实Spring Boot 还有一个特性叫做 自动装配。
这就是为什么5个实现,我们最后启动的是Tomcat。此处也不做展开。后面我专门搞一个解析SpringBoot 自动装配的文章。
在这里插入图片描述
我们看一下内部start 的TomcatWebServer的内部实现。了解过Tomcat 源码的同学看到这儿就基本明白了。
在这里插入图片描述
\==好源码跟进过程我们到此结束,我们整理和总结一下。==
通过扫一遍源码我们大概可以总结出来如下三个阶段
准备阶段、应用上下文创建阶段、刷新上下文阶段

1. 准备阶段: Spring Boot 会加载应用程序的初始设置,并创建 Spring Boot 上下文。这个阶段的核心源码是 SpringApplication 类的 run() 方法,它会调用 Spring Boot 的各个初始化器进行初始化和准备工作。

2. 应用上下文创建阶段 : Spring Boot 会创建应用程序的上下文,包括各种配置信息、Bean 的加载和初始化等。这个阶段的核心源码是 Spring Boot 自动配置机制,通过扫描 classpath 中的配置文件,自动加载和配置各种组件和 Bean。

3. 刷新上下文阶段: Spring Boot 会执行各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。这个阶段的核心源码是 Spring Boot 的刷新机制,它会调用各种初始化器和监听器,执行各种启动任务。其中启动Tomcat 就是在这个环节进行

2. 核心源码解析

既然上面我们已经基本上总结除了,Spring Boot的启动脉络。也梳理出了一些核心源码。那么我们对启动过程的核心源码解析一下。

2.1. 准备阶段

在准备阶段中,Spring Boot 会加载应用程序的初始设置,并创建 Spring Boot 上下文。这个阶段的核心源码是 SpringApplication 类的 run() 方法,它会调用 Spring Boot 的各个初始化器进行初始化和准备工作。

public ConfigurableApplicationContext run(String... args) {
   
   
                 // 启动计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

                 // 定义应用程序上下文和异常报告器列表
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

                 // 配置 Headless 属性
        configureHeadlessProperty();

                 // 获取 Spring Boot 启动监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
                 // 执行启动监听器的 starting 方法
        listeners.starting();

        try {
   
   
                 // 解析命令行参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                 // 准备应用程序环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                 // 配置忽略 BeanInfo
            configureIgnoreBeanInfo(environment);
                 // 打印 Banner
            Banner printedBanner = printBanner(environment);
                 // 创建应用程序上下文
            context = createApplicationContext();
                 // 获取异常报告器,关于异常报告,我下次专门讲一下SpringBoot 的异常收集器。
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{
   
   ConfigurableApplicationContext.class}, context);
                 // 准备应用程序上下文
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                 // 刷新应用程序上下文
            refreshContext(context);
                 // 刷新后操作
            afterRefresh(context, applicationArguments);
                 // 停止计时器
            stopWatch.stop();
                 // 记录启动日志
            if (this.logStartupInfo) {
   
   
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
                 // 执行启动监听器的 started 方法
            listeners.started(context);
                 // 执行 Runner
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
   
   
                 // 处理启动失败
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
   
   
                 // 执行启动监听器的 running 方法
            listeners.running(context);
        } catch (Throwable ex) {
   
   
                 // 处理启动失败
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }

                 // 返回应用程序上下文
        return context;
    }

在 run() 方法中,Spring Boot 首先会创建一个 StopWatch 对象,用于记录整个启动过程的耗时。然后,Spring Boot 会调用 getRunListeners(args) 方法获取 Spring Boot 的各个启动监听器,并调用
starting() 方法通知这些监听器启动过程已经开始。 接着调用 prepareEnvironment(listeners, applicationArguments) 方法创建应用程序的环境变量。这个方法会根据用户的配置和默认设置创建一个 ConfigurableEnvironment对象,并将其传给后面的 createApplicationContext() 方法。printBanner(environment) 方法打印启动界面的 Banner,调用 refreshContext(context)方法刷新上下文。这个方法会启动上下文,执行各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。具体的启动任务会在刷新上下文阶段中进行。

2.2. 应用上下文创建阶段

在应用上下文创建阶段中,Spring Boot 会创建应用程序的上下文,包括各种配置信息、Bean 的加载和初始化等。这个阶段的核心源码是 Spring Boot 自动配置机制,通过扫描 classpath 中的配置文件,自动加载和配置各种组件和 Bean。

protected ConfigurableApplicationContext createApplicationContext() {
   
   
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
   
   
        try {
   
   
            switch (this.webApplicationType) {
   
   
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
   
   
            throw new IllegalStateException(
                    "Unable to create a default ApplicationContext, " +
                    "please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

在 createApplicationContext() 方法中,Spring Boot 首先会判断应用程序的类型,如果是 Web 应用程序,则会创建一个 WebApplicationContext;否则,会创建一个普通的 ApplicationContext。调用 BeanUtils.instantiateClass(contextClass) 方法创建应用程序的上下文。这个方法会根据上面的逻辑创建一个相应的 ApplicationContext。调用 load() 方法加载应用程序的配置。关于加载应用配置,可以参阅我之前写一篇文章三分钟了解SpringBoot配置优先级底层源码解析。这个方法会扫描 classpath 中的各种配置文件,例如 application.properties、application.yml、META-INF/spring.factories 等,自动配置各种组件和 Bean。调用 postProcessApplicationContext() 方法对应用程序的上下文进行后处理。这个方法会调用各种初始化器和监听器,执行各种初始化任务。

2.3. 刷新上下文阶段

在刷新上下文阶段中,Spring Boot 会执行各种启动任务,包括创建 Web 服务器(刚才我们跟源码的时候也看到了,如上我的截图)、加载应用程序的配置、初始化各种组件等。这个阶段的核心源码是 Spring Boot 的刷新机制,它会调用各种初始化器和监听器,执行各种启动任务。

protected void refreshContext(ConfigurableApplicationContext applicationContext) {
   
   
    refresh(applicationContext);
    if (this.registerShutdownHook) {
   
   
        try {
   
   
            applicationContext.registerShutdownHook();
        }
        catch (AccessControlException ex) {
   
   
            // Not allowed in some environments.
        }
    }
}

在 refreshContext() 方法中调用 refresh(applicationContext) 方法刷新上下文。这个方法是 ApplicationContext 接口的核心方法,会启动上下文,执行各种启动任务。调用 registerShutdownHook() 方法注册应用程序的关闭钩子。这个方法会在应用程序关闭时自动执行,清理资源、关闭线程等,所以我们利用此特性在服务关闭的时候清理一些资源。并向外部发送告警通知。
在 refresh(applicationContext) 方法中,Spring Boot 会执行上下文的各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。具体的启动任务会调用各种初始化器和监听器,例如:

for (ApplicationContextInitializer<?> initializer : getInitializers()) {
   
   
    initializer.initialize(applicationContext);
}

另外,Spring Boot 还会调用各种监听器,我们不做赘述,例如:

for (ApplicationListener<?> listener : getApplicationListeners()) {
   
   
    if (listener instanceof SmartApplicationListener) {
   
   
        SmartApplicationListener smartListener = (SmartApplicationListener) listener;
        if (smartListener.supportsEventType(eventType)
                && smartListener.supportsSourceType(sourceType)) {
   
   
            invokeListener(smartListener, event);
        }
    }
    else if (supportsEvent(listener, eventType)) {
   
   
        invokeListener(listener, event);
    }
}

基本上就是这些了。
关于SpringApplication的官方文档讲的比较简单,大家可供参考。地址如下:
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application
在这里插入图片描述

👏好了,本次的分享就到这儿,我是冰点,下次见。如果我的文章对你有所收获,可以给个赞。如果有疑问可以在评论区留言。

目录
相关文章
|
7天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
25 2
|
26天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
8天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
20天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
39 3
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
67 0
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
152 1
|
22天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
97 62
|
20天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
37 2
|
22天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
基于Java+Springboot+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的大学竞赛报名管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
221 3
基于Java+Springboot+Vue开发的大学竞赛报名管理系统

推荐镜像

更多
下一篇
无影云桌面