bigywater.tech
Spring · 73

SpringApplication.run 的启动阶段与启动事件

应用启动时到底按什么顺序触发了哪些事件?@Component 里写的监听器为什么收不到 ApplicationStartingEvent?runner 在 Ready 之前还是之后跑?慢的 runner 会不会让端口一直关着?这篇在 Spring Boot 4.0.8 上把每个阶段都打了点。

10 / 6
addListeners 与 spring.factories 的监听器收到 10 个事件,@Component 的监听器只收到 6 个
5 → 6 → 56
ContextInitialized、Prepared、ContextRefreshed 三个时间点的 Bean 定义数
870 ms vs 2388 ms
外部客户端第一次连上端口,与 1500 ms 的慢 runner 结束,相差约 1500 ms

事件的顺序

实测 · 本机执行输出
  +   0 ms  [main] main() 开始
  + 201 ms  [A] ApplicationStartingEvent
  + 201 ms  [R] starting
  + 319 ms  [A] ApplicationEnvironmentPreparedEvent  (此时 app.v=from-file)
  + 319 ms  [R] environmentPrepared  (此时 app.v=from-file)
  + 351 ms  [A] ApplicationContextInitializedEvent  (Bean 定义 5 个, isActive=false)
  + 351 ms  [R] contextPrepared  (Bean 定义 5 个, isActive=false)
  + 374 ms  [A] ApplicationPreparedEvent  (Bean 定义 6 个, isActive=false)
  + 374 ms  [R] contextLoaded  (Bean 定义 6 个, isActive=false)
  + 523 ms  [bean] 某个 Bean 的 @PostConstruct
  + 544 ms  [life] SmartLifecycle.start()
  + 545 ms  [A] ContextRefreshedEvent  (Bean 定义 56 个, isActive=true)
  + 545 ms  [A] ApplicationStartedEvent  (Bean 定义 56 个, isActive=true)
  + 546 ms  [A] AvailabilityChangeEvent  (LivenessState.CORRECT)
  + 546 ms  [R] started
  + 547 ms  [run] R1 CommandLineRunner @Order(1)  (args=[--foo=bar, --flag, baz])
  + 551 ms  [run] R2 ApplicationRunner @Order(2)  (options=[flag, foo] nonOption=[baz] foo=[bar] flag=[])
  + 551 ms  [run] R3 CommandLineRunner(没有 @Order)
  + 552 ms  [A] ApplicationReadyEvent  (Bean 定义 56 个, isActive=true)
  + 552 ms  [A] AvailabilityChangeEvent  (ReadinessState.ACCEPTING_TRAFFIC)
  + 552 ms  [R] ready
  + 552 ms  [main] app.run() 返回
  + 553 ms  [A] ContextClosedEvent
  退出码 0

Starting → EnvironmentPrepared → ContextInitialized → Prepared → refresh → ContextRefreshed → Started → runners → Ready;配置文件在 EnvironmentPrepared 之前加载好。

谁能收到哪些事件

实测 · 本机执行输出
  [A] app.addListeners(...):共 10 个——ApplicationStarting、ApplicationEnvironmentPrepared、ApplicationContextInitialized、ApplicationPrepared、ContextRefreshed、ApplicationStarted、AvailabilityChange、ApplicationReady、AvailabilityChange、ContextClosed
  [B] @Component:共 6 个——ContextRefreshed、ApplicationStarted、AvailabilityChange、ApplicationReady、AvailabilityChange、ContextClosed
  [C] spring.factories 的 ApplicationListener:共 10 个——ApplicationStarting、ApplicationEnvironmentPrepared、ApplicationContextInitialized、ApplicationPrepared、ContextRefreshed、ApplicationStarted、AvailabilityChange、ApplicationReady、AvailabilityChange、ContextClosed
  [R] spring.factories 的 SpringApplicationRunListener:共 6 个——starting、environmentPrepared、contextPrepared、contextLoaded、started、ready

addListeners 与 spring.factories 能收到全部;@Component 的监听器只收到 refresh 之后的 6 个。

失败与退出码

实测 · 本机执行输出
  -- Bean 的 @PostConstruct 抛异常:
  +   0 ms  [main] main() 开始
  + 171 ms  [A] ApplicationStartingEvent
  + 171 ms  [R] starting
  + 261 ms  [A] ApplicationEnvironmentPreparedEvent  (此时 app.v=null)
  + 261 ms  [R] environmentPrepared  (此时 app.v=null)
  + 292 ms  [A] ApplicationContextInitializedEvent  (Bean 定义 5 个, isActive=false)
  + 292 ms  [R] contextPrepared  (Bean 定义 5 个, isActive=false)
  + 313 ms  [A] ApplicationPreparedEvent  (Bean 定义 6 个, isActive=false)
  + 313 ms  [R] contextLoaded  (Bean 定义 6 个, isActive=false)
  + 454 ms  [bean] 某个 Bean 的 @PostConstruct
  + 457 ms  [A] ApplicationFailedEvent  (BeanCreationException: Error creating bean with name 'bad': Invocation of init method failed)
  + 457 ms  [R] failed  (BeanCreationException)
  退出码 1;堆栈里的异常:org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bad': Invocation of init method failed
  -- 最后一个 runner 抛异常:
  +   0 ms  [main] main() 开始
  + 186 ms  [A] ApplicationStartingEvent
  + 186 ms  [R] starting
  + 276 ms  [A] ApplicationEnvironmentPreparedEvent  (此时 app.v=null)
  + 276 ms  [R] environmentPrepared  (此时 app.v=null)
  + 308 ms  [A] ApplicationContextInitializedEvent  (Bean 定义 5 个, isActive=false)
  + 308 ms  [R] contextPrepared  (Bean 定义 5 个, isActive=false)
  + 331 ms  [A] ApplicationPreparedEvent  (Bean 定义 6 个, isActive=false)
  + 332 ms  [R] contextLoaded  (Bean 定义 6 个, isActive=false)
  + 471 ms  [bean] 某个 Bean 的 @PostConstruct
  + 494 ms  [A] ContextRefreshedEvent  (Bean 定义 56 个, isActive=true)
  + 494 ms  [A] ApplicationStartedEvent  (Bean 定义 56 个, isActive=true)
  + 495 ms  [A] AvailabilityChangeEvent  (LivenessState.CORRECT)
  + 495 ms  [R] started
  + 496 ms  [run] R1 CommandLineRunner @Order(1)  (args=[])
  + 496 ms  [run] R2 ApplicationRunner @Order(2)  (options=[] nonOption=[] foo=null flag=null)
  + 496 ms  [run] R3 CommandLineRunner(没有 @Order)
  + 497 ms  [A] ApplicationFailedEvent  (IllegalStateException: boom in runner)
  + 497 ms  [R] failed  (IllegalStateException)
  + 504 ms  [A] ContextClosedEvent
  退出码 1;堆栈里的异常:java.lang.IllegalStateException: boom in runner
  -- 用 SpringApplication.exit(ctx, () -> 42) 主动退出:
  +   0 ms  [main] main() 开始
  + 177 ms  [A] ApplicationStartingEvent
  + 266 ms  [A] ApplicationEnvironmentPreparedEvent  (此时 app.v=null)
  + 303 ms  [A] ApplicationContextInitializedEvent  (Bean 定义 5 个, isActive=false)
  + 331 ms  [A] ApplicationPreparedEvent  (Bean 定义 6 个, isActive=false)
  + 534 ms  [A] ContextRefreshedEvent  (Bean 定义 56 个, isActive=true)
  + 534 ms  [A] ApplicationStartedEvent  (Bean 定义 56 个, isActive=true)
  + 535 ms  [A] AvailabilityChangeEvent  (LivenessState.CORRECT)
  + 537 ms  [A] ApplicationReadyEvent  (Bean 定义 56 个, isActive=true)
  + 537 ms  [A] AvailabilityChangeEvent  (ReadinessState.ACCEPTING_TRAFFIC)
  + 537 ms  [main] app.run() 返回
  + 538 ms  [A] ExitCodeEvent  (exitCode=42)
  + 538 ms  [A] ContextClosedEvent
  + 539 ms  [main] SpringApplication.exit 返回  (code=42)
  退出码 42

Bean 初始化失败与 runner 抛异常都是退出码 1,前者没有 ContextClosed,后者没有 Ready;SpringApplication.exit 可以自定义退出码。

端口与 readiness

实测 · 本机执行输出
  + 190 ms  [A] ApplicationStartingEvent
  + 289 ms  [A] ApplicationEnvironmentPreparedEvent  (此时 app.v=null)
  + 327 ms  [A] ApplicationContextInitializedEvent  (Bean 定义 5 个, isActive=false)
  + 350 ms  [A] ApplicationPreparedEvent  (Bean 定义 6 个, isActive=false)
  + 758 ms  [bean] 某个 Bean 的 @PostConstruct
  + 870 ms  【外部客户端】第一次连上端口
  + 878 ms  [A] ServletWebServerInitializedEvent
  + 878 ms  [life] SmartLifecycle.start()
  + 879 ms  [A] ContextRefreshedEvent  (Bean 定义 150 个, isActive=true)
  + 880 ms  [A] ApplicationStartedEvent  (Bean 定义 150 个, isActive=true)
  + 881 ms  [A] AvailabilityChangeEvent  (LivenessState.CORRECT)
  + 882 ms  [run] R1 CommandLineRunner @Order(1)  (args=[])
  + 882 ms  [run] R2 ApplicationRunner @Order(2)  (options=[] nonOption=[] foo=null flag=null)
  + 882 ms  [run] R3 CommandLineRunner(没有 @Order)
  + 914 ms  【外部客户端】GET /state 收到响应:HTTP 200 liveness=CORRECT readiness=REFUSING_TRAFFIC
  +2388 ms  [run] R3 慢 runner 结束  (睡了 1500 ms)
  +2389 ms  [A] ApplicationReadyEvent  (Bean 定义 150 个, isActive=true)
  +2389 ms  [A] AvailabilityChangeEvent  (ReadinessState.ACCEPTING_TRAFFIC)
  runner 结束之后再请求一次:HTTP 200 liveness=CORRECT readiness=ACCEPTING_TRAFFIC

内嵌容器的端口在 runner 之前就开放;runner 期间 readiness 是 REFUSING_TRAFFIC,Ready 事件之后才变 ACCEPTING_TRAFFIC。

分步演示

启动阶段与事件
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用 Spring Boot 4.0.8 的单个程序(第一至三节用 spring-boot-starter,非 Web 模式;第四节用 spring-boot-starter-webmvc,内嵌 Tomcat),每个场景一个独立 JVM;时间是从 main() 第一行起算的毫秒数,单次运行,每次会有抖动;Bean 定义数取自 getBeanDefinitionCount();第四节的外部客户端是另一个 Python 进程,用 5 ms 间隔轮询端口,所以「第一次连上」的时间有几毫秒的误差
    • 没有验证:测试里的 SpringBootTest 启动顺序;虚拟线程与 @Async 对启动事件的影响;Actuator 的 /health/readiness 读的是不是这里的 ApplicationAvailability(本篇没有引入 Actuator);Kubernetes 里 TCP 探针与 readiness 探针的实际表现;有 BootstrapRegistry、DefaultBootstrapContext 的场景(Spring Cloud 配置中心早期阶段);GraalVM 原生镜像下的启动顺序
    京ICP备2026067502号-1京公网安备11010502063487号