Spring Boot 启动流程到底做了什么?从 main 到就绪的全链路源码拆解

写了这么多 Spring 原理,但每次面试被问"Spring Boot 启动流程"还是说不全?今天从 SpringApplication.run() 出发,逐行跟踪源码,把启动全链路拆清楚。读完这篇,从 main 方法到应用就绪,每一步你都能说出在干什么、为什么这样干。


一、从一个 main 方法说起

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

这三行代码背后,Spring Boot 做了大量工作。我们逐层拆解。

scss 复制代码
SpringApplication.run() 做了什么?
│
├── 1. 创建 SpringApplication 实例
│   ├── 推断 Web 应用类型
│   ├── 加载 ApplicationContextInitializer
│   ├── 加载 ApplicationListener
│   └── 推断主配置类
│
├── 2. 运行 run() 方法
│   ├── 创建并启动计时器
│   ├── 获取 SpringApplicationRunListeners
│   ├── 准备 Environment
│   ├── 创建 ApplicationContext
│   ├── 准备上下文
│   ├── 刷新上下文(核心!)
│   ├── 执行后置处理
│   └── 发布就绪事件
│
└── 3. 返回 ApplicationContext

二、SpringApplication 构造方法

java 复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 推断 Web 应用类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加载 Initializer
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 加载 Listener
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 推断主配置类
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

2.1 推断 Web 应用类型

java 复制代码
static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", ...)
            && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", ...)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, ...)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}
css 复制代码
推断逻辑
│
├── 有 reactive + 无 servlet → REACTIVE
├── 无 servlet 相关类 → NONE(非 Web 应用)
└── 默认 → SERVLET

2.2 加载 Spring.factories

getSpringFactoriesInstances()META-INF/spring.factories 中加载指定类型的实现类。

markdown 复制代码
Spring.factories 加载机制
│
├── 扫描路径
│   ├── spring-boot.jar 中的 spring.factories
│   ├── spring-boot-autoconfigure.jar 中的 spring.factories
│   └── 项目自定义的 spring.factories
│
├── 缓存机制
│   ├── 第一次加载时遍历所有 jar
│   ├── 结果缓存在 SpringFactoriesLoader.cache
│   └── 后续直接读缓存
│
└── 加载的组件
    ├── ApplicationContextInitializer(7 个默认)
    ├── ApplicationListener(9 个默认)
    └── 其他(SpringBootExceptionReporter 等)

三、run() 方法核心流程

这是整个启动最核心的方法,展开源码:

java 复制代码
public ConfigurableApplicationContext run(String... args) {
    // 1. 计时器
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // 2. 获取 RunListeners(通过 spring.factories)
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();

    // 3. 准备 Environment
    ConfigurableEnvironment environment = prepareEnvironment(listeners, args);
    listeners.environmentPrepared(environment);

    // 4. 创建 ApplicationContext
    ConfigurableApplicationContext context = createApplicationContext();

    // 5. 准备上下文
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);

    // 6. 刷新上下文(最核心!)
    refreshContext(context);

    // 7. 后置处理
    afterRefresh(context, applicationArguments);

    // 8. 停止计时
    stopWatch.stop();

    // 9. 发布就绪事件
    listeners.started(context);
    callRunners(context);

    return context;
}

3.1 各阶段耗时分布

erlang 复制代码
典型启动耗时分布(总计 ~2.5s)
│
├── starting + environment     ~0.2s   (8%)
├── createApplicationContext    ~0.1s   (4%)
├── prepareContext              ~0.1s   (4%)
├── refreshContext              ~1.8s   (72%)  ← 大头!
│   ├── Bean 定义扫描           ~0.3s
│   ├── Bean 创建与依赖注入     ~1.0s
│   ├── 自动配置处理            ~0.3s
│   └── 其他                    ~0.2s
├── afterRefresh               ~0.1s   (4%)
└── callRunners                ~0.2s   (8%)

四、Environment 准备

java 复制代码
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners, ApplicationArguments arguments) {
    // 1. 创建 Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    
    // 2. 配置 Environment
    configureEnvironment(environment, arguments.getSourceArgs());
    
    // 3. ConfigurationProperties 绑定
    ConfigurationPropertySources.attach(environment);
    
    // 4. 通知 Listener
    listeners.environmentPrepared(environment);
    
    // 5. 绑定到 SpringApplication
    bindToSpringApplication(environment);
    
    return environment;
}

Environment 加载的配置源

scss 复制代码
配置源优先级(从高到低)
│
├── 1. 命令行参数          (--server.port=8080)
├── 2. JNDI 属性
├── 3. Java 系统属性       (System.getProperties())
├── 4. 操作系统环境变量     (PORT, JAVA_HOME)
├── 5. RandomValuePropertySource  (${random.int})
├── 6. application-{profile}.yml  (外部)
├── 7. application-{profile}.yml  (jar 内)
├── 8. application.yml           (外部)
└── 9. application.yml           (jar 内)

关键事件ApplicationEnvironmentPreparedEvent,ConfigFileApplicationListener 在此时加载 application.yml


五、创建 ApplicationContext

java 复制代码
protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextClass != null
        ? this.applicationContextClass
        : (ConfigurableApplicationContext) switch (this.webApplicationType) {
            case SERVLET -> new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE -> new AnnotationConfigReactiveWebServerApplicationContext();
            default -> new AnnotationConfigApplicationContext();
        };
}
css 复制代码
三种上下文类型
│
├── SERVLET
│   └── AnnotationConfigServletWebServerApplicationContext
│       └── 内置 Tomcat/Jetty/Undertow
│
├── REACTIVE
│   └── AnnotationConfigReactiveWebServerApplicationContext
│       └── 内置 Netty
│
└── NONE
    └── AnnotationConfigApplicationContext
        └── 无 Web 容器

六、prepareContext:准备上下文

java 复制代码
private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, ...) {
    // 1. 设置 Environment
    context.setEnvironment(environment);
    
    // 2. 后置处理 ApplicationContext
    postProcessApplicationContext(context);
    
    // 3. 执行所有 Initializer
    applyInitializers(context);
    
    // 4. 通知 Listener
    listeners.contextPrepared(context);
    
    // 5. 注册单例 Bean(args, banner 等)
    context.getBeanFactory().registerSingleton("springApplicationArguments", arguments);
    context.getBeanFactory().registerSingleton("springBootBanner", banner);
    
    // 6. 加载 Bean 定义
    Set<Object> sources = getAllSources();
    load(context, sources.toArray(new Object[0]));
    
    // 7. 通知 Listener
    listeners.contextLoaded(context);
}

Initializer 执行顺序

lua 复制代码
默认 Initializer(按 @Order 排序)
│
├── DelegatingApplicationContextInitializer
│   └── 委托给 context.initializer.classes 配置的 Initializer
│
├── SharedMetadataReaderFactoryContextInitializer
│   └── 注册 ConfigurationClassPostProcessor 需要的 MetadataReaderFactory
│
├── ContextIdApplicationContextInitializer
│   └── 设置 ApplicationContext 的 ID
│
├── ConfigurationWarningsApplicationContextInitializer
│   └── 检查常见的错误配置(如错误的包扫描)
│
├── RSocketPortInfoApplicationContextInitializer
│   └── RSocket 端口信息
│
├── ServerPortInfoApplicationContextInitializer
│   └── Web 服务器端口信息(local.server.port)
│
└── ConditionEvaluationReportLoggingListener
    └── 自动配置报告(debug 模式可见)

七、refreshContext:核心刷新

refreshContext() 调用 AbstractApplicationContext.refresh(),这是 Spring 的核心方法。

java 复制代码
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;
        }
    }
}

各步骤详解

yaml 复制代码
refresh() 十二步详解
│
├── Step 1: prepareRefresh
│   ├── 记录启动时间
│   ├── 设置 active 标志
│   └── 初始化占位符属性源
│
├── Step 2: obtainFreshBeanFactory
│   ├── 刷新 BeanFactory(如有必要则重建)
│   └── 返回 BeanFactory
│
├── Step 3: prepareBeanFactory
│   ├── 设置 ClassLoader
│   ├── 注册 BeanExpressionResolver(SpEL)
│   ├── 注册 PropertyEditorRegistrar
│   ├── 注册 Environment、ApplicationContext 等
│   └── 配置自动装配忽略规则
│
├── Step 4: postProcessBeanFactory
│   └── Web 上下文注册 Servlet 相关 Bean
│
├── Step 5: invokeBeanFactoryPostProcessors  ← 核心!
│   ├── 先执行 BeanDefinitionRegistryPostProcessor
│   │   └── ConfigurationClassPostProcessor 解析 @Configuration
│   │       ├── 处理 @ComponentScan
│   │       ├── 处理 @Import
│   │       ├── 处理 @Bean
│   │       └── 触发自动配置(AutoConfiguration)
│   └── 再执行普通 BeanFactoryPostProcessor
│
├── Step 6: registerBeanPostProcessors
│   ├── AutowiredAnnotationBeanPostProcessor
│   ├── CommonAnnotationBeanPostProcessor
│   └── 其他自定义 BeanPostProcessor
│
├── Step 7-8: MessageSource + EventMulticaster
│   └── 国际化 + 事件广播
│
├── Step 9: onRefresh
│   └── Web 服务器启动(Tomcat/Jetty)
│
├── Step 10: registerListeners
│   ├── 注册静态 Listener
│   └── 发布早期事件
│
├── Step 11: finishBeanFactoryInitialization  ← 最耗时!
│   ├── 实例化 ConversionService
│   ├── 冻结 Bean 定义
│   └── 实例化所有非懒加载单例 Bean
│
└── Step 12: finishRefresh
    ├── 清除缓存
    ├── 初始化 LifecycleProcessor
    ├── 发布 ContextRefreshedEvent
    └── 参与 LiveBeansView(JMX)

Step 5 详解:自动配置的触发

java 复制代码
ConfigurationClassPostProcessor 处理链
│
├── 1. 解析 @SpringBootApplication
│   ├── @SpringBootConfiguration → 标记为配置类
│   ├── @EnableAutoConfiguration → 触发自动配置
│   │   ├── @Import(AutoConfigurationImportSelector.class)
│   │   └── AutoConfigurationImportSelector.selectImports()
│   │       ├── 加载 META-INF/spring.factories
│   │       ├── 过滤 @Conditional 条件
│   │       └── 排序(@AutoConfigureOrder)
│   └── @ComponentScan → 扫描当前包及子包
│
├── 2. 处理自动配置类
│   ├── 每个 @Conditional 条件判断
│   ├── 条件不满足 → 跳过
│   └── 条件满足 → 注册 Bean 定义
│
└── 3. 处理 @Bean 方法
    └── 注册为 BeanDefinition

Step 9 详解:Web 服务器启动

scss 复制代码
ServletWebServerApplicationContext.onRefresh()
│
├── createWebServer()
│   ├── 从 BeanFactory 获取 ServletWebServerFactory
│   │   ├── TomcatServletWebServerFactory(默认)
│   │   ├── JettyServletWebServerFactory
│   │   └── UndertowServletWebServerFactory
│   │
│   ├── factory.getWebServer()
│   │   ├── 创建 Tomcat 实例
│   │   ├── 配置 Connector(端口等)
│   │   └── 启动 Tomcat
│   │
│   └── 注册 WebServerGracefulShutdownLifecycle
│
└── 端口绑定完成
    └── 发布 WebServerInitializedEvent

八、callRunners:启动后执行器

java 复制代码
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 : runners) {
        if (runner instanceof ApplicationRunner) {
            ((ApplicationRunner) runner).run(args);
        }
        if (runner instanceof CommandLineRunner) {
            ((CommandLineRunner) runner).run(args.getSourceArgs());
        }
    }
}
css 复制代码
Runner 执行顺序
│
├── 1. 按 @Order 排序
├── 2. ApplicationRunner 先执行
├── 3. CommandLineRunner 后执行
│
└── 典型用途
    ├── 数据初始化
    ├── 缓存预热
    ├── 健康检查
    └── 通知服务就绪

九、完整启动事件序列

scss 复制代码
Spring Boot 启动事件时间线
│
├── 1. starting
│   └── SpringApplicationRunListener.starting()
│
├── 2. environmentPrepared
│   └── Environment 创建完成,配置文件已加载
│
├── 3. contextPrepared
│   └── ApplicationContext 创建完成,Initializer 已执行
│
├── 4. contextLoaded
│   └── Bean 定义已加载,Listener 已注册
│
├── 5. [refresh 过程中]
│   ├── BeanDefinitionRegistryPostProcessor 执行
│   ├── Bean 创建与注入
│   └── Web 服务器启动
│
├── 6. started
│   └── ApplicationContext 刷新完成
│
├── 7. ready
│   └── ApplicationRunner / CommandLineRunner 执行完成
│
└── 完整耗时 = started - starting

十、启动优化 Checklist

# 优化手段 效果 说明
1 开启懒加载 启动提速 30-50% spring.main.lazy-initialization=true,但首次请求变慢
2 排除不需要的自动配置 启动提速 10-20% @SpringBootApplication(exclude={...})
3 减少组件扫描范围 启动提速 5-10% 显式指定 scanBasePackages
4 使用 spring.factories 索引 启动提速 5-10% 添加 spring-boot-autoconfigure-processor 依赖
5 并行初始化 启动提速 15-30% Spring Boot 3.2+ 的 spring.main.startup-mode=PARALLEL
6 优化日志输出 微小提升 减少不必要的日志
7 使用 GraalVM Native Image 启动提速 90%+ 编译时优化,启动到毫秒级

懒加载 vs 急加载

对比项 急加载(默认) 懒加载
启动时间 慢(创建所有 Bean) 快(只创建必要的)
首次请求 慢(需要创建 Bean)
问题发现 启动时就能发现 运行时才发现
适用场景 生产环境 开发环境
内存占用 启动即占满 按需分配

十一、调试技巧

11.1 查看自动配置报告

bash 复制代码
# 启动时加 --debug 或设置
debug=true

输出:

markdown 复制代码
CONDITIONS EVALUATION REPORT
============================

Positive matches:
-----------------
   AopAutoConfiguration matched:
      - @ConditionalOnProperty (spring.aop.auto=true) matched

Negative matches:
-----------------
   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory'

11.2 查看启动耗时

yaml 复制代码
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: startup

访问 /actuator/startup 可查看每个阶段的耗时。

11.3 自定义启动事件监听

java 复制代码
@Component
public class StartupLogger implements ApplicationListener<ApplicationStartedEvent> {
    
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        log.info("应用启动完成,耗时:{}ms", 
            event.getTimeTaken().toMillis());
    }
}

十二、面试速答模板

Q:Spring Boot 启动流程?

SpringApplication.run() 分两大步:构造和运行。

构造时推断应用类型、加载 Initializer 和 Listener。

运行时按序执行:创建 Environment → 创建 ApplicationContext → prepareContext(执行 Initializer、加载 Bean 定义)→ refresh(核心!执行 BeanFactoryPostProcessor、注册 BeanPostProcessor、实例化单例 Bean、启动 Web 服务器)→ afterRefresh → 发布就绪事件。

其中 refresh 最耗时,占 70%+,主要是 Bean 创建和自动配置处理。

Q:refresh() 哪步最关键?

invokeBeanFactoryPostProcessors,触发 ConfigurationClassPostProcessor,解析 @Configuration、@ComponentScan、@Import,完成自动配置。没有这一步,所有 Bean 定义都不会被注册。

Q:自动配置怎么生效的?

@SpringBootApplication 包含 @EnableAutoConfiguration,它 @Import 了 AutoConfigurationImportSelector,从 spring.factories 加载自动配置类,再经 @Conditional 过滤,条件满足的配置类注册到容器。


下一篇我们聊 Spring Bean 生命周期:从实例化到销毁,Bean 到底经历了哪些回调?为什么InitializingBean 比 @PostConstruct 先执行?BeanPostProcessor 和 Aware 接口谁先谁后?


本文是 Java 技术系列第 8 篇,系列目录:

  1. Spring 循环依赖三级缓存源码拆解
  2. Spring 事务失效 7 种场景
  3. Spring Boot 自动配置原理
  4. Spring AOP 代理选择与原理
  5. JVM 内存模型与 GC 调优
  6. MyBatis-Plus 插件机制原理
  7. Spring 事件机制与监听器模式
  8. 本文:Spring Boot 启动流程源码拆解
相关推荐
花生了什么事o2 小时前
Docker 部署 SpringBoot:从镜像构建到服务器运行
服务器·spring boot·docker
摇滚侠2 小时前
《SpringBoot 3:入门与应用实战》第 15 章 生产级特性 使用监控体系 阅读笔记
spring boot·笔记·后端
倔强的石头1062 小时前
Spring Boot 接入金仓数据库:配置分层、启动自检与常见错误
数据库·spring boot·后端
仍然.3 小时前
SpringBoot统一功能处理
java·spring boot·后端
凤山老林3 小时前
SpringBoot + Configuration2 实现配置的实时双向更新
java·spring boot·后端
2601_963869955 小时前
【计算机毕业设计】基于 Spring Boot+Vue的手工体验馆管理系统的设计与实现
java·spring boot·后端
软萌萌的113 小时前
Java Spring Boot 修改yml配置&加载顺序规则
java·spring boot·python
LSL666_1 天前
SpringBoot静态资源映射
java·spring boot·spring
LSL666_1 天前
10 Springboot的热部署
java·spring boot·后端