Spring Boot 启动流程深度解析:从源码到实践

news2025/6/3 14:42:27

Spring Boot 启动流程深度解析:从源码到实践

Spring Boot 作为 Java 开发的主流框架,其 “约定大于配置” 的理念极大提升了开发效率。本文将从源码层面深入解析 Spring Boot 的启动流程,并通过代码示例展示其工作机制。

一、Spring Boot 启动的入口点

Spring Boot 应用的启动通常从一个包含main方法的类开始:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@SpringBootApplication是一个组合注解,包含:

  • @SpringBootConfiguration:声明这是一个配置类
  • @EnableAutoConfiguration:启用自动装配机制
  • @ComponentScan:启用组件扫描

SpringApplication.run()是启动的核心方法,下面我们深入分析其执行流程。

二、SpringApplication 的初始化

当调用SpringApplication.run()时,首先会创建SpringApplication实例:

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的构造函数会执行一系列初始化操作:

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 1. 判断应用类型(REACTIVE, SERVLET, NONE)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 2. 设置ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 3. 设置ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 4. 推断主应用类(包含main方法的类)
    this.mainApplicationClass = deduceMainApplicationClass();
}

关键步骤解析:

  1. 应用类型推断:根据类路径中的类推断应用类型(WebFlux、Servlet 或普通应用)
  2. 初始化器加载:从META-INF/spring.factories加载ApplicationContextInitializer
  3. 监听器加载:从META-INF/spring.factories加载ApplicationListener
  4. 主应用类推断:通过栈轨迹找到包含 main 方法的类

三、SpringApplication.run () 方法解析

run()方法是 Spring Boot 启动的核心逻辑:

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    // 1. 获取并启动所有SpringApplicationRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 2. 准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 3. 打印Banner
        Banner printedBanner = printBanner(environment);
        // 4. 创建ApplicationContext
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 5. 准备上下文
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 6. 刷新上下文
        refreshContext(context);
        // 7. 刷新后的处理
        afterRefresh(context, applicationArguments);
        // 8. 计时结束
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        // 9. 发布应用启动完成事件
        listeners.started(context, timeTakenToStartup);
        // 10. 调用所有Runners
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 11. 发布应用就绪事件
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
    }
    catch (Throwable ex) {
        if (context != null) {
            context.close();
            throw new IllegalStateException("Error handling failed", ex);
        }
        throw new IllegalStateException(ex);
    }
    return context;
}

四、关键步骤详解

1. 启动监听器(SpringApplicationRunListeners)

Spring Boot 通过事件机制在启动的不同阶段发布事件,允许开发者介入:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
            this.applicationStartup);
}

核心监听器包括EventPublishingRunListener,它会发布一系列事件:

  • ApplicationStartingEvent
  • ApplicationEnvironmentPreparedEvent
  • ApplicationContextInitializedEvent
  • ApplicationPreparedEvent
  • ApplicationStartedEvent
  • ApplicationReadyEvent
2. 环境准备(prepareEnvironment)

环境准备包括创建并配置ConfigurableEnvironment,加载属性源和配置文件:

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        DefaultBootstrapContext bootstrapContext,
        ApplicationArguments applicationArguments) {
    // 创建环境(Web或非Web)
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 发布环境准备事件
    listeners.environmentPrepared(bootstrapContext, environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}
3. 创建应用上下文(createApplicationContext)

根据应用类型创建不同的ApplicationContext

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);
}
4. 准备应用上下文(prepareContext)

对创建好的上下文进行初始化,包括设置环境、加载源、应用初始化器等:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 设置环境
    context.setEnvironment(environment);
    // 应用上下文后置处理器
    postProcessApplicationContext(context);
    // 应用初始化器
    applyInitializers(context);
    // 发布上下文初始化事件
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // 添加资源加载器和主应用类
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    // 设置懒加载
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // 加载源(主配置类)
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    // 发布上下文加载完成事件
    listeners.contextLoaded(context);
}
5. 刷新应用上下文(refreshContext)

调用AbstractApplicationContext.refresh()方法,这是 Spring 框架的核心逻辑,包括:

  • BeanFactory 的创建与初始化
  • BeanDefinition 的加载
  • Bean 的创建与依赖注入
  • 自动装配的处理
  • 事件发布器的初始化
  • 嵌入式 Servlet 容器的启动(如果是 Web 应用)
private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
    refresh(context);
}

protected void refresh(ConfigurableApplicationContext context) {
    context.refresh();
}
6. 调用应用 Runner(callRunners)

启动完成后,调用所有实现了ApplicationRunnerCommandLineRunner的 Bean:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    // 添加所有ApplicationRunner
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 添加所有CommandLineRunner
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 排序
    AnnotationAwareOrderComparator.sort(runners);
    // 调用
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

五、自定义启动流程

开发者可以通过以下方式自定义启动流程:

1. 添加 ApplicationContextInitializer
public class CustomApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 在上下文刷新前执行自定义逻辑
        System.out.println("Custom initializer called");
    }
}

注册方式:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);
        app.addInitializers(new CustomApplicationContextInitializer());
        app.run(args);
    }
}
2. 添加 ApplicationListener
public class CustomApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        // 应用启动后执行
        System.out.println("Application started!");
    }
}

注册方式:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);
        app.addListeners(new CustomApplicationListener());
        app.run(args);
    }
}
3. 实现 CommandLineRunner 或 ApplicationRunner
@Component
public class CustomCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner executed with args: " + Arrays.toString(args));
    }
}

六、启动流程总结

Spring Boot 的启动流程可以概括为以下关键步骤:

  1. 初始化 SpringApplication
    • 推断应用类型
    • 加载初始化器和监听器
    • 确定主应用类
  2. 执行 run () 方法
    • 发布启动事件
    • 准备环境(加载配置属性)
    • 创建应用上下文
    • 准备上下文(设置环境、加载源)
    • 刷新上下文(核心 Spring 容器初始化)
    • 调用应用 Runner
    • 发布就绪事件
  3. 核心机制
    • 事件机制:通过ApplicationEventApplicationListener实现
    • 自动装配:在上下文刷新阶段通过@EnableAutoConfiguration触发
    • 条件注解:控制配置类的加载条件

理解 Spring Boot 的启动流程有助于开发者更好地调试应用、自定义启动行为,以及解决启动过程中遇到的问题。通过扩展点(Initializers、Listeners、Runners),开发者可以在不修改核心代码的情况下介入启动流程,实现各种定制需求。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习|pytorch基本运算-乘除法和幂运算

【1】引言 前序学习进程中&#xff0c;已经对pytorch张量数据的生成和广播做了详细探究&#xff0c;文章链接为&#xff1a; 深度学习|pytorch基本运算-CSDN博客 深度学习|pytorch基本运算-广播失效-CSDN博客 上述探索的内容还止步于张量的加减法&#xff0c;在此基础上&am…

4.2.4 Spark SQL 数据写入模式

在本节实战中&#xff0c;我们详细探讨了Spark SQL中数据写入的四种模式&#xff1a;ErrorIfExists、Append、Overwrite和Ignore。通过具体案例&#xff0c;我们演示了如何使用mode()方法结合SaveMode枚举类来控制数据写入行为。我们首先读取了一个JSON文件生成DataFrame&#…

论文笔记: Urban Region Embedding via Multi-View Contrastive Prediction

AAAI 2024 1 INTRO 之前基于多视图的region embedding工作大多遵循相同的模式 单独的单视图表示多视图融合 但这种方法存在明显的局限性&#xff1a;忽略了不同视图之间的信息一致性 一个区域的多个视图所携带的信息是高度相关的&#xff0c;因此它们的表示应该是一致的如果能…

初学者如何微调大模型?从0到1详解

本文将手把手带你从0到1&#xff0c;详细解析初学者如何微调大模型&#xff0c;让你也能驾驭这些强大的AI工具。 1. 什么是大模型微调&#xff1f; 想象一下&#xff0c;预训练大模型就像一位博览群书但缺乏专业知识的通才。它掌握了海量的通用知识&#xff0c;但可能无法完美…

西瓜书第十一章——降维与度量学习

文章目录 降维与度量学习k近邻学习原理头歌实战-numpy实现KNNsklearn实现KNN 降维——多维缩放&#xff08;Multidimensional Scaling, MDS&#xff0c;MDS&#xff09;提出背景与原理重述1.**提出背景**2.**数学建模与原理推导**3.**关键推导步骤** Principal Component Analy…

Portainer安装指南:多节点监控的docker管理面板-家庭云计算专家

背景 Portainer 是一个轻量级且功能强大的容器管理面板&#xff0c;专为 Docker 和 Kubernetes 环境设计。它通过直观的 Web 界面简化了容器的部署、管理和监控&#xff0c;即使是非技术用户也能轻松上手。Portainer 支持多节点管理&#xff0c;允许用户从一个中央控制台管理多…

vscode实用配置

前端开发安装插件&#xff1a; 1.可以更好看的显示文件图标 2.用户快速打开文件 使用步骤&#xff1a;在html文件下右键点击 open with live server 即可 刷力扣&#xff1a; 安装这个插件 还需要安装node.js即可

React 项目中封装 Excel 导入导出组件:技术分享与实践

文章目录 前言一、为什么需要封装 Excel 组件&#xff1f;二、技术选型三、核心实现1. 安装依赖2. 封装Excel导出3. 封装导入组件 &#xff08;UploadExcel&#xff09; 总结 前言 在 React 项目中&#xff0c;处理 Excel 文件的导入和导出是常见的业务需求。无论是导出报表数…

【2025CCF中国开源大会】RISC-V 开源生态的挑战与机遇分论坛重磅来袭!共探开源芯片未来

点击蓝字 关注我们 CCF Opensource Development Committee 开源浪潮正从软件席卷硬件领域&#xff0c;RISC-V作为全球瞩目的开源芯片架构&#xff0c;正在重塑计算生态的版图&#xff01;相较于成熟的x86与ARM&#xff0c;RISC-V生态虽处爆发初期&#xff0c;却蕴藏着无限可能。…

python完成批量复制Excel文件并根据另一个Excel文件中的名称重命名

import openpyxl import shutil import os # 原始文件路径 original_file "C:/Users/Administrator/Desktop/事业联考面试名单/郑州.xlsx" # 读取包含名称的Excel文件 # 修改为您的文件名 wb openpyxl.load_workbook( "C:/Users/Administrator/Desktop/事…

Vue-2-前端框架Vue基础入门之二

文章目录 1 计算属性1.1 计算属性简介1.2 计算属性示例 2 侦听器2.1 简单的侦听器2.2 深度监听2.3 监听对象单个属性 3 vue-cli3.1 工程化的Vue项目3.2 Vue项目的运行流程 4 vue组件4.1 Vue组件的三个部分4.1.1 template4.1.2 script4.1.3 style 4.2 组件之间的关系4.2.1 使用组…

CPT208 Human-Centric Computing 人机交互 Pt.7 交互和交互界面

文章目录 1. 界面隐喻&#xff08;Interface metaphors&#xff09;1.1 界面隐喻的应用方式1.2 界面隐喻的优缺点 2. 交互类型2.1 Instructing&#xff08;指令式交互&#xff09;2.2 Conversing&#xff08;对话式交互&#xff09;2.3 Manipulating&#xff08;操作式交互&…

[网页五子棋][匹配模块]前后端交互接口(消息推送机制)、客户端开发(匹配页面、匹配功能)

让多个用户&#xff0c;在游戏大厅中能够进行匹配&#xff0c;系统会把实力相近的两个玩家凑成一桌&#xff0c;进行对战 约定前后端交互接口 消息推送机制 匹配这样的功能&#xff0c;也是依赖消息推送机制的 玩家 1 点击开始匹配按钮&#xff0c;就会告诉服务器&#xff1…

【数据分析】Matplotlib+Pandas+Seaborn绘图

【数据分析】MatplotlibPandasSeaborn绘图 &#xff08;一&#xff09;Matplotlib绘图1.1 matplotlib绘图方式1: 状态接口1.2 matplotlib绘图方式2: 面向对象1.3 通过安斯科姆数据集, 说明可视化的重要性1.4 MatPlotlib绘图-单变量-直方图1.5 MatPlotlib绘图-双变量-散点图1.6 …

NLP学习路线图(十五):TF-IDF(词频-逆文档频率)

在自然语言处理&#xff08;NLP&#xff09;的浩瀚宇宙中&#xff0c;TF-IDF&#xff08;词频-逆文档频率&#xff09; 犹如一颗恒星&#xff0c;虽古老却依然璀璨。当ChatGPT、BERT等大模型光芒四射时&#xff0c;TF-IDF作为传统方法的代表&#xff0c;其简洁性、高效性与可解…

[Redis] Redis命令在Pycharm中的使用

初次学习&#xff0c;如有错误还请指正 目录 String命令 Hash命令 List命令 set命令 SortedSet命令 连接pycharm的过程见&#xff1a;[Redis] 在Linux中安装Redis并连接桌面客户端或Pycharm-CSDN博客 redis命令的使用见&#xff1a;[Redis] Redis命令&#xff08;1&#xf…

openpnp - 给M4x0.7mm的直油嘴加油的工具选择

文章目录 openpnp - 给M4x0.7mm的直油嘴加油的工具选择概述如果换上带卡口的M4x0.7直油嘴END openpnp - 给M4x0.7mm的直油嘴加油的工具选择 概述 X导轨用了一个HG15的滑块 滑块上的注油口的黄油嘴是M4x0.7mm的直油嘴。 外表面是6边形的柱子&#xff0c;没有可以卡住加油嘴工…

EasyExcel复杂Excel导出

效果图展示 1、引入依赖 <!-- easyExcel --> <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.2</version> </dependency>2、实体类 import com.alibaba.excel.annotatio…

1,QT的编译教程

目录 整体流程: 1,新建project文件 2,编写源代码 3,打开QT的命令行窗口 4,生成工程文件(QT_demo.pro) 5,生成Make file 6,编译工程 7,运行编译好的可执行文件 整体流程: 1,新建project文件 新建文本文件,后缀改为.cpp 2,编写源代码

【笔记】在 MSYS2(MINGW64)中安装 Python 工具链的记录

#工作记录 &#x1f4cc; 安装背景 操作系统&#xff1a;MSYS2 MINGW64当前时间&#xff1a;2025年6月1日Python 版本&#xff1a;3.12&#xff08;默认通过 pacman 安装&#xff09;目标工具链&#xff1a; pipxnumpypipsetuptoolswheel &#x1f6e0;️ 安装过程与结果记录…