【SpringBoot合集-03】Spring Boot 启动过程学习

目录

[一、Spring Boot 启动过程核心步骤解析](#一、Spring Boot 启动过程核心步骤解析)

[1. SpringApplication 实例化阶段](#1. SpringApplication 实例化阶段)

[2. run () 方法核心执行全流程](#2. run () 方法核心执行全流程)

关键步骤拆解

二、启动过程扩展点解析

按执行顺序排列的核心扩展点

[1. ApplicationContextInitializer](#1. ApplicationContextInitializer)

[2. ApplicationListener](#2. ApplicationListener)

[3. BeanFactoryPostProcessor](#3. BeanFactoryPostProcessor)

[4. BeanPostProcessor](#4. BeanPostProcessor)

[5. CommandLineRunner / ApplicationRunner](#5. CommandLineRunner / ApplicationRunner)

三、配置文件优先级与加载解析

[1. 核心原理](#1. 核心原理)

[2. 完整优先级排序(从高到低)](#2. 完整优先级排序(从高到低))

3.配置文件加载核心源码

配置文件搜索顺序源码逻辑

[4. Profile 配置加载源码](#4. Profile 配置加载源码)

[5. 拓展](#5. 拓展)

四、面试速记总结


一、Spring Boot 启动过程核心步骤解析

Spring Boot 启动的核心入口是 SpringApplication 类,整个流程分为实例化构造run() 方法执行两大阶段,本质是对 Spring 原生容器的封装与生命周期扩展。

1. SpringApplication 实例化阶段

构造方法主要完成 4 件核心事,为后续启动做前置准备:

java 复制代码
// org.springframework.boot.SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 1. 保存主启动类,后续作为包扫描的根入口
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 2. 推断当前Web应用类型:SERVLET / REACTIVE / NONE
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    // 3. SPI加载所有ApplicationContextInitializer上下文初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    
    // 4. SPI加载所有ApplicationListener事件监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    // 5. 遍历异常栈,推断main方法所在的主启动类
    this.mainApplicationClass = deduceMainApplicationClass();
}
  • 推断 Web 应用类型 通过 ClassUtils.isPresent() 判断类路径下是否存在 Servlet、DispatcherServlet 等核心类,将应用分为 3 种类型:
    • SERVLET:传统 Servlet Web 应用,对应内嵌 Tomcat/Jetty
    • REACTIVE:响应式 Web 应用,对应 Netty 等容器
    • NONE:非 Web 应用,仅运行普通 Spring 容器
  • 加载初始化器 ApplicationContextInitializer 借助 SpringFactoriesLoader 扫描所有 META-INF/spring.factories 文件,加载并缓存所有上下文初始化器实现类。
  • 加载事件监听器 ApplicationListener 同样通过 SPI 机制加载全局事件监听器,用于后续启动各阶段的事件回调。
  • 推断主启动类 通过构造运行时栈,遍历栈帧找到包含 main 方法的类,作为启动的主配置类。

2. run () 方法核心执行全流程

run() 是 Spring Boot 启动的主流程,完整主干源码如下,去掉了异常分支的冗余包装:

java 复制代码
// org.springframework.boot.SpringApplication
public ConfigurableApplicationContext run(String... args) {
    // 1. 启动计时器,统计启动耗时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    
    // 2. 设置Java.awt.headless模式,无图形环境也能正常运行
    configureHeadlessProperty();
    
    // 3. SPI加载SpringApplicationRunListener,用于发布启动各阶段事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(); // 发布应用开始启动事件
    
    try {
        // 4. 封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        // 5. 准备运行环境:加载配置文件、环境变量、系统属性
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        
        // 6. 打印启动Banner
        Banner printedBanner = printBanner(environment);
        
        // 7. 根据Web类型创建对应ApplicationContext上下文实例
        context = createApplicationContext();
        
        // 8. 加载异常报告器,启动失败时输出分析信息
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        
        // 9. 上下文前置准备:注入环境、执行初始化器、注册启动类、发布事件
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        // 10. 刷新上下文:Spring核心流程,Bean扫描、实例化、容器初始化
        refreshContext(context);
        
        // 11. 刷新后钩子,空实现留给子类扩展
        afterRefresh(context, applicationArguments);
        
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        
        // 12. 发布应用启动完成事件
        listeners.started(context);
        
        // 13. 执行所有CommandLineRunner和ApplicationRunner
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 14. 发布应用就绪事件,此时应用可对外提供服务
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

这是 Spring Boot 启动的主干逻辑,每一步都对应明确的扩展点与生命周期事件。

关键步骤拆解
  1. 准备运行环境 创建 ConfigurableEnvironment 对象,加载系统属性、环境变量、配置文件等所有属性源;同时激活指定的 profile,完成多环境配置的筛选。这一步完成后,所有配置信息就已经全部就绪。

  2. 创建应用上下文 根据前面推断的应用类型,创建对应上下文实现类:Servlet 环境 → AnnotationConfigServletWebServerApplicationContext;Reactive 环境 → AnnotationConfigReactiveWebServerApplicationContext;非 Web 环境 → AnnotationConfigApplicationContext

  3. 上下文前置处理 调用所有 ApplicationContextInitializerinitialize() 方法,对上下文做自定义预处理;随后发布上下文准备完成事件,通知所有监听器。

  4. 刷新上下文(核心) 调用 Spring 原生的 refresh() 方法,完成 Bean 定义扫描、Bean 实例化、依赖注入、后置处理器执行、容器初始化等全部核心流程。Web 环境下,这一步还会触发内嵌 Tomcat 的创建与启动。

    java 复制代码
    // org.springframework.context.support.AbstractApplicationContext
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            prepareRefresh();                 // 1. 刷新前预处理
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 2. 获取Bean工厂
            prepareBeanFactory(beanFactory);  // 3. Bean工厂基础配置
            
            // 4. 执行所有BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);
            // 5. 注册所有BeanPostProcessor
            registerBeanPostProcessors(beanFactory);
            
            initMessageSource();              // 6. 初始化国际化
            initApplicationEventMulticaster();// 7. 初始化事件多播器
            onRefresh();                      // 8. 子类扩展钩子:内嵌Tomcat在这里启动
            registerListeners();              // 9. 注册事件监听器
            finishBeanFactoryInitialization(beanFactory); // 10. 实例化所有单例Bean
            finishRefresh();                  // 11. 完成刷新,发布上下文刷新事件
        }
    }

    自动配置类的解析、@Bean 方法的扫描,都发生在第 4 步 invokeBeanFactoryPostProcessors 中,由 ConfigurationClassPostProcessor 负责完成。

  5. 刷新后处理与收尾 执行 afterRefresh 钩子,发布 ApplicationStartedEvent 启动完成事件;依次执行所有 CommandLineRunnerApplicationRunner;最后发布 ApplicationReadyEvent 就绪事件,标志应用完全启动可用。

二、启动过程扩展点解析

Spring Boot 全程采用事件驱动 + 钩子接口的设计,在启动的每个节点都预留了扩展点,开发者可以在不修改框架源码的情况下介入启动流程。

按执行顺序排列的核心扩展点

1. ApplicationContextInitializer
  • 执行时机:上下文创建完成后,refresh 刷新之前
  • 核心作用:对 Spring 上下文做预处理,比如注册自定义属性源、强制激活指定 profile、注册 Bean 定义加载器
2. ApplicationListener
  • 执行时机:贯穿启动全流程,对应不同生命周期事件触发
  • 核心作用:基于观察者模式,监听启动各阶段事件,做解耦的扩展逻辑
  • 常用事件
    • ApplicationEnvironmentPreparedEvent:环境准备完成
    • ApplicationContextInitializedEvent:上下文初始化完成
    • ApplicationPreparedEvent:上下文准备完成
    • ApplicationStartedEvent:应用启动完成
    • ApplicationReadyEvent:应用完全就绪
3. BeanFactoryPostProcessor
  • 执行时机:所有 Bean 定义加载完成后,Bean 实例化之前
  • 核心作用:修改或增强 Bean 定义,比如批量修改 Bean 属性、注册额外的 Bean 定义
  • 典型代表PropertySourcesPlaceholderConfigurer,负责把配置文件中的 ${xxx} 占位符替换成真实值。
4. BeanPostProcessor
  • 执行时机:Bean 实例化后、初始化方法执行的前后
  • 核心作用:对 Bean 实例做增强,比如动态代理、属性填充
  • 典型代表AutowiredAnnotationBeanPostProcessor,负责 @Autowired 注解的依赖注入。
5. CommandLineRunner / ApplicationRunner
  • 执行时机:容器完全刷新完成、应用即将就绪之前
  • 核心作用:项目启动后执行初始化逻辑,比如数据预热、缓存加载、服务自检
  • 两者区别ApplicationRunner 对命令行参数做了封装,可以更方便地解析 --key=value 格式的参数;CommandLineRunner 接收原始字符串数组。
java 复制代码
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());
    // 按@Order注解排序
    AnnotationAwareOrderComparator.sort(runners);
    
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            ((ApplicationRunner) runner).run(args);
        }
        if (runner instanceof CommandLineRunner) {
            ((CommandLineRunner) runner).run(args.getSourceArgs());
        }
    }
}

设计细节 :两种 Runner 放在同一个列表排序,@Order 值越小优先级越高,支持跨类型排序。

三、配置文件优先级与加载解析

1. 核心原理

Spring Boot 所有配置最终都会被封装成 PropertySource 对象,按优先级顺序存入 Environment 的属性源列表中。同名属性时,排在列表前面的属性源会覆盖后面的,这就是配置优先级的底层逻辑。

java 复制代码
// org.springframework.core.env.MutablePropertySources
public class MutablePropertySources implements PropertySources {
    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    
    // 加到最前面,优先级最高
    public void addFirst(PropertySource<?> propertySource) {
        this.propertySourceList.add(0, propertySource);
    }
    // 加到最后面,优先级最低
    public void addLast(PropertySource<?> propertySource) {
        this.propertySourceList.add(propertySource);
    }
}

获取属性时按列表顺序遍历,找到第一个匹配值就返回,这就是优先级的底层实现。

2. 完整优先级排序(从高到低)

优先级 配置来源 说明
1 命令行参数 形如 --server.port=8080 的启动参数,优先级最高
2 SPRING_APPLICATION_JSON 环境变量 以 JSON 格式注入的整体配置
3 ServletConfig 初始化参数 Web 容器的 Servlet 配置参数
4 ServletContext 初始化参数 Web 应用上下文参数
5 JNDI 属性 java:comp/env 下的 JNDI 配置
6 Java 系统属性 System.getProperties() 中的属性
7 操作系统环境变量 系统层面设置的环境变量
8 random.* 随机值属性源 框架内置的随机数生成属性源
9 Jar 包外的 application-{profile}.yml/properties 外部带环境的配置文件
10 Jar 包内的 application-{profile}.yml/properties 内部带环境的配置文件
11 Jar 包外的 application.yml/properties 外部主配置文件
12 Jar 包内的 application.yml/properties 内部主配置文件
13 @PropertySource 注解引入的配置 注解指定的配置文件
14 SpringApplication.setDefaultProperties 设置的默认属性 代码硬编码的默认配置,优先级最低

3.配置文件加载核心源码

Spring Boot 2.4+ 版本通过 ConfigDataEnvironmentPostProcessor 加载配置文件,核心调用链:

java 复制代码
// ConfigDataEnvironmentPostProcessor
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    // 解析所有配置数据位置(classpath、file路径等)
    ConfigDataLocations locations = resolveLocations(environment);
    // 加载所有配置文件
    ConfigData configData = loadConfigData(locations, environment);
    // 按优先级顺序添加到环境中
    addPropertySources(environment, configData);
}
  • 配置文件的加载核心由 ConfigFileApplicationListener(2.4 版本后升级为 ConfigDataEnvironmentPostProcessor)负责,它本身是一个环境准备阶段的事件监听器。
  • 加载时会先加载所有 application 主配置,再加载对应 profile 的配置;profile 配置会覆盖主配置中的同名属性。
  • 同一目录下,.properties 文件优先级高于 .yml 文件。
配置文件搜索顺序源码逻辑

默认搜索 4 个位置,优先级从高到低:

  1. file:./config/ --- 项目目录下的 config 子目录
  2. file:./ --- 项目根目录
  3. classpath:/config/ --- 类路径下的 config 目录
  4. classpath:/ --- 类路径根目录

加载时按从低到高 顺序加载,后加载的调用 addFirst 插入到列表头部,最终实现前面路径覆盖后面路径的效果。

4. Profile 配置加载源码

加载完主配置后,会根据激活的 profile 加载对应环境配置:

  1. 先加载所有 application.yml 主配置
  2. 再加载 application-{profile}.yml 环境配置
  3. 环境配置通过 addFirst 加入,因此优先级高于主配置
  4. 多 profile 同时激活时,后声明的 profile 优先级更高

5. 拓展

  • 外部配置文件会覆盖 Jar 包内的同名配置,生产环境常用这个特性做差异化配置,不用重新打包。
  • 配置文件的搜索路径优先级:./config/ > ./ > classpath:/config/ > classpath:/
  • 多 profile 同时激活时,后激活的 profile 配置会覆盖先激活的。

四、面试速记总结

  1. 启动主干:构造方法推断类型 + 加载 SPI 扩展 → run 方法准备环境 → 创建上下文 → 执行初始化器 → refresh 刷新容器 → 回调 Runner → 发布就绪事件。
  2. 扩展点顺序:ApplicationContextInitializer(refresh 前)→ BeanFactoryPostProcessor(Bean 定义后)→ BeanPostProcessor(Bean 实例化前后)→ Runner(启动完成后)。
  3. 配置优先级 :命令行 > 系统 / 环境变量 > 外部带 profile 配置 > 内部带 profile 配置 > 外部主配置 > 内部主配置 > 默认配置。配置本质:所有属性源存在 List 中,靠前覆盖靠后;addFirst 提高优先级,addLast 降低优先级;profile 配置覆盖主配置。
  4. 内嵌 Tomcat 入口 :触发点是 ServletWebServerApplicationContextonRefresh 方法,由 Spring 刷新流程驱动,Tomcat 是容器内的普通 Bean。
  5. 设计本质:Spring Boot 没有发明新技术,只是把 Spring 容器、Web 服务器、自动配置等组件通过 SPI 和事件机制串联起来,用「约定大于配置」的思想降低了使用门槛。
相关推荐
孟浩浩3 小时前
JAVA SpringAI+阿里云百炼应用开发
java·开发语言·阿里云
钱多多_qdd3 小时前
ListUtil#split和remove搭配使用的坑
java
碧蓝的水壶3 小时前
数据转换过程
java·开发语言·windows
2501_947575809 小时前
计算机毕业设计之jsp开山车行二手车交易系统
java·开发语言·hadoop·python·信息可视化·django·课程设计
骑士雄师9 小时前
java面试题 4:鉴权
java·开发语言
Byron__10 小时前
AI学习_06_短期记忆与长期记忆
人工智能·python·学习
帅次11 小时前
Android 高级工程师面试:Java 基础知识 近1年高频追问 22 题
android·java·面试
蓝胖的四次元口袋11 小时前
Java集合(4)
java·哈希算法
2501_9481069111 小时前
计算机毕业设计之基于jsp教科研信息共享系统
java·开发语言·信息可视化·spark·课程设计