Spring Boot 启动流程源码解析:从 `main()` 到 Web 服务就绪

Spring Boot 启动流程源码解析:从 main() 到 Web 服务就绪

一句 SpringApplication.run() 背后,藏着整个 Spring 生态的启动引擎。

你是否曾:

  • 在面试被问:"Spring Boot 启动过程做了哪些事?"
  • 遇到启动慢、Bean 找不到、配置不生效等问题却无从下手?
  • 想自定义启动行为(如动态加载配置、埋点监控),但不知从何切入?

答案,都在 SpringApplication.run() 的源码里

今天,我们就逐行拆解 Spring Boot 3.x(兼容 2.x)的启动主流程 ,带你从 main() 方法一路走到内嵌 Tomcat 启动完成!

一、入口:main() 方法

typescript 复制代码
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

看似简单,实则调用了 SpringApplication 的静态方法:

typescript 复制代码
// SpringApplication.java
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 实例,再调用其 run() 方法。

二、阶段 1:构造 SpringApplication 对象

scss 复制代码
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. 推断应用类型(SERVLET / REACTIVE / NONE)
    this.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());

    // 2. 从 spring.factories 加载 BootstrapRegistryInitializer
    this.bootstrapRegistryInitializers = new ArrayList<>(
                getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

    // 3. 从 spring.factories 加载 ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

    // 4. 从 spring.factories 加载 ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 5. 推断主配置类(即包含 main 方法的类)
    this.mainApplicationClass = deduceMainApplicationClass();
}

🔑 核心动作:

  • 推断 Web 类型

    css 复制代码
    SERVLET:classpath 中存在 Spring MVC 相关类(如 DispatcherServlet)
    REACTIVE:存在 WebFlux 相关类(如 DispatcherHandler)
    NONE:非 Web 应用(如批处理、定时任务)
  • 加载扩展点 :通过 SpringFactoriesLoader 读取 META-INF/spring.factories 中的 SPI 实现。

💡 这就是 Spring Boot 自动装配和扩展机制的起点

三、阶段 2:执行 run(args) ------ 启动主流程

这是最核心的方法,我们分步解析:

步骤 1:准备监听器

ini 复制代码
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
  • getRunListeners() 返回所有 SpringApplicationRunListener 实例(默认是 EventPublishingRunListener
  • starting() 发布 ApplicationStartingEvent
    → 可用于早期日志初始化、APM 埋点

步骤 2:准备 Environment(环境)

ini 复制代码
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

prepareEnvironment() 中:

  • 创建 Environment(StandardServletEnvironment)
  • 调用 environmentPrepared() → 发布 ApplicationEnvironmentPreparedEvent
  • 此时 application.properties 已加载!

🌟 实战价值:Nacos/Apollo 客户端在此阶段注入远程配置!


步骤 3:创建 ApplicationContext(应用上下文)

ini 复制代码
context = createApplicationContext();

根据 webApplicationType 选择上下文类型:

  • SERVLETAnnotationConfigServletWebServerApplicationContext
  • REACTIVEAnnotationConfigReactiveWebServerApplicationContext

该上下文继承自 GenericApplicationContext,并具备 内嵌 Web 容器支持

步骤 4:准备上下文

scss 复制代码
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

内部关键操作:

  • 注册 banner Bean
  • 应用所有 ApplicationContextInitializer
  • 发布 ApplicationContextInitializedEvent

⚠️ 注意:此时 Bean 还未实例化,只是定义已加载。

步骤 5:刷新上下文(Refresh)------ 最重量级阶段!

scss 复制代码
refreshContext(context);

最终调用 AbstractApplicationContext.refresh()(Spring Framework 的核心方法):

scss 复制代码
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新(记录启动时间、设置活跃状态)
        prepareRefresh();

        // 2. 获取 BeanFactory(通常是 DefaultListableBeanFactory)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 3. 配置 BeanFactory(设置类加载器、表达式解析器等)
        prepareBeanFactory(beanFactory);

        // 4. 执行 BeanFactoryPostProcessor(如 @ConfigurationProperties 绑定)
        invokeBeanFactoryPostProcessors(beanFactory);

        // 5. 注册 BeanPostProcessor
        registerBeanPostProcessors(beanFactory);

        // 6. 初始化 MessageSource(国际化)
        initMessageSource();

        // 7. 初始化事件广播器
        initApplicationEventMulticaster();

        // 8. 【模板方法】子类可扩展(如 ServletWebServerApplicationContext 会在此启动内嵌容器)
        onRefresh();

        // 9. 注册监听器
        registerListeners();

        // 10. 实例化所有非懒加载的单例 Bean!
        finishBeanFactoryInitialization(beanFactory);

        // 11. 完成刷新(发布 ContextRefreshedEvent)
        finishRefresh();
    }
}
🔥 重点子阶段解析:
  • invokeBeanFactoryPostProcessors

    ConfigurationClassPostProcessor 扫描 @Component@Bean,解析自动配置类(spring.factories 中的 EnableAutoConfiguration

  • onRefresh()(在 Servlet 上下文中)

    typescript 复制代码
    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer(); // 启动内嵌 Tomcat/Jetty
        }
    }
  • finishBeanFactoryInitialization

    → 调用 preInstantiateSingletons(),触发所有单例 Bean 的创建(包括依赖注入、@PostConstruct

步骤 6:执行 Runner 启动完成

scss 复制代码
/ 执行 CommandLineRunner / ApplicationRunner
callRunners(context, applicationArguments);

✅ 此时服务已完全就绪,可处理请求!

四、启动流程全景图(简化版)

scss 复制代码
ain()
  ↓
new SpringApplication()
  ├── 推断 Web 类型
  ├── 加载 Initializers & Listeners
  ↓
run(args)
  ├── starting() → ApplicationStartingEvent
  ├── prepareEnvironment() → 加载 application.properties
  ├── createApplicationContext()
  ├── prepareContext() → 注册主配置类
  ├── refreshContext()
  │     ├── invokeBeanFactoryPostProcessors → 自动配置生效
  │     ├── onRefresh() → 启动内嵌 Web 容器
  │     └── finishBeanFactoryInitialization → 初始化所有 Bean
  ├── callRunners() → 执行启动后任务

五、学源码有什么用?实战场景举例

场景 利用的启动阶段 扩展方式
动态加载远程配置 environmentPrepared 实现 EnvironmentPostProcessor
启动耗时分析 starting() / running() 自定义 SpringApplicationRunListener
服务注册延迟 ContextRefreshedEvent 监听事件,确保 Bean 全部就绪
自定义 Banner prepareContext 阶段 实现 Banner 接口
避免循环依赖报错 理解 finishBeanFactoryInitialization 顺序 调整依赖关系或使用 @Lazy

📌 关注我 ,每天5分钟,带你从 Java 小白变身编程高手!

👉 点赞 + 关注,让更多小伙伴一起进步!

相关推荐
漂亮的小碎步丶2 小时前
【3】Spring事务管理
java·数据库·spring
WZTTMoon2 小时前
Spring Boot Swagger3 使用指南
java·spring boot·后端·swagger3
Java天梯之路2 小时前
Spring Boot 钩子全集实战(一):构造与配置阶段
java·spring boot·面试
月明长歌3 小时前
【码道初阶】LeetCode 622:设计循环队列:警惕 Rear() 方法中的“幽灵数据”陷阱
java·算法·leetcode·职场和发展
程序员根根3 小时前
SpringBoot Web 入门核心知识点(快速开发案例 + 分层解耦实战)
java·spring boot
Dylan的码园3 小时前
链表与LinkedList
java·数据结构·链表
【非典型Coder】3 小时前
JVM 垃圾收集器中的记忆集与读写屏障
java·开发语言·jvm
小橙编码日志3 小时前
Java事务常见的失效场景总结
后端·面试
feathered-feathered3 小时前
Redis【事务】(面试相关)与MySQL相比较,重点在Redis事务
android·java·redis·后端·mysql·中间件·面试