人人都会AI编程

26.4 Spring Boot 启动流程源码解析

更新时间:2026-07-11

如果你曾好奇:为什么在 main 方法里执行一行 SpringApplication.run(),整个应用就能启动并对外服务?这一节我们就深入 SpringApplication 的源码,拆解 Spring Boot 启动的完整过程。理解这些步骤,不仅有助于排查启动时的诡异问题,也能让你对自动配置、事件监听、扩展点有更立体的认识。

26.4.1 SpringApplication.run() 的两大阶段

从调用入口看:

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

run 静态方法内部会先创建一个 SpringApplication 实例,然后调用实例方法 run。整个启动被清晰地分为 准备阶段运行阶段

26.4.2 准备阶段:new SpringApplication(primarySources)

SpringApplication 的构造器主要做了两件事:推断应用类型、加载初始化器与监听器。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath(); // ①
    this.bootstrapRegistryInitializers = new ArrayList<>(
            getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); // ②
    setInitializers(
            getSpringFactoriesInstances(ApplicationContextInitializer.class)); // ③
    setListeners(getSpringFactoriesInstances(ApplicationListener.class));    // ④
    this.mainApplicationClass = deduceMainApplicationClass();                // ⑤
}

推断 Web 类型deduceFromClasspath() 会检查 classpath 下是否存在 DispatcherServlet 等关键类,决定创建 SERVLETREACTIVE 还是普通的 ApplicationContext
②-④ 加载扩展点:通过 SpringFactoriesLoaderMETA-INF/spring.factories(Spring Boot 2.x)或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)中读取并实例化 ApplicationContextInitializerApplicationListener 等。这些是 Spring Boot 留给开发者的常用扩展接口。
推断主类:通过检查调用栈,找到包含 main 方法的类并记录下来。

实用提醒ApplicationContextInitializerApplicationListener 是定制启动行为的两个核心入口。如果你希望在容器刷新之前对上下文做一些个性化配置,可以实现前者并通过 spring.factories 注册。

26.4.3 运行阶段:实例方法 run(String... args)

这是真正的启动流程,核心代码在 SpringApplication.run() 实例方法中。我把整个过程拆成 10 个关键步骤,并附上对应的源码片段。

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // 1
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);    // 2
    listeners.starting(bootstrapContext, this.mainApplicationClass);   // 3
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                bootstrapContext, applicationArguments);                // 4
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);                // 5
        context = createApplicationContext();                           // 6
        context.setApplicationStartup(this.applicationStartup);
        prepareContext(bootstrapContext, context, environment,
                listeners, applicationArguments, printedBanner);        // 7
        refreshContext(context);                                        // 8
        afterRefresh(context, applicationArguments);                    // 9
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.started(context, timeTakenToStartup);                // 10
        callRunners(context, applicationArguments);                    // 11
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);                    // 12
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

下面逐一解析每个步骤。


1. 创建 BootstrapContext(Spring Boot 2.4+)

DefaultBootstrapContext 是一个轻量级的引导上下文,用于在环境准备之前共享资源(如配置信息的早期来源)。它会在环境准备好后被关闭,其生命周期比 ApplicationContext 更短。

2. 获取 SpringApplicationRunListeners

spring.factories 加载 SpringApplicationRunListener 的实现,默认只有 EventPublishingRunListener,它会将启动过程中的各个里程碑转化为 Spring 事件,广播给之前加载的 ApplicationListener

3. 发布 starting 事件

listeners.starting(...) 标志着启动正式开始。此时 ApplicationContext 还未创建,开发者可以通过监听 ApplicationStartingEvent 做一些非常早期的处理(比如初始化日志框架)。

4. 准备 Environment

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        DefaultBootstrapContext bootstrapContext,
        ApplicationArguments applicationArguments) {
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(bootstrapContext, environment);
    // ... 移动到SpringApplication中处理
    bindToSpringApplication(environment);
    // 如果有自定义的转换逻辑,此处还会执行
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}
  • 根据 Web 类型创建对应的 Environment(如 StandardServletEnvironment)。
  • 将命令行参数、系统属性、配置文件等添加到环境中。
  • 发布 ApplicationEnvironmentPreparedEvent 事件,此时 ConfigFileApplicationListener 会监听到并加载 application.properties/yml 等配置文件。

5. 打印 Banner

如果启用了 banner,控制台会打印 Spring 的 ASCII 艺术字。你可以通过 spring.banner.location 指定自定义 banner,或实现 Banner 接口编程式打印。

6. 创建 ApplicationContext

根据之前推断的 webApplicationType 实例化对应的容器:AnnotationConfigServletWebServerApplicationContext(Servlet)、AnnotationConfigReactiveWebServerApplicationContext(响应式)或 AnnotationConfigApplicationContext(普通)。

7. 准备上下文 (prepareContext)

这一步在 refresh 之前,将各种准备好的零件安装到刚创建的空容器中:

private void prepareContext(DefaultBootstrapContext bootstrapContext, 
        ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, 
        Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);                // 调用所有 ApplicationContextInitializer
    listeners.contextPrepared(context);
    // 关闭 BootstrapContext
    bootstrapContext.close(context);
    // 将启动参数、Banner 等注册为 Bean
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    // 加载 sources,即主配置类
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

关键点:

  • applyInitializers 会执行所有 ApplicationContextInitializer,允许你在 refresh 前对上下文进行编程式调整。
  • load(context, sources) 把主配置类(被 @SpringBootApplication 标注的类)注册为 Bean 定义,它是后续自动配置的入口。

8. 刷新上下文 (refreshContext)

调用 AbstractApplicationContext.refresh(),这是 Spring Framework 的核心启动方法,也是整个过程中最“重”的一步。主要内容包括:

  • prepareRefresh():准备刷新,设置启动时间、属性源等。
  • obtainFreshBeanFactory():解析配置类,扫描并注册所有 Bean 定义。
  • prepareBeanFactory():注册内置 Bean,如 environmentsystemProperties
  • postProcessBeanFactory():留给子类扩展,例如 Web 容器的上下文会在这里注册 ServletContext。Spring Boot 的自动配置入口 ConfigurationClassPostProcessor 也会在这一阶段解析 @Configuration 类。
  • invokeBeanFactoryPostProcessors():调用所有 BeanFactoryPostProcessor,其中 ConfigurationClassPostProcessor 负责处理 @ComponentScan@Import@ImportResource 等,并触发 @EnableAutoConfiguration 的加载逻辑,最终完成自动配置。
  • registerBeanPostProcessors():注册 BeanPostProcessor(如 AutowiredAnnotationBeanPostProcessor)。
  • initMessageSource()initApplicationEventMulticaster():国际化与事件广播器。
  • onRefresh():由子类实现。Web 容器上下文在此创建内嵌的 Web 服务器(Tomcat/Jetty/Undertow)并启动。
  • registerListeners():注册已存在的 ApplicationListener Bean。
  • finishBeanFactoryInitialization():实例化所有非懒加载的单例 Bean。
  • finishRefresh():发布 ContextRefreshedEvent,启动 Web 服务器完成并接受请求。

9. afterRefresh

afterRefresh 是一个空实现,留给子类覆盖。如果你需要自定义 SpringApplication,可以在此处添加容器刷新后的额外处理。

10. 发布 started 事件

listeners.started(context, timeTakenToStartup) 发布 ApplicationStartedEvent。此时 ApplicationContext 已刷新,但 CommandLineRunnerApplicationRunner 还未执行。你可以利用这个事件执行在应用真正“就绪”之前的逻辑。

11. 调用 Runner

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    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);
        }
    }
}

这两个 Runner 是 Spring Boot 提供的应用启动后的回调接口。常用于启动后加载缓存、预加载数据或打印启动信息。ApplicationRunnerCommandLineRunner 的区别仅在于参数封装不同,前者是 ApplicationArguments,后者是原始 String... args

12. 发布 ready 事件

listeners.ready(context, timeTakenToReady) 发布 ApplicationReadyEvent。此时整个启动流程完全结束,应用真正处于可用状态。你可以在此时执行一些必须等技术完全就绪后才能进行的操作。

26.4.4 启动过程的事件体系

Spring Boot 在启动过程中会依次发布这些事件:

  1. ApplicationStartingEvent – 启动开始
  2. ApplicationEnvironmentPreparedEvent – 环境准备完成
  3. ApplicationContextInitializedEvent – Context 创建并经过初始化后
  4. ApplicationPreparedEvent – Context 准备完成,即将刷新
  5. ApplicationStartedEvent – Context 刷新完成,Runner 执行前
  6. ApplicationReadyEvent – Runner 执行完毕,应用已就绪
  7. ApplicationFailedEvent – 启动过程中出现异常

理解这些事件顺序,就能在合适的时机插入自定义逻辑。例如,你可以在 ApplicationEnvironmentPreparedEvent 中添加自定义的 PropertySource,或者在 ApplicationReadyEvent 中记录“服务已启动”的标记。

26.4.5 总结:启动流程全景图

简而言之一句话:SpringApplication 通过事件驱动的方式,串联起 Environment 准备、Context 创建、自动配置加载、Web 服务器启动、Runner 回调等步骤。其中 refreshContext 是 Spring Framework 的经典核心,而 Spring Boot 在它的前后添加了大量便于扩展的钩子(Listener、Initializer、Runner),使得框架在保持强大能力的同时,又极为灵活。

下一次当你面对一个“启动半天没反应”的问题时,不妨顺着这个流程去排查:是 SpringFactories 加载卡住?是某个自动配置类耗时过长?还是 Web 服务器端口被占用?掌握了启动源码的脉络,你就不再是一个只会使用注解的开发者,而是可以真正驾驭框架的人。