【SpringBoot合集-03】Spring Boot 启动过程学习 目录一、Spring Boot 启动过程核心步骤解析1. SpringApplication 实例化阶段2. run () 方法核心执行全流程关键步骤拆解二、启动过程扩展点解析按执行顺序排列的核心扩展点1. ApplicationContextInitializer2. ApplicationListener3. BeanFactoryPostProcessor4. BeanPostProcessor5. CommandLineRunner / ApplicationRunner三、配置文件优先级与加载解析1. 核心原理2. 完整优先级排序从高到低3.配置文件加载核心源码配置文件搜索顺序源码逻辑4. Profile 配置加载源码5. 拓展四、面试速记总结一、Spring Boot 启动过程核心步骤解析Spring Boot 启动的核心入口是SpringApplication类整个流程分为实例化构造和run()方法执行两大阶段本质是对 Spring 原生容器的封装与生命周期扩展。1. SpringApplication 实例化阶段构造方法主要完成 4 件核心事为后续启动做前置准备// 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/JettyREACTIVE响应式 Web 应用对应 Netty 等容器NONE非 Web 应用仅运行普通 Spring 容器加载初始化器ApplicationContextInitializer借助SpringFactoriesLoader扫描所有META-INF/spring.factories文件加载并缓存所有上下文初始化器实现类。加载事件监听器ApplicationListener同样通过 SPI 机制加载全局事件监听器用于后续启动各阶段的事件回调。推断主启动类通过构造运行时栈遍历栈帧找到包含main方法的类作为启动的主配置类。2. run () 方法核心执行全流程run()是 Spring Boot 启动的主流程完整主干源码如下去掉了异常分支的冗余包装// org.springframework.boot.SpringApplication public ConfigurableApplicationContext run(String... args) { // 1. 启动计时器统计启动耗时 StopWatch stopWatch new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context null; CollectionSpringBootExceptionReporter 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 启动的主干逻辑每一步都对应明确的扩展点与生命周期事件。关键步骤拆解准备运行环境创建ConfigurableEnvironment对象加载系统属性、环境变量、配置文件等所有属性源同时激活指定的 profile完成多环境配置的筛选。这一步完成后所有配置信息就已经全部就绪。创建应用上下文根据前面推断的应用类型创建对应上下文实现类Servlet 环境 →AnnotationConfigServletWebServerApplicationContextReactive 环境 →AnnotationConfigReactiveWebServerApplicationContext非 Web 环境 →AnnotationConfigApplicationContext上下文前置处理调用所有ApplicationContextInitializer的initialize()方法对上下文做自定义预处理随后发布上下文准备完成事件通知所有监听器。刷新上下文核心调用 Spring 原生的refresh()方法完成 Bean 定义扫描、Bean 实例化、依赖注入、后置处理器执行、容器初始化等全部核心流程。Web 环境下这一步还会触发内嵌 Tomcat 的创建与启动。// 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负责完成。刷新后处理与收尾执行afterRefresh钩子发布ApplicationStartedEvent启动完成事件依次执行所有CommandLineRunner和ApplicationRunner最后发布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对命令行参数做了封装可以更方便地解析--keyvalue格式的参数CommandLineRunner接收原始字符串数组。private void callRunners(ApplicationContext context, ApplicationArguments args) { ListObject 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的属性源列表中。同名属性时排在列表前面的属性源会覆盖后面的这就是配置优先级的底层逻辑。// org.springframework.core.env.MutablePropertySources public class MutablePropertySources implements PropertySources { private final ListPropertySource? 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.port8080的启动参数优先级最高2SPRING_APPLICATION_JSON环境变量以 JSON 格式注入的整体配置3ServletConfig 初始化参数Web 容器的 Servlet 配置参数4ServletContext 初始化参数Web 应用上下文参数5JNDI 属性java:comp/env下的 JNDI 配置6Java 系统属性System.getProperties()中的属性7操作系统环境变量系统层面设置的环境变量8random.*随机值属性源框架内置的随机数生成属性源9Jar 包外的application-{profile}.yml/properties外部带环境的配置文件10Jar 包内的application-{profile}.yml/properties内部带环境的配置文件11Jar 包外的application.yml/properties外部主配置文件12Jar 包内的application.yml/properties内部主配置文件13PropertySource注解引入的配置注解指定的配置文件14SpringApplication.setDefaultProperties设置的默认属性代码硬编码的默认配置优先级最低3.配置文件加载核心源码Spring Boot 2.4 版本通过ConfigDataEnvironmentPostProcessor加载配置文件核心调用链// ConfigDataEnvironmentPostProcessor public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // 解析所有配置数据位置classpath、file路径等 ConfigDataLocations locations resolveLocations(environment); // 加载所有配置文件 ConfigData configData loadConfigData(locations, environment); // 按优先级顺序添加到环境中 addPropertySources(environment, configData); }配置文件的加载核心由ConfigFileApplicationListener2.4 版本后升级为ConfigDataEnvironmentPostProcessor负责它本身是一个环境准备阶段的事件监听器。加载时会先加载所有application主配置再加载对应 profile 的配置profile 配置会覆盖主配置中的同名属性。同一目录下.properties文件优先级高于.yml文件。配置文件搜索顺序源码逻辑默认搜索 4 个位置优先级从高到低file:./config/— 项目目录下的 config 子目录file:./— 项目根目录classpath:/config/— 类路径下的 config 目录classpath:/— 类路径根目录加载时按从低到高顺序加载后加载的调用addFirst插入到列表头部最终实现前面路径覆盖后面路径的效果。4. Profile 配置加载源码加载完主配置后会根据激活的 profile 加载对应环境配置先加载所有application.yml主配置再加载application-{profile}.yml环境配置环境配置通过addFirst加入因此优先级高于主配置多 profile 同时激活时后声明的 profile 优先级更高5. 拓展外部配置文件会覆盖 Jar 包内的同名配置生产环境常用这个特性做差异化配置不用重新打包。配置文件的搜索路径优先级./config/./classpath:/config/classpath:/。多 profile 同时激活时后激活的 profile 配置会覆盖先激活的。四、面试速记总结启动主干构造方法推断类型 加载 SPI 扩展 → run 方法准备环境 → 创建上下文 → 执行初始化器 → refresh 刷新容器 → 回调 Runner → 发布就绪事件。扩展点顺序ApplicationContextInitializerrefresh 前→ BeanFactoryPostProcessorBean 定义后→ BeanPostProcessorBean 实例化前后→ Runner启动完成后。配置优先级命令行 系统 / 环境变量 外部带 profile 配置 内部带 profile 配置 外部主配置 内部主配置 默认配置。配置本质所有属性源存在 List 中靠前覆盖靠后addFirst 提高优先级addLast 降低优先级profile 配置覆盖主配置。内嵌 Tomcat 入口触发点是ServletWebServerApplicationContext的onRefresh方法由 Spring 刷新流程驱动Tomcat 是容器内的普通 Bean。设计本质Spring Boot 没有发明新技术只是把 Spring 容器、Web 服务器、自动配置等组件通过 SPI 和事件机制串联起来用「约定大于配置」的思想降低了使用门槛。