第19篇 · Spring Boot启动源码浅析:从main方法到应用就绪的幕后之旅

这是本专栏的最后一篇。

在前面十八篇里,我们拆解了 Servlet 规范、IoC 容器、AOP、事务、MyBatis、Spring MVC------这些是 Spring 框架和生态的"零件"。而 Spring Boot 的职责,就是把这些零件按照正确的顺序组装起来,让整个机器能够一键启动

你每天写的 SpringApplication.run(Application.class, args),背后到底发生了什么?为什么一行代码就能启动一个 Web 服务器、加载所有配置、扫描所有 Bean、打开数据库连接?

这一篇,我们把 Spring Boot 的启动过程从头到尾走一遍。

学习目标

  • 理解 SpringApplication.run()核心流程
  • 掌握 Spring Boot 启动的三大阶段(SpringApplication 初始化 → 运行 → 刷新容器)
  • 理解 Spring Boot 的事件机制ApplicationEventApplicationListener
  • 了解 Spring Boot 的自动配置在启动流程中的加载时机

正文

一、SpringApplication 的初始化:启动前的"准备工作"

Spring Boot 的启动始于 SpringApplication.run() 这个静态方法。但在这之前,有一个容易被忽略的步骤------创建 SpringApplication 实例

java 复制代码
// SpringApplication.java (Spring Boot 3.2.x)
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return new SpringApplication(primarySource).run(args);
}

run() 方法实际上做了两件事:先 new 一个 SpringApplication 对象,再调用它的 run() 方法

SpringApplication 的构造方法做了哪些事情?我们来看核心逻辑:

java 复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 1. 推断 Web 应用类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    // 2. 加载 ApplicationContextInitializer
    this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
    this.initializers = getInitializers();
    
    // 3. 加载 ApplicationListener
    this.listeners = getListeners();
    
    // 4. 推断主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}

① 推断 Web 应用类型(deduceFromClasspath()

Spring Boot 根据 classpath 中是否存在特定的类来判断应用类型:

类型 判断依据
REACTIVE classpath 中存在 org.springframework.web.reactive.DispatcherHandler,且不存在 org.springframework.web.servlet.DispatcherServlet
SERVLET classpath 中存在 jakarta.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext
NONE 以上都不满足(纯命令行应用)

这个判断决定了后续创建什么类型的 ApplicationContext------AnnotationConfigServletWebServerApplicationContext(Servlet 环境)或 AnnotationConfigReactiveWebServerApplicationContext(Reactive 环境)。

② 加载 ApplicationContextInitializer 和 ApplicationListener

通过 SpringFactoriesLoaderMETA-INF/spring.factories(Spring Boot 2.x)或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)中加载。

这些 Initializer 和 Listener 是 Spring Boot 启动流程中的"扩展点"------你可以在这些阶段插入自定义逻辑。

③ 推断主配置类(deduceMainApplicationClass()

通过分析堆栈信息,找到包含 main 方法的类。这个类通常就是标注了 @SpringBootApplication 的启动类。

二、run 方法的执行流程:从 main 到容器就绪

SpringApplication 对象创建完成后,run() 方法才是真正的"启动引擎"。我们来看 run() 方法的完整骨架:

java 复制代码
public ConfigurableApplicationContext run(String... args) {
    // 1. 启动计时
    long startTime = System.nanoTime();
    
    // 2. 创建引导上下文(BootstrapContext)
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    
    // 3. 配置 headless 模式
    configureHeadlessProperty();
    
    // 4. 获取并启动运行监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    
    try {
        // 5. 准备应用参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        // 6. 准备环境(Environment)
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        
        // 7. 打印 Banner
        printBanner(environment);
        
        // 8. 创建应用上下文(ApplicationContext)
        ConfigurableApplicationContext context = createApplicationContext();
        
        // 9. 准备上下文
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        
        // 10. 刷新上下文 ------ 最核心的一步!
        refreshContext(context);
        
        // 11. 刷新后处理
        afterRefresh(context, applicationArguments);
        
        // 12. 启动完成,发布事件
        listeners.started(context);
        
        // 13. 执行 Runner
        callRunners(context, applicationArguments);
        
        // 14. 就绪,发布事件
        listeners.ready(context);
        
        return context;
    } catch (Throwable ex) {
        // 异常处理
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
}

我们把其中几个关键步骤拆开来看。

步骤 4:获取并启动运行监听器(SpringApplicationRunListeners

SpringApplicationRunListener 是 Spring Boot 特有的监听器接口,它和 Spring 的 ApplicationListener 不同------前者专门监听 Spring Boot 启动过程的不同阶段,后者监听 Spring 容器内的事件。

启动过程中会按顺序发布以下事件:

事件 触发时机
ApplicationStartingEvent run() 方法开始执行时
ApplicationEnvironmentPreparedEvent Environment 准备完成时
ApplicationContextInitializedEvent ApplicationContext 创建并初始化完成时
ApplicationPreparedEvent ApplicationContext 刷新之前
ApplicationStartedEvent ApplicationContext 刷新完成之后
ApplicationReadyEvent 所有 Runner 执行完成,应用完全就绪
ApplicationFailedEvent 启动失败时

步骤 6:准备环境(prepareEnvironment()

Environment 是 Spring 对"配置来源"的抽象,它聚合了系统属性、环境变量、命令行参数、application.yml 等各种配置源。

prepareEnvironment() 的核心工作:

  1. 创建 ConfigurableEnvironment 实例(根据 Web 类型选择 StandardServletEnvironmentStandardReactiveWebEnvironmentStandardEnvironment
  2. 添加默认的 PropertySource(命令行参数、系统属性、环境变量等)
  3. 加载 application.properties / application.yml
  4. 激活 Profile(spring.profiles.active
  5. 发布 ApplicationEnvironmentPreparedEvent

步骤 8:创建应用上下文(createApplicationContext()

根据之前推断的 Web 应用类型,创建对应的 ApplicationContext 实现:

Web 类型 ApplicationContext 实现
SERVLET AnnotationConfigServletWebServerApplicationContext
REACTIVE AnnotationConfigReactiveWebServerApplicationContext
NONE AnnotationConfigApplicationContext

步骤 9:准备上下文(prepareContext()

在刷新容器之前,做一些前置准备工作:

  1. Environment 设置到 ApplicationContext
  2. 执行所有 ApplicationContextInitializer
  3. 将启动类(primarySources)注册为 Bean 定义
  4. 发布 ApplicationContextInitializedEventApplicationPreparedEvent

步骤 10:刷新上下文(refreshContext()

这是整个启动流程中最核心的一步 ------它调用了 Spring 容器的 refresh() 方法,完成了 IoC 容器的初始化。我们单独展开。

三、refresh 容器的核心:AbstractApplicationContext.refresh() 的 12 个步骤

refreshContext() 最终会调用 AbstractApplicationContext.refresh() 方法。这是 Spring IoC 容器的"总调度中心"。

java 复制代码
// AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 步骤1:容器启动前的准备与状态验证
        prepareRefresh();
        
        // 步骤2:获取底层 BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 步骤3:配置 BeanFactory 的标准特征
        prepareBeanFactory(beanFactory);
        
        try {
            // 步骤4:允许子容器对 BeanFactory 进行后置处理
            postProcessBeanFactory(beanFactory);
            
            // 步骤5:实例化并调用所有 BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);
            
            // 步骤6:注册 BeanPostProcessor
            registerBeanPostProcessors(beanFactory);
            
            // 步骤7:初始化消息源(国际化支持)
            initMessageSource();
            
            // 步骤8:初始化应用事件广播器
            initApplicationEventMulticaster();
            
            // 步骤9:留给子类初始化其他特殊的 Bean
            onRefresh();
            
            // 步骤10:注册监听器,并发布早期事件
            registerListeners();
            
            // 步骤11:完成所有单例 Bean 的实例化(重点!)
            finishBeanFactoryInitialization(beanFactory);
            
            // 步骤12:完成刷新,发布相应事件
            finishRefresh();
            
        } catch (BeansException ex) {
            // 异常处理:销毁已创建的单例
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        } finally {
            resetCommonCaches();
        }
    }
}

步骤 2:obtainFreshBeanFactory()

获取或创建一个新的 BeanFactory 实例(通常是 DefaultListableBeanFactory)。这一步会加载所有的 Bean 定义(BeanDefinition)------无论是通过 XML、注解还是 Java 配置定义的。

步骤 5:invokeBeanFactoryPostProcessors()------自动配置的触发时机

这是自动配置被加载的关键步骤

BeanFactoryPostProcessor 是在 Bean 实例化之前 执行的,它可以修改 BeanDefinition。而 Spring Boot 的自动配置正是通过 ConfigurationClassPostProcessor(一个 BeanFactoryPostProcessor)来解析 @Configuration 类、处理 @Import(包括 @EnableAutoConfiguration 导入的 AutoConfigurationImportSelector)。

简单说,自动配置类的加载发生在 invokeBeanFactoryPostProcessors() 阶段。AutoConfigurationImportSelector 会读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,加载所有自动配置类,然后通过 @Conditional 条件注解决定哪些配置类真正生效。

步骤 6:registerBeanPostProcessors()

注册所有 BeanPostProcessor。这些后置处理器会在 Bean 初始化前后被调用,AOP 的代理创建就是通过 BeanPostProcessor 实现的。

步骤 11:finishBeanFactoryInitialization()------所有 Bean 被创建

这一步实例化所有非懒加载的单例 Bean 。每个 Bean 都会经历我们在第 18 篇讲过的完整生命周期:实例化 → 属性赋值 → 初始化(Aware → BeanPostProcessor 前置 → @PostConstructInitializingBeaninit-methodBeanPostProcessor 后置)。

四、自动配置的触发时机:定位到源码行

把自动配置在整个启动流程中的位置精确地定位一下:

复制代码
SpringApplication.run()
    ↓
new SpringApplication()  ← ① 初始化:加载 ApplicationListener 等
    ↓
SpringApplication.run()  ← ② 执行 run 方法
    ↓
prepareEnvironment()     ← ③ 准备环境,加载 application.yml
    ↓
createApplicationContext()
    ↓
prepareContext()         ← ④ 准备上下文,注册启动类
    ↓
refreshContext()
    ↓
AbstractApplicationContext.refresh()
    ↓
invokeBeanFactoryPostProcessors()  ← ⑤ ★ 自动配置在这里触发!
    ├── ConfigurationClassPostProcessor 解析 @Configuration
    ├── @EnableAutoConfiguration → AutoConfigurationImportSelector
    ├── 读取 AutoConfiguration.imports 文件
    ├── 应用 @Conditional 条件过滤
    └── 注册符合条件的自动配置类
    ↓
finishBeanFactoryInitialization()  ← ⑥ 实例化所有单例 Bean
    ↓
应用启动完成

关键认知 :自动配置不是在 SpringApplication 初始化时加载的,而是在 refresh()invokeBeanFactoryPostProcessors() 阶段------此时 Bean 还没有被实例化 ,但 Bean 的定义(BeanDefinition)已经可以被修改和新增。

这也是为什么自动配置类上可以用 @ConditionalOnMissingBean------因为在 invokeBeanFactoryPostProcessors() 阶段,容器中已经注册了用户自定义的 Bean 定义,自动配置可以判断"这个 Bean 用户有没有自己定义",从而决定是否创建默认的 Bean。

五、事件机制:启动过程中的"广播站"

Spring Boot 的启动过程中,事件机制扮演着"广播站"的角色------在不同阶段发布不同的事件,让监听器可以在特定时机插入自定义逻辑。

两种监听器

类型 接口 作用范围
ApplicationListener Spring 框架 监听 Spring 容器内的事件
SpringApplicationRunListener Spring Boot 监听 Spring Boot 启动过程的阶段

Spring Boot 启动过程中的事件发布顺序

复制代码
listeners.starting()
    ↓
ApplicationStartingEvent
    ↓
prepareEnvironment()
    ↓
ApplicationEnvironmentPreparedEvent
    ↓
prepareContext()
    ↓
ApplicationContextInitializedEvent
    ↓
ApplicationPreparedEvent
    ↓
refreshContext()
    ↓
(容器刷新完成)
    ↓
listeners.started()
    ↓
ApplicationStartedEvent
    ↓
callRunners()
    ↓
listeners.ready()
    ↓
ApplicationReadyEvent

自定义监听器的用法

java 复制代码
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("应用已就绪,可以执行初始化数据、预热缓存等操作");
    }
}

或者使用 @EventListener 注解:

java 复制代码
@Component
public class MyEventListener {
    @EventListener(ApplicationReadyEvent.class)
    public void onReady() {
        System.out.println("应用已就绪!");
    }
}

代码示例

示例一:在启动过程中添加自定义 Listener

方式一:实现 ApplicationListener 接口

java 复制代码
package com.example.demo.listener;

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class CustomStartedListener implements ApplicationListener<ApplicationStartedEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("🟢 [监听器] ApplicationStartedEvent ------ 容器已刷新,应用启动中");
        // 可以在这里做一些初始化工作,但此时 Runner 还没执行
    }
}

方式二:使用 @EventListener 注解(更简洁)

java 复制代码
package com.example.demo.listener;

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class CustomReadyListener {

    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReady() {
        System.out.println("✅ [监听器] ApplicationReadyEvent ------ 应用已完全就绪!");
        // 在这里做初始化数据、预热缓存等操作
    }

    @EventListener(ApplicationFailedEvent.class)
    public void onApplicationFailed(ApplicationFailedEvent event) {
        System.err.println("❌ [监听器] ApplicationFailedEvent ------ 应用启动失败");
        Throwable exception = event.getException();
        System.err.println("异常原因: " + exception.getMessage());
    }
}

方式三:通过 SpringApplication.addListeners() 编程式添加

java 复制代码
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(DemoApplication.class);
        app.addListeners(new CustomStartedListener());
        app.run(args);
    }
}

运行观察

启动应用后,控制台会按顺序输出:

复制代码
🟢 [监听器] ApplicationStartedEvent ------ 容器已刷新,应用启动中
...(其他启动日志)...
✅ [监听器] ApplicationReadyEvent ------ 应用已完全就绪!

示例二:分析自动配置的加载时机(断点调试)

如果你想亲自验证自动配置的加载时机,可以在 IDE 中做以下操作:

第一步:在 AutoConfigurationImportSelector.selectImports() 方法上打断点

这个方法的调用链是:

复制代码
AbstractApplicationContext.refresh()
  → invokeBeanFactoryPostProcessors()
    → ConfigurationClassPostProcessor.processConfigBeanDefinitions()
      → ConfigurationClassParser.processImports()
        → AutoConfigurationImportSelector.selectImports()

第二步:以 Debug 模式启动应用

当断点命中时,观察调用栈(Call Stack):

复制代码
selectImports:XXX, AutoConfigurationImportSelector
processImports:XXX, ConfigurationClassParser
processConfigurationClass:XXX, ConfigurationClassParser
processConfigBeanDefinitions:XXX, ConfigurationClassPostProcessor
invokeBeanFactoryPostProcessors:XXX, AbstractApplicationContext
refresh:XXX, AbstractApplicationContext
refreshContext:XXX, SpringApplication
run:XXX, SpringApplication
main:XXX, DemoApplication

关键观察 :调用栈中可以看到,selectImports() 是在 refresh()invokeBeanFactoryPostProcessors() 的调用链中被触发的------印证了自动配置在 invokeBeanFactoryPostProcessors() 阶段加载

第三步:查看自动配置报告

application.yml 中添加:

yaml 复制代码
debug: true

启动后,控制台会输出自动配置报告,显示哪些配置类被加载(Positive matches)和未被加载(Negative matches)及其原因。

调试技巧 :在 AutoConfigurationImportSelectorgetAutoConfigurationEntry() 方法中,你可以看到自动配置类被加载、过滤、去重的完整过程。

新手错误 vs 正确姿势

错误表象 根本原因 正确姿势
启动时某些 Bean 未加载,明明加了 @Component 却不生效 @ComponentScan 默认只扫描启动类所在包及其子包 使用 @ComponentScan(basePackages = {"com.example.module1", "com.example.module2"}) 显式指定扫描范围
引入了一个 Starter,但它的自动配置没有生效 自动配置的条件不满足(如缺少某个类、某个配置属性不匹配) application.yml 中添加 debug: true,查看自动配置报告中的 Negative matches
启动慢,项目启动需要几十秒 类路径扫描范围过大,或大量 Bean 在启动时被实例化 优化 @ComponentScan 范围;开启延迟初始化 spring.main.lazy-initialization=true
@EventListener 方法没有被调用 监听器类未被 Spring 管理(未加 @Component 确保监听器类添加了 @Component 或在配置类中显式声明
ApplicationReadyEvent 中执行了耗时操作,导致应用启动完成但接口无法访问 误解了 ApplicationReadyEvent 的语义------此时应用已完全就绪,但监听器中的逻辑是同步执行 如果需要在启动后执行耗时操作,使用 @Async 异步执行,或使用 CommandLineRunner / ApplicationRunner

疑难深度追问

Q1:SpringApplication.run()ApplicationContext.refresh() 是什么关系?

SpringApplication.run() 是 Spring Boot 层面的启动入口,它负责统筹整个启动流程 ------包括创建 SpringApplication 实例、准备 Environment、创建 ApplicationContext、打印 Banner、执行 Runner 等。

ApplicationContext.refresh() 是 Spring 容器层面的核心方法,它负责初始化 IoC 容器------加载 Bean 定义、注册后置处理器、实例化单例 Bean 等。

两者的关系是:SpringApplication.run() 调用了 ApplicationContext.refresh()refresh()run() 方法中最核心的一步,但不是全部。

Q2:Spring Boot 启动时是如何判断当前是 Web 应用还是非 Web 应用的?

通过 WebApplicationType.deduceFromClasspath() 方法。它检查 classpath 中是否存在特定的类:

java 复制代码
static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null)
            && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)) {
        return REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return NONE;
        }
    }
    return SERVLET;
}
  • 如果有 DispatcherHandler(WebFlux)且没有 DispatcherServlet(Spring MVC)→ REACTIVE
  • 如果有 ServletConfigurableWebApplicationContextSERVLET
  • 否则 → NONE

Q3:Spring Boot 启动过程中的事件发布机制有什么实际用途?

最常见的用途有三个:

  1. 启动后初始化数据 :监听 ApplicationReadyEvent,在应用完全启动后执行数据初始化、缓存预热等操作
  2. 启动失败告警 :监听 ApplicationFailedEvent,在启动失败时发送告警通知
  3. 动态配置加载ConfigFileApplicationListener 监听 ApplicationEnvironmentPreparedEvent,在环境准备阶段加载 application.yml

思考与延伸

  1. 动手验证 :在 application.yml 中添加 debug: true,启动应用后查看自动配置报告,找出 3 个 Positive matches 和 3 个 Negative matches,理解它们生效/不生效的原因。

  2. 思考题ApplicationReadyEventApplicationStartedEvent 有什么区别?如果你需要在应用启动后执行初始化数据的逻辑,应该监听哪个事件?为什么?

  3. 延伸阅读 :Spring Boot 官方文档的 "SpringApplication" 章节对启动流程和事件机制有详细的说明。另外,SpringApplication.run() 方法的源码是理解整个启动流程的最佳入口------建议在 IDE 中打开源码,跟着文中的步骤走一遍。

参考与延伸阅读

  • Spring Boot. SpringApplication. Spring Boot Documentation, 3.2.x
  • Spring Boot. Application Events and Listeners. Spring Boot Documentation
  • 腾讯云. 深入解析Spring Boot启动魔法:SpringApplication.run()源码全流程拆解. 2025-08-27
  • 腾讯云. 深入解析Spring Boot核心启动流程与上下文初始化. 2025-08-27
  • CSDN. Spring IOC容器启动全流程揭秘:从配置元数据加载到Refresh()方法执行. 2025-10-25
  • CSDN. Spring Boot 启动过程深度解析. 2025-02-19