SpringNotes

第 07 章:Spring Boot 启动流程

zjc 于 2026-01-07 发布

这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 SpringApplication.run 看似只有一行,实际完成了环境准备、上下文创建、Bean 加载、内嵌服务器启动和生命周期回调。掌握这条链路,可以定位启动失败、启动慢、配置未生效、端口冲突、优雅停机等常见问题。

7.1 启动入口

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

也可以显式定制:

SpringApplication app = new SpringApplication(OrderApplication.class);
app.setApplicationType(ApplicationType.SERVLET);
app.setBannerMode(Banner.Mode.OFF);
app.setWebApplicationType(WebApplicationType.SERVLET);
app.addListeners(new StartupListener());
app.run(args);

7.2 SpringApplication 构造阶段

构造 SpringApplication 时会推断:

  1. 应用类型:Servlet、Reactive、普通应用;
  2. 初始化器;
  3. 监听器;
  4. 主类。

判断依据来自 classpath:

存在 Servlet 类 -> SERVLET
存在 Reactive 相关类且不存在 Servlet -> REACTIVE
都不存在 -> NONE

因此引入或移除依赖可能改变上下文类型,进而影响自动装配结果。

7.3 run 主线

简化流程:

1. StopWatch 计时
2. 获取 SpringApplicationRunListeners
3. prepareEnvironment
4. configureIgnoreBeanInfo
5. printBanner
6. createApplicationContext
7. prepareContext
8. refreshContext
9. afterRefresh
10. listeners.started
11. callRunners
12. listeners.ready

关键扩展点:

阶段 扩展
环境准备后 ApplicationEnvironmentPreparedEvent
上下文准备 ApplicationContextInitializer
上下文加载 ApplicationPreparedEvent
刷新完成 ApplicationStartedEvent
Runner 前 ApplicationRunner / CommandLineRunner
完全就绪 ApplicationReadyEvent

7.4 环境准备

ApplicationArguments
  -> ConfigurableEnvironment
  -> configureEnvironment
  -> 配置 conversion service
  -> 绑定 spring.main
  -> EnvironmentPostProcessor

常见配置源会进入 Environment:

  1. 命令行属性;
  2. 系统属性;
  3. 环境变量;
  4. application.yml;
  5. profile 文件; 6.配置中心扩展的 PropertySource。

Spring Boot 的配置文件加载由 ConfigDataEnvironmentPostProcessor 主导。自定义 EnvironmentPostProcessor 可以补充属性源:

public class SecurePropertyEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
                                       SpringApplication application) {
        Map<String, Object> props = Map.of("app.secret-source", "vault");
        environment.getPropertySources().addFirst(
                new MapPropertySource("secureProperties", props));
    }
}

注册文件:

src/main/resources/META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.SecurePropertyEnvironmentPostProcessor

Spring Boot 3 中部分机制从 spring.factories 迁移到不同文件,需按当前版本文档注册。

7.5 上下文创建与刷新

Servlet 应用通常创建:

AnnotationConfigServletWebServerApplicationContext

refreshContext 会执行 Spring 容器 refresh,并在 onRefresh 阶段创建 WebServer。

ServletWebServerApplicationContext
  -> onRefresh
  -> createWebServer
  -> TomcatServletWebServerFactory
  -> getTomcatWebServer

容器启动时机:

阶段 状态
createWebServer 服务器对象创建
finishRefresh WebServer 启动
ApplicationReadyEvent 应用完全就绪

端口绑定失败通常发生在服务器启动阶段。

7.6 应用事件监听

@Component
public class StartupEventListener {

    @EventListener
    public void onReady(ApplicationReadyEvent event) {
        System.out.println("application ready");
    }
}

注意:@Component 监听器通常在 Bean 初始化后才能接收后续事件。如果要在环境准备、上下文创建前监听,需要通过 SpringApplication.addListeners 或注册文件添加全局 Listener。

事件顺序:

ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent

关闭时:

ContextClosedEvent

7.7 Runner

@Component
public class CacheWarmUpRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) {
        warmUp();
    }
}

执行时机在 ApplicationStartedEvent 之后、ApplicationReadyEvent 之前。因此 Runner 阻塞会推迟就绪时间。

建议:

  1. 只做必要初始化;
  2. 慢速预热异步执行;
  3. 外部调用设置超时;
  4. 记录启动阶段耗时;
  5. 失败策略明确:阻止启动还是降级运行。

7.8 启动耗时分析

开启启动诊断:

logging.level.org.springframework.boot.context.config=DEBUG

Java Flight Recorder:

java -XX:StartFlightRecording=filename=startup.jfr,settings=profile \
  -jar app.jar

常见慢点:

慢点 现象
类扫描过宽 主类放在根包外或扫描 com.*
Bean 过多 大量自动装配和第三方客户端
初始化远程调用 Runner 中同步加载配置
数据库连接 初始化连接池或执行迁移
CPU limit 低 JIT 和类加载慢
磁盘慢 日志、证书、本地文件读取
依赖冲突 反复解析和失败重试

Spring Boot 也可以输出 Startup Endpoint:

/actuator/startup

该端点依赖 BufferingApplicationEventPublisher,并需要安全保护。

7.9 常见启动失败

异常 方向
Port already in use 端口冲突
UnsatisfiedDependencyException Bean 缺失或循环依赖
InvalidConfigurationPropertyException 配置绑定错误
Cannot determine embedded database driver 缺数据库依赖或 URL
ClassCastException 依赖版本冲突
NoSuchMethodError 编译期与运行期版本不一致
FileNotFoundException 外部配置或证书路径错误
Unable to load ApplicationContext 通常看 caused by

排查原则:

读完整 caused by
  -> 确认版本矩阵
  -> 查看自动装配报告
  -> 最小化复现工程

开启自动装配报告:

java -jar app.jar --debug

7.10 嵌入式服务器选择

常见服务器:

服务器 特点
Tomcat Spring Boot 默认,生态成熟
Jetty 轻量,传统支持广泛
Undertow 常用于追求低内存和高吞吐场景

排除 Tomcat 并引入 Jetty:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

选择应基于压测,而不是传闻。

本章小结

Spring Boot 启动流程由 SpringApplication 引导,完成应用类型推断、环境准备、上下文创建、容器刷新、内嵌服务器启动和 Runner 执行。ApplicationReadyEvent 才是完全就绪信号。排查启动问题要抓住完整异常链、事件阶段和自动装配报告,优化启动时要区分必要初始化和可异步预热。

思考题

  1. ApplicationStartedEventApplicationReadyEvent 有什么区别?
  2. 为什么 Runner 中不宜执行耗时预热?
  3. 内嵌服务器是在 refresh 的哪个阶段创建的?
  4. 如何分析 Spring Boot 启动慢?
  5. 引入依赖为什么可能改变应用类型?