观察者模式
观察者模式是一种行为设计模式,它定义了对象之间的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。在这个模式中,改变状态的对象被称为主题,依赖的对象被称为观察者。
举个实际的例子:
- 事件源(Event
Source):可以视为“主题(
Subject
)”,当其状态发生变化时(比如播放新的内容),会通知所有的观察者。想象我们正在听广播,广播电台就是一个事件源,它提供了大量的新闻、音乐和其他内容。 - 事件(Event):这是主题状态改变的具体表示,对应到广播例子中,就是新闻、音乐和其他内容。每当电台播放新的内容时,就相当于一个新的事件被发布了。
- 广播器(Event Publisher / Multicaster):广播器起到的是中介的作用,它将事件从事件源传递到监听器。在这个例子中,广播塔就充当了这个角色,它将电台的节目的无线电信号发送到空气中,以便无线电接收器(监听器)可以接收。
- 监听器(Listener):监听器就是“观察者”,它们监听并响应特定的事件。在例子中,无线电接收器就是监听器,它接收广播塔发出的信号,然后播放电台的内容。
在Spring
中,事件模型的工作方式也是类似的:
- 当
Spring
应用程序中发生某个行为时(比如一个用户完成了注册),那么产生这个行为的组件(比如用户服务)就会创建一个事件,并将它发布出去。 - 事件一旦被发布,
Spring
的ApplicationContext
就会作为广播器,把这个事件发送给所有注册的监听器。 - 各个监听器接收到事件后,就会根据事件的类型和内容,进行相应的处理(比如发送欢迎邮件,赠送新用户优惠券等)。
这就是Spring
事件模型的工作原理,它实现了事件源、广播器和监听器之间的解耦,使得事件的生产者和消费者可以独立地进行开发和修改,增强了程序的灵活性和可维护性。
生命周期监听
监听 Spring Boot 的生命周期的事件,让开发者能在应用启动不同阶段(如环境准备、容器初始化、Bean 加载等)插入自定义逻辑,实现对启动流程的精细控制。
自定义SpringApplicationRunListener
来监听事件
SpringApplicationRunListener
是Spring Boot
定义的应用启动生命周期监听器接口,作用是
“感知应用启动的关键阶段,触发自定义逻辑”
,比如在环境准备好时打印启动参数、在 Bean 加载完成后做初始化校验等
实现与配置自定义监听器的流程如下
编写实现类:
自定义一个类,实现
SpringApplicationRunListener
接口,重写接口中定义的生命周期方法(如starting
、environmentPrepared
等 ),在方法里写你想执行的逻辑(比如日志打印、参数校验 )。配置
spring.factories
: 在项目META-INF
目录下创建(或已有)spring.factories
文件,添加配置,还可以指定⼀个有参构造器,接 受两个参数(SpringApplication application, String[] args)
1
2org.springframework.boot.SpringApplicationRunListener=\
你的包名.MyRunListener这样,Spring Boot 启动时会通过 SPI 机制(
spring.factories
是 SPI 配置方式 ),找到并加载你的监听器。
而 Spring Boot 在 spring-boot.jar
中配置了默认的
Listener,如下

Spring Boot 自身在 spring-boot.jar
的
META-INF/spring.factories
中,默认配置了
EventPublishingRunListener
,它的核心作用是
“事件转发”,在上述生命周期阶段,它会把
SpringApplicationRunListener
的事件,转换为 Spring 标准的
ApplicationEvent
(如
ApplicationStartingEvent
、ApplicationEnvironmentPreparedEvent
等 ),然后发布到 Spring 事件机制中。这样,其他通过
@EventListener
注解监听这些事件的组件,就能感知启动流程。
我们来写一个实例,来看看实现SpringApplicationRunListener
手写监听器都需要实现什么方法
1 | public class MyAppListener implements SpringApplicationRunListener { |
可以发现,一个空的SpringApplicationRunListener
的实现类有如上这些方法需要被实现,这也对应着其
Spring Boot 应用的生命周期
Spring Boot 启动过程会触发 SpringApplicationRunListener
的多个方法,对应不同阶段,可理解为
“应用启动的时间轴”:
阶段方法 | 触发时机 & 作用 | 关键说明 |
---|---|---|
starting |
SpringApplication.run()
调用后立即执行,此时应用刚启动,BootstrapContext 可用 |
可做最早期初始化(如标记启动开始时间 ) |
environmentPrepared |
环境准备完成(启动参数、系统变量等绑定到 Environment
),但 IoC 容器 还未创建 |
可修改环境变量(如动态设置配置参数 ) |
contextPrepared |
IoC 容器 创建并准备好,但主配置类(sources
)未加载、组件未创建 |
可对容器做预配置(如注册自定义 Bean 后处理器 ) |
contextLoaded |
IoC 容器 加载主配置类,但容器未刷新(Bean
未实例化、依赖未注入 ) |
可在此阶段加载额外配置类 |
started |
IoC 容器 刷新完成(所有 Bean 已创建、依赖注入完成
),但 ApplicationRunner /CommandLineRunner
未执行 |
可做 Bean 初始化后的校验、日志统计 |
ready |
ApplicationRunner /CommandLineRunner
执行完毕,应用完全就绪 |
可标记启动完成,对外暴露服务就绪状态 |
failed |
启动过程中发生异常时触发 | 可做异常兜底(如记录详细错误、发送告警 ) |
在META-INF中配好了我们自己实现的SpringApplicationRunListener
监听器后,来看看启动后的关键流程变化


项目启动的生命周期流程分析
所以整个启动流程就分为如下三步
引导
关键动作围绕
BootstrapContext
展开,利用它引导上下文启动,它是一个轻量级上下文,存放启动初期必要的基础配置、监听器等,支撑应用启动流程 “开个头”。首先就是这个 staring 方法,
SpringApplicationRunListener
接口中的源码如下1
2default void starting(ConfigurableBootstrapContext bootstrapContext) {
}之后,我们返回到我们的启动类中,可以发现
1
2
3
4
5
6
7
8
9public ConfigurableApplicationContext run(String... args) {
Startup startup = SpringApplication.Startup.create();
if (this.properties.isRegisterShutdownHook()) {
shutdownHook.enableShutdownHookAddition(); // 注册关闭钩子,用于优雅停机
}
// 关键:创建引导上下文 BootstrapContext
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
// ... 后续流程
}从
SpringApplication.run()
调用开始,引导阶段的核心步骤可拆解为:当你在启动类调用
SpringApplication.run(xxx.class, args)
run方法时候,Spring Boot 会进入启动流程,先初始化一些基础对象(如Startup
记录启动指标、shutdownHook
注册关闭钩子 ),然后创建BootstrapContext
然后触发
starting
方法,SpringApplicationRunListener
介入,创建完BootstrapContext
后,Spring Boot 会主动调用所有SpringApplicationRunListener
的starting
方法 。1
SpringApplicationRunListeners listeners = this.getRunListeners(args);
以你自定义的监听器为例,Linstener先要从
META-INF / spring.factories
中读到实现类,用构造器(SpringApplication application, String[] args
)实例化后,执行starting
逻辑1
listeners.starting(bootstrapContext, this.mainApplicationClass);
此时,只要有了
BootstrapContext
,starting
方法里的逻辑就会执行(比如打印 “应用开始启动” 日志、记录启动时间戳 ),这是应用启动生命周期的第一个可扩展点,能在最早期插入自定义逻辑。之后就是进行环境准备,把环境准备好,启动参数什么的进行绑定,但是此时,ioc容器还没有被创建
1
2
3ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = this.printBanner(environment);
BootstrapContext
虽然 “生命周期短”(主要在引导阶段活跃 ),但承担两个关键职责:- 存放基础配置:启动过程中需要的一些基础 Bean、配置参数,会临时存在这里,供后续流程快速获取。
- 支撑监听器交互:
starting
等方法的参数是BootstrapContext
,监听器可通过它读取 / 修改启动初期的基础配置(比如动态设置环境变量 ),影响后续启动流程。 - 引导阶段结束后,
BootstrapContext
并不会直接销毁,而是会传递给后续流程(比如environmentPrepared
阶段,监听器仍能拿到它 ),直到IoC 容器
(如AnnotationConfigApplicationContext
)创建并准备好,引导上下文的使命才会逐步收尾,应用进入 “启动阶段”(如contextPrepared
、contextLoaded
等流程 )。
启动
启动阶段” 是 Spring Boot 真正构建
IoC 容器
、加载配置、初始化 Bean 的过程,对应SpringApplicationRunListener
的environmentPrepared
、contextPrepared
、contextLicooaded
、started
等方法。可以理解为:引导阶段搭好 “启动框架”,启动阶段负责 “填充内容、构建ioc容器”。引导创建启动上下文之后,Spring Boot 会进入环境准备完成阶段,触发
SpringApplicationRunListener
的environmentPrepared
方法。1
2default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
}此时,从引导阶段传递下来的
BootstrapContext
对象就会帮助其完成Environment
的准备,在这里,我们可以修改 / 校验环境配置,比如动态设置配置参数、检查必要配置是否存在。1
2
3// - 创建并配置 Environment(包含系统属性、配置文件、命令行参数等)
// - 触发 listeners.environmentPrepared() 事件
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);这一步,本质上是引导的延续,也可以算是引导的一环
接下来,就来到
contextPrepared
这一步,此时,IoC 容器
(如AnnotationConfigApplicationContext
)已创建并 “准备好”,但主配置类(sources
,即@SpringBootApplication
标注的类 )还未加载,容器内也没有任何 Bean。在这里,框架会创建
IoC 容器
并触发此阶段,此时可以发现,容器是空的,但你可以通过context
做预配置1
2
3
4// 4. 创建应用上下文(IoC 容器)
// 根据应用类型(Web/非 Web)创建对应的 ApplicationContext
context = this.createApplicationContext();
// 这里只是准备好了 IOC 容器之后,就来到
contextLoaded
这步了,加载主配置类,但 Bean 未实例化,此时IoC 容器
已加载主配置类(@SpringBootApplication
类 ),解析其@Bean
、@ComponentScan
等注解,但容器未刷新(Bean 未实例化、依赖未注入 )里面没有组件,bean此时还没有创建。在这里,就是允许你加载额外的@Configuration
类的时候了继续追随 run 方法,可以看到在
createApplicationContext()
之后,有1
2// 这一步,就是准备应用上下文,配置上下文环境、加载主配置类,触发 listeners.contextPrepared() 和 listeners.contextLoaded() 事件
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);追随
prepareContext
方法,继续阅读1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 设置之前准备好的 Environment 到应用上下文
context.setEnvironment(environment);
// 应用上下文后置处理(模板方法,子类可重写)
// 例如:ServletWebServerApplicationContext 会在此设置 Servlet 相关配置
this.postProcessApplicationContext(context);
// 添加 AOT(提前编译)生成的初始化器(如果适用)
this.addAotGeneratedInitializerIfNecessary(this.initializers);
// 应用所有 ApplicationContextInitializer
// 允许第三方组件在容器刷新前自定义上下文(如注册属性源、添加 Bean 定义)
this.applyInitializers(context);
// 触发 contextPrepared 事件
// 此时容器已创建但尚未加载任何配置
listeners.contextPrepared(context);
// 关闭 BootstrapContext(引导上下文)
// 标志引导阶段结束,后续使用正式的应用上下文
bootstrapContext.close(context);这也就是为什么,
contextLoaded
事件是 Spring Boot 启动流程中的配置扩展点接下来,就是对应
started
这步,此时IoC 容器
已刷新完成(所有 Bean 已实例化、依赖注入完成、@PostConstruct
等初始化方法已执行 ),但ApplicationRunner
/CommandLineRunner
还未执行。继续追随 run 方法,可以发现ioc容器刷新了,bean都造好了,这里涉及到容器刷新12大步,我之前也讲过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 6. 刷新应用上下文(核心启动逻辑)
// - 初始化 BeanFactory、加载所有 Bean 定义
// - 实例化单例 Bean、处理依赖注入
// - 触发各种 Bean 生命周期回调(@PostConstruct、InitializingBean 等)
this.refreshContext(context);
// 7. 刷新后的处理(模板方法)
// 子类可重写此方法,在容器刷新后执行自定义逻辑
this.afterRefresh(context, applicationArguments);
// 8. 记录启动完成时间
startup.started();
// 打印启动信息(如果启用)
if (this.properties.isLogStartupInfo()) {
(new StartupInfoLogger(this.mainApplicationClass, environment)).logStarted(this.getApplicationLog(), startup);
}
// 9. 触发容器启动完成事件
// 调用 listeners.started(),通知所有监听器容器已启动
listeners.started(context, startup.timeTakenToStarted());最后就是启动的终点,触发
SpringApplicationRunListener
的ready
方法,执行ApplicationRunner
/CommandLineRunner
逻辑。这一步标志着:应用已完全就绪,可对外提供服务。来到我们run 方法主要流程try块中的最后一行,我们会发现
1
2
3
4// 10. 执行应用运行器和命令行运行器
// 调用所有实现 ApplicationRunner 或 CommandLineRunner 的 Bean
// 这是应用完全启动前的最后一步,常用于执行初始化任务
this.callRunners(context, applicationArguments);这里面会调用
callRunners
方法,执行ApplicationRunner
/CommandLineRunner
逻辑,源码如下在
callRunners
执行完毕后,Spring Boot 会触发SpringApplicationRunListener
的ready
方法,这标志着- 所有 Bean 已初始化
- 所有
ApplicationRunner
/CommandLineRunner
已执行 - 应用可以正式对外提供服务
1
2
3
4
5
6
7
8
9
10
11
12private void callRunners(ConfigurableApplicationContext context, ApplicationArguments args) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] beanNames = beanFactory.getBeanNamesForType(Runner.class);
Map<Runner, String> instancesToBeanNames = new IdentityHashMap();
for(String beanName : beanNames) {
instancesToBeanNames.put((Runner)beanFactory.getBean(beanName, Runner.class), beanName);
}
Comparator<Object> comparator = this.getOrderComparator(beanFactory).withSourceProvider(new FactoryAwareOrderSourceProvider(beanFactory, instancesToBeanNames));
instancesToBeanNames.keySet().stream().sorted(comparator).forEach((runner) -> this.callRunner(runner, args));
}下面的catch块,就是在 ready 返回的 context 中判断出现了什么样的异常,进行进一步的处理
运行
之后,我们发现,就剩下一个方法没有被处理过,那就是在运行过程中发生异常(如 Bean 初始化失败、Runner 执行异常),Spring Boot 会触发
failed
事件这里就是用到了 run 方法中那一大堆的 catch 块
1
2
3
4
5
6
7
8try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
return context;
} catch (Throwable ex) {
throw this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null);
}而其中会有一个
handleRunFailure
专门处理failed
事件异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33private RuntimeException handleRunFailure(ConfigurableApplicationContext context, Throwable exception, SpringApplicationRunListeners listeners) {
if (exception instanceof AbandonedRunException abandonedRunException) {
return abandonedRunException;
} else {
try {
try {
this.handleExitCode(context, exception);
// 触发 failed 事件
if (listeners != null) {
listeners.failed(context, exception);
}
} finally {
this.reportFailure(this.getExceptionReporters(context), exception);
if (context != null) {
context.close();
shutdownHook.deregisterFailedApplicationContext(context);
}
}
} catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
Object var10000;
if (exception instanceof RuntimeException runtimeException) {
var10000 = runtimeException;
} else {
var10000 = new IllegalStateException(exception);
}
return (RuntimeException)var10000;
}
}
总结下来,整个流程就行如下图

事件触发的时机
各种回调监听器
Spring Boot 监听器(Listeners)基于 Spring Framework
的事件机制(ApplicationEvent
和ApplicationListener
),用于在应用生命周期或自定义事件触发时执行特定逻辑。它们提供了一种松耦合的方式响应应用状态变化,常用于初始化资源、监控应用状态、执行异步任务等。
而 Spring Boot 中存在很多事件回调监听器,这些监听器都是 Spring Boot 为了让开发者 “感知应用启动流程、插入自定义逻辑” 设计的扩展点,覆盖从 “最早期引导上下文” 到 “应用完全就绪” 的全生命周期,可按需选择对应监听器实现精细化控制。
简单说,这些监听器就像 Spring Boot 启动流程里的 “钩子”,从最早期的引导上下文,到容器初始化、事件驱动,再到应用就绪,不同阶段都有对应的扩展点。根据需求选对应的 “钩子”,就能精准在 Spring Boot 启动的各个环节插入自定义逻辑,实现如安全校验、配置修改、初始化加载等功能
BootstrapRegistryInitializer
感知阶段:引导上下文(
BootstrapContext
)初始化阶段触发时机:创建
BootstrapContext
时触发(启动最早期,比IoC
容器创建还早 ),在进入 run 方法的时候就被创建在run方法中有
1
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
可以发现,run方法进入以后,首先就调用了
BootstrapRegistryInitializer
中的initialize
方法使用方式
配置:在
META-INF/spring.factories
里加org.springframework.boot.BootstrapRegistryInitializer=你的实现类
1
2
3org.springframework.boot.SpringApplicationRunListener=com.your.listener.MyApplicationListener
org.springframework.boot.BootstrapRegistryInitializer=com.your.listener.MyBootstrapRegistryInitializer代码:
application.addBootstrapRegistryInitializer(...)
在启动主类中动态添加
典型场景:最早期的 “底层初始化”,比如密钥校验、授权初始化(需要在容器启动前完成的安全逻辑 )
ApplicationContextInitializer
感知阶段:
IoC
容器(ApplicationContext
)初始化阶段(容器创建后,刷新前 )触发时机:
IoC
容器准备好,但还没加载 Bean 定义、实例化 Bean 时触发,经过上文的分析,我们知道,ApplicationContextInitializer
执行是在contextPrepared
(ioc容器准备完成)之前,environmentPrepared
(环境准备完成)之后run 方法中,之后不断进入,就能知道其中什么时候调用了这个方法
1
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
使用方式
- 配置:
META-INF/spring.factories
里加org.springframework.context.ApplicationContextInitializer=你的实现类
- 代码:
application.addInitializers(...)
动态添加
- 配置:
典型场景:对
IoC
容器做预配置,比如修改环境变量、注册自定义 Bean 后处理器(影响容器后续加载流程 )
ApplicationListener
感知阶段:基于 Spring 事件机制,感知全流程事件(从应用启动到关闭的各种事件 )这也是为什么,我们要是重写监听器,一般都会实现这个接口,它是基于事件机制感知全阶段的,是感知事件用的,不能够去进行对应操作。类似于那种 AOP 的普通通知,而下面的
SpringApplicationRunListener
类似于环绕通知触发时机:对应事件发生时触发(如
ApplicationStartingEvent
、ContextRefreshedEvent
等 )使用方式
注解:
@Bean
定义监听器 Bean,或用@EventListener
标注方法(更简洁)这里涉及到后面的事件驱动开发,我这里就简单说一下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
// 实现 ApplicationListener,指定监听的事件类型
public class MyAppReadyListener implements ApplicationListener<ApplicationReadyEvent> {
public void onApplicationEvent(ApplicationReadyEvent event) {
// event.getTimestamp() 是事件触发时间(接近应用启动完成时间)
// 可以结合启动开始时间,计算耗时(实际可通过上下文存启动开始时间)
System.out.println("应用已完全就绪!启动耗时可在此计算...");
// 也能获取容器、环境等信息
System.out.println("当前环境:" + event.getApplicationContext().getEnvironment().getActiveProfiles()[0]);
}
}在配置类(或启动类 )里,通过
@Bean
把监听器加入 Spring 容器:1
2
3
4
5
6
7
8
9
10
11import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class ListenerConfig {
public MyAppReadyListener myAppReadyListener() {
return new MyAppReadyListener();
}
}或者
无需显式实现
ApplicationListener
接口,直接在 Spring 管理的 Bean(如@Component
类 )里,用@EventListener
标注方法,指定要监听的事件类型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
// 必须是 Spring 管理的 Bean
public class MyEventListenerBean {
// 标注 @EventListener,指定监听 ApplicationReadyEvent
public void onAppReady(ApplicationReadyEvent event) {
System.out.println("通过 @EventListener 监听到应用就绪事件!");
// 同样可操作容器、环境等
System.out.println("当前激活配置:" + event.getApplicationContext().getEnvironment().getProperty("spring.profiles.active"));
}
}配置:启动类中添加
SpringApplication.addListeners(...)
或spring.factories
配置
典型场景:事件驱动的零散逻辑,比如监听应用启动完成发通知、监听 Bean 加载完成做缓存预热(灵活,可针对不同事件写逻辑 )
举个例子
1
2
3
4
5
6public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("----事件" + event + "到达-------");
}
}然后,写到 META-INF 中
1
2org.springframework.context.ApplicationListener=\
edu.software.ergoutree.springbootwebpart2.listener.MyApplicationListener
启动后发现,事件就被标注出来了

SpringApplicationRunListener
- 感知阶段:不仅能够感知应用启动全生命周期(从
starting
到ready
/failed
所有阶段 ),而且还能进行各阶段的自定义操作 - 触发时机:启动流程的每个关键节点(如环境准备、容器刷新、应用就绪等 )都会触发对应方法,这个在上面已经说的比较多了
- 使用方式:
META-INF/spring.factories
配置org.springframework.boot.SpringApplicationRunListener=你的实现类
- 典型场景:深度干预启动流程,比如在每个阶段记录日志、统计启动耗时、自定义启动失败处理(功能最全面,覆盖所有阶段 )
ApplicationRunner
& CommandLineRunner
感知阶段:感知应用就绪,是应用就绪前最后一步(所有 Bean 加载完成,准备对外提供服务时 ),也就是感知应用就绪
ready
这步,这些 Runner 的作用就是,在runner执行的前后都会发送探针事件触发时机:
IoC
容器刷新完成,ApplicationRunner
先执行(参数是ApplicationArguments
),CommandLineRunner
后执行(参数是原始命令行参数数组 )从 run 方法中的try块最后一行进入到started 方法来
进入 callRunner 方法,不断进入
使用方式:定义实现类,用
@Bean
注入 Spring 容器典型场景:应用就绪前的初始化逻辑,比如加载基础数据、注册服务到注册中心;如果逻辑里抛异常,会阻断应用就绪(让应用启动失败,适合做 “启动前最后校验” )
9大事件
经过上述示例,我们实现了ApplicationListene
接口,标注出了其中的事件,这些事件的内容如下
1 | ----事件org.springframework.boot.context.event.ApplicationStartingEvent[source=org.springframework.boot.SpringApplication@5204062d]到达------- |
ApplicationStartingEvent
- 触发时机:应用启动最开始,仅完成
listeners
(监听器 )和initializers
(初始化器 )的注册,还未加载环境、创建容器。在进行任何处理之前发送(除了监听器和初始化器注册之外)。 - 核心作用:标记 “应用启动的起点”,可在此做最早期初始化(如设置全局日志标识、初始化基础工具类 )。
ApplicationEnvironmentPreparedEvent
- 触发时机:
Environment
(环境配置,包含配置文件、系统变量、命令行参数等 )准备完成,但IoC
容器(ApplicationContext
)还未创建。 - 核心作用:允许修改 / 校验环境配置,是干预应用配置的关键节点。
- 实践场景:动态设置配置参数(如根据环境变量切换数据源地址
);校验必要配置是否存在(如
database.url
未配置则抛异常 )。
ApplicationContextInitializedEvent
- 触发时机:
IoC
(ApplicationContext
)容器创建完成,且ApplicationContextInitializers
(容器初始化器 )已调用,但没有加载任何 Bean 定义(主配置类、@Bean
等还未解析 )。此时,容器是空的 - 核心作用:对空容器做预配置,影响后续 Bean 加载流程。
- 实践场景:注册自定义
BeanFactoryPostProcessor
(修改 Bean 定义的处理器 );提前设置容器级别的属性(如资源加载器 )。
ApplicationPreparedEvent
- 触发时机:容器刷新(
refresh
)之前,Bean 定义信息已加载(主配置类、@ComponentScan
扫描的 Bean、@Bean
等已解析为 Bean 定义 ),但 Bean 未实例化。此时,容器里面没有组件,bean此时还没有创建。 - 核心作用:最后一次修改 Bean 定义的机会,可调整 Bean 的配置。
- 实践场景:根据环境动态替换 Bean 定义(如开发环境用模拟 Bean,生产环境用真实 Bean );校验 Bean 定义的合法性(如某个 Bean 必须设置特定属性 )。
ContextRefreshedEvent
- 触发时机:Spring
应用上下文(
ApplicationContext
)完成刷新时触发。具体发生在:- 容器初始化完成:所有 Bean
定义已加载、实例化、依赖注入完成,且执行了所有初始化回调(如
@PostConstruct
、InitializingBean
)。 - 容器刷新操作结束:调用
refresh()
方法(包括应用启动时自动调用,或手动刷新)的最后阶段。 - 可多次触发:对于支持热刷新的容器(如
GenericApplicationContext
),每次刷新都会触发;但 Spring Boot 默认容器通常只在启动时刷新一次。
- 容器初始化完成:所有 Bean
定义已加载、实例化、依赖注入完成,且执行了所有初始化回调(如
- 核心作用:
- 标记容器完全就绪:此时所有 Bean 已处于可用状态,可执行依赖于完整容器环境的操作。
- 扩展容器功能:注册动态组件(如事件监听器、MBean)、启动后台任务、执行最终配置校验。
ApplicationStartedEvent
- 触发时机:容器刷新完成(所有 Bean
已实例化、依赖注入完成、
@PostConstruct
等初始化方法执行完毕 ),但ApplicationRunner
/CommandLineRunner
未执行。 - 核心作用:标记 “容器已就绪,但应用还未完全对外服务”,可做 Bean 初始化后的校验。
- 实践场景:检查关键 Bean 的状态(如数据库连接 Bean 是否正常 );初始化业务资源(如缓存预热、定时任务注册 )。
AvailabilityChangeEvent
(LivenessState.CORRECT
)
- 触发时机:应用存活状态变更为
“健康”(
LivenessState.CORRECT
),一般在容器正常刷新后触发,属于存活探针。 - 核心作用:告知监控系统 “应用进程还活着,基础功能正常”。
- 实践场景:配合 Kubernetes 等平台的存活检测;自定义监控逻辑,标记应用存活状态。
ApplicationReadyEvent
- 触发时机:所有
ApplicationRunner
/CommandLineRunner
执行完毕,应用完全就绪,可对外提供服务。 - 核心作用:标记 “应用已准备好处理外部请求”,是启动流程的关键终点。
- 实践场景:发送应用就绪通知(如给运维平台、监控系统
);记录完整启动耗时(从
ApplicationStartingEvent
到此时 )。
AvailabilityChangeEvent
(ReadinessState.ACCEPTING_TRAFFIC
)
- 触发时机:应用就绪状态变更为
“可接收流量”(
ReadinessState.ACCEPTING_TRAFFIC
),在ApplicationReadyEvent
后触发,属于就绪探针。 - 核心作用:告知外部系统 “应用不仅活着,还能处理请求”。
- 实践场景:Kubernetes 等平台据此判断是否将流量转发到该实例;网关、负载均衡器感知应用状态。
ApplicationFailedEvent
九大事件有十个怎么了
- 触发时机:启动过程中发生异常(如 Bean 初始化失败、Runner 执行报错 ),导致应用启动终止。
- 核心作用:捕获启动异常,做兜底处理。
- 实践场景:记录详细错误日志(便于排查问题 );发送启动失败告警(邮件、短信通知运维 )。
总结下来就是这张图

flowchart TB
%% 定义节点样式
classDef eventStyle fill:#f9f,stroke:#333,stroke-width:1px,rx:5px,ry:5px;
classDef listenerStyle fill:#b8e994,stroke:#333,stroke-width:1px,rx:5px,ry:5px;
%% 应用事件流程
A["ApplicationStartingEvent\n应用开始启动"]:::eventStyle --> B["prepareEnvironment\n准备环境"]
B --> C["ApplicationEnvironmentPreparedEvent\n环境准备完成"]:::eventStyle
C --> D["prepareContext\n初始化容器"]
D --> E["ApplicationContextInitializedEvent\nInitializer 调用"]:::eventStyle
D --> F["ApplicationPreparedEvent\n应用准备完成"]:::eventStyle
F --> G["refreshContext 之后\n容器刷新完成"]
G --> H["ApplicationStartedEvent\nstarted 阶段"]:::eventStyle
G --> I["AvailabilityChangeEvent\n(LivenessState.CORRECT)\n可用探针"]:::eventStyle
H --> J["callRunners\n调用所有 runner"]
J --> K["ApplicationReadyEvent\n应用运行完成"]:::eventStyle
J --> L["AvailabilityChangeEvent\n(ReadinessState.ACCEPTING_TRAFFIC)\n就绪探针"]:::eventStyle
%% 异常分支
B --> M["所有异常、进行捕获"]
D --> M
F --> M
G --> M
H --> M
J --> M
M --> N["ApplicationFailedEvent\n应用失败"]:::eventStyle
%% 监听器运行流程
O["listeners.starting"]:::listenerStyle --> P["listeners.environmentPrepared"]:::listenerStyle
P --> Q["listeners.contextPrepared"]:::listenerStyle
Q --> R["listeners.contextLoaded"]:::listenerStyle
R --> S["listeners.started"]:::listenerStyle
S --> T["callRunners\n调用所有 runner"]
T --> U["listeners.ready"]:::listenerStyle
%% 异常时监听器执行
M --> V["listeners.failed"]:::listenerStyle


sequenceDiagram
title Spring Boot 启动事件时序
participant 应用 as 应用启动入口
participant 事件1 as ApplicationStartingEvent
participant 事件2 as ApplicationEnvironmentPreparedEvent
participant 事件3 as ApplicationContextInitializedEvent
participant 事件4 as ApplicationPreparedEvent
participant 事件5 as ApplicationStartedEvent
participant 事件6 as AvailabilityChangeEvent(Liveness)
participant 事件7 as ApplicationReadyEvent
participant 事件8 as AvailabilityChangeEvent(Readiness)
participant 事件9 as ApplicationFailedEvent
应用->>事件1: 启动开始,仅注册监听器/初始化器
事件1->>事件2: 环境准备完成(配置加载)
事件2->>事件3: IoC容器创建,未加载Bean
事件3->>事件4: Bean定义加载完成,未实例化
事件4->>事件5: 容器刷新完成,Bean初始化完成
事件5->>事件6: 标记应用存活
事件6->>事件7: Runner执行完毕,应用就绪
事件7->>事件8: 标记应用可接收流量
应用->>事件9: 启动异常时触发
哪个看得懂看哪个
实践总结
项目启动前做事 →
BootstrapRegistryInitializer
+ApplicationContextInitializer
核心需求:在应用启动最早期(容器创建前、Bean 加载前 )执行逻辑。
BootstrapRegistryInitializer
:触发时机:引导上下文(
BootstrapContext
)创建时(比IoC
容器还早 )。典型场景:最底层的初始化(如密钥校验、授权认证,需要在容器启动前完成 )。
代码示例
1
2
3
4
5
6
7
8
9
10
11public class MyBootstrapInitializer implements BootstrapRegistryInitializer {
public void initialize(BootstrapRegistry registry) {
// 启动前校验:比如检查 License 文件是否有效
if (!checkLicense()) {
throw new IllegalStateException("License 无效");
}
}
}
// 在 META-INF/spring.factories 配置:
// org.springframework.boot.BootstrapRegistryInitializer=com.example.MyBootstrapInitializer
ApplicationContextInitializer
:触发时机:
IoC
容器创建后,但 Bean 未加载时(可修改容器环境、预配置 )。典型场景:动态修改配置(如根据环境变量切换数据源 )、注册 Bean 后处理器。
代码示例
1
2
3
4
5
6
7
8
9
10
11public class MyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext context) {
// 修改环境变量:生产环境启用调试模式
if (context.getEnvironment().getActiveProfiles()[0].equals("prod")) {
context.getEnvironment().setActiveProfiles("prod", "debug");
}
}
}
// 在 META-INF/spring.factories 配置:
// org.springframework.context.ApplicationContextInitializer=com.example.MyContextInitializer
项目启动完成后做事 →
ApplicationRunner
+CommandLineRunner
- 核心需求:应用完全就绪(所有 Bean 加载完成、容器刷新完成 )后,执行初始化逻辑。
ApplicationRunner
:- 触发时机:应用就绪后,接收
ApplicationArguments
(封装命令行参数 )。 - 典型场景:需要解析命令行参数的初始化(如根据
--import-data
参数加载数据 )。
- 触发时机:应用就绪后,接收
CommandLineRunner
:- 触发时机:应用就绪后,接收原始命令行参数数组(更简单直接 )。
- 典型场景:无需复杂参数解析的初始化(如打印启动完成日志、注册服务 )。
干涉生命周期做事 →
SpringApplicationRunListener
- 核心需求:深度控制启动流程,在每个生命周期阶段(如环境准备、容器刷新、应用就绪 )插入逻辑。
用事件机制做事 →
ApplicationListener
- 核心需求:通过事件驱动,灵活响应特定阶段(如应用启动、Bean 加载完成 )。
需求场景 | 推荐工具 | 核心优势 |
---|---|---|
启动前最早期初始化(容器创建前 ) | BootstrapRegistryInitializer |
干预引导上下文,做底层校验(如 License ) |
启动前容器级初始化(容器创建后,Bean 加载前 ) | ApplicationContextInitializer |
修改环境、预配置容器(如动态切换数据源 ) |
启动后业务初始化(需解析命令行参数 ) | ApplicationRunner |
支持复杂参数解析(如 --import-data ) |
启动后简单初始化(无需参数解析 ) | CommandLineRunner |
代码简洁,直接执行逻辑(如注册服务 ) |
深度干预生命周期(每个阶段精细控制 ) | SpringApplicationRunListener |
覆盖全生命周期,统计耗时、自定义失败处理 |
事件驱动响应(灵活监听任意阶段 ) | ApplicationListener |
解耦逻辑,通过事件触发(如应用就绪、Bean 加载完成 ) |