SpringBoot-核心原理
SpringBoot-核心原理
事件和监听器
什么是事件和监听器
可以先用一个简单模型进行理解

这里面有三个核心角色:
| 角色 | Spring中的代表 | 作用 |
|---|---|---|
| 事件 | ApplicationEvent | 表示"某件事件发生了" |
| 事件发布者 | ApplicationEventPublisher | 发布事件 |
| 事件监听器 | ApplicationListener/@EventListener | 接收事件并执行处理逻辑 |
Spring Framework官方将这种机制描述为标准的观察者模式。所以事件本质上时一种:应用内部组件之间的通知机制。发布者不用知道"谁来处理",监听者也不用直接调用发布者。
例如:
订单服务
|
|发布
|
OrderCreatedEvent
|
|--- 库存监听器
|--- 邮件监听器
|___ 日志监听器
这样可以降低组件之间的耦合。
Spring Boot为什么还要专门提供启动事件
Spring Framework已经有事件机制了,但是Spring Boot启动时还有一个非常重要的对象:
SpringApplication
典型的Spring Boot启动:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
看起来只有这一行:SpringApplication.run(...),实际上背后要经历:
- 准备启动
- 准备Environment
- 创建ApplicationContext
- 加载BeanDefinition
- refresh ApplicationContext
- 启动 WebServer
- 执行 Runner
- 应用完全启动
Spring Boot为这些关键时间点定义了相应事件。
因此:Spring Framework提供了通用事件机制(ApplicationEvent、ApplicaitonListener、ApplicationEventPublisher)。Spring Boot利用这个机制发布SpringApplication启动事件。即Spring Boot的application events是通过Spring Framework的时间发送机制发送的。
SpringApplicationRunListener
它是SpringApplication.run方法的监听器,监听整个Spring Boot应用的启动流程,也就是SpringApplicaiton.run方法的生命周期。
核心方法
| 方法 | 所处阶段 | 关键含义 |
|---|---|---|
| starting() | 启动刚开始 | run()刚刚开始 |
| environmentPrepared() | 环境准备完成 | Environment已准备,Context尚未创建 |
| contextPrepared() | Context创建并初步准备 | Context已创建,配置源尚未加载 |
| contextLoaded() | Context内容已加载 | BeanDefinition等已加载,即将refresh |
| started() | Context refresh完成 | 应用已启动,但Runner尚未执行 |
| ready() | 应用完全就绪 | Runner已执行完 |
| failed() | 启动异常 | 启动过程中出现异常 |
完整过程(围绕着SpringApplication.run方法的生命周期)
可以把SpringApplicationRunListener理解成下面这条生命周期主线

自定义SpringApplicationRunListener来进行生命周期回调用于检查应用启动的过程
1.编写SpringApplicationRunListener实现类
/**
* SpringBoot的应用生命周期监听
* 具体源码可以参见SpringApplication该类中的run方法以及SpringApplicationRunListeners被调用的方法
*
* listener先要从META-INF/spring.factories 读取
*
* 分三个步骤:
* 1、引导:利用BootstrapContext 引导整个项目启动
* starting:应用开始,SpringApplication的run方法一调用,只要有了BootstrapContext(引导上下文)就执行
* environmentPrepared:环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建 【调用一次】
* 2、启动:
* contextPrepared: ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建 【调用一次】
* contextLoaded: ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(bean-组件还没创建)
* ==============截至目前,ico容器里面还没有构造bean组件=====================
* started: ioc容器刷新了(所有bean构建完毕),但是runner还没调用
* ready: ioc容器刷新了(所有bean构建完毕),所有runner调用完了
* 3、运行
* 以前步骤都正确执行,代表容器running。
*
* Spring Boot应用启动过程主要经历以下几个生命周期阶段:
* SpringApplication.run()
* 1.Starting: 应用开始启动
* 2.Environment Prepared: Environment准备完成
* 3.Context Prepared: ApplicationContext准备完成
* 4.Context Loaded: Bean定义加载完成
* 5.Context Refreshed: Spring容器刷新完成
* 6.Started: 应用启动完成(Runner执行前)
* 7.Ready: 应用完成就绪
* 8.Failed: 启动失败
*/
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
//ConfigurableBootstrapContext 启动/引导上下文
System.out.println("======starting======正在启动============");
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
System.out.println("======environmentPrepared======环境准备完成============");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
// ConfigurableApplicationContext 应用上下文 ioc容器
System.out.println("======contextPrepared======上下文(ioc容器)准备完成============");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("======contextLoaded======ioc容器加载完成============");
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("======started======启动完成============");
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("======ready======准备就绪============");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("======failed======应用启动失败============");
}
}
2.在META-INF/spring.factories中注册自己的监听器
src/main/resources/
└── META-INF/
└── spring.factories
org.springframework.boot.SpringApplicationRunListener=com.demo.core.listener.MySpringApplicationRunListener
除此之外Spring Boot自己也是这么注册默认实现的

也就是说EventPublishingRunListener本身就是SpringApplicationRunListener的一个实现。
因此你的实现和Spring Boot默认实现实际上是并列关系:
SpringApplicationRunListener
│
├── EventPublishingRunListener
│ Spring Boot官方实现
│
└── MySpringApplicationRunListener
自定义实现
并不是你替换了EventPublishingRunListener而是Spring Boot会同时加载它们
3.启动应用程序,检查日志打印
SpringApplication自己知道运行到哪个阶段,并主动调用对应的Listener方法
F:\environment\java\jdk-21.0.3\bin\java.exe com.demo.core.CoreApplication
======starting======正在启动============
======environmentPrepared======环境准备完成============
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.5.9)
======contextPrepared======上下文(ioc容器)准备完成============
2026-09-20T21:41:51.610+08:00 INFO 12624 --- [ main] com.demo.core.CoreApplication : Starting CoreApplication using Java 21.0.3 with PID 12624 (F:\environment\dev\git-project\springboot3-demo\boot3-09-core\target\classes started by 11720 in F:\environment\dev\git-project\springboot3-demo)
2026-09-20T21:41:51.612+08:00 INFO 12624 --- [ main] com.demo.core.CoreApplication : No active profile set, falling back to 1 default profile: "default"
======contextLoaded======ioc容器加载完成============
2026-09-20T21:41:52.292+08:00 INFO 12624 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2026-09-20T21:41:52.304+08:00 INFO 12624 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2026-09-20T21:41:52.304+08:00 INFO 12624 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.50]
2026-09-20T21:41:52.346+08:00 INFO 12624 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2026-09-20T21:41:52.347+08:00 INFO 12624 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 688 ms
2026-09-20T21:41:52.641+08:00 INFO 12624 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2026-09-20T21:41:52.646+08:00 INFO 12624 --- [ main] com.dmoe.core.CoreApplication : Started CoreApplication in 1.461 seconds (process running for 1.948)
======started======启动完成============
======ready======准备就绪============
ApplicationListener
ApplicationListener不是Spring Boot自己定义的类,而是Spring Framework提供的接口。Spring Boot在SpringApplication启动过程中大量使用Spring的事件发布机制,因此经常在Spring Boot启动的生命周期里看到它。
ApplicationListener是什么
源码层面可以简化理解为:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
核心方法就一个:void onApplicationEvent(E event);
它表达的意思非常直接:
当我关心的 ApplicationEvent 发生时
↓
Spring 通知我
↓
执行 onApplicationEvent(event)
官方文档对它的定位就是:由应用事件监听器实现的接口,监听器可以通过泛型声明自己感兴趣的事件类型,Spring会进行类型过滤,只把匹配的事件发送给这个监听器。
ApplicationListener本质上时观察者模式
Spring官方明确指出,ApplicationListener基于标准EventListener,用于实现Observer Pattern(观察者模式)
可以把整个模型理解成:
发布事件
│
▼
ApplicationEventPublisher
│
▼
ApplicationEventMulticaster
│
查找匹配监听器
┌─────────┼──────────┐
▼ ▼ ▼
Listener A Listener B Listener C
│
▼
onApplicationEvent(event)
其中三个角色很重要:
| 角色 | Spring中对应 | 作用 |
|---|---|---|
| 事件 | ApplicationEvent | 描述"发生了什么" |
| 发布者 | ApplicationEventPublisher | 发布事件 |
| 监听者 | ApplicaitonListener | 接收并处理事件 |
Spring Framwork官方文档指出:ApplicaitonContext本身就具有事件发布能力;当事件发布到ApplicationContext后,实现了ApplicationListener的Bean就会得到通知。因此它不是:
Listener
↓
主动检查系统有没有事件
而是
某个组件发布事件
↓
Spring 事件机制
↓
找到对应 ApplicationListener
↓
调用 onApplicationEvent()
Spring Boot启动过程中的九大事件
ApplicationStartingEvent: SpringBoot刚开始启动ApplicationEnvironmentPreparedEvent: Environment已准备完成ApplicationContextInitializedEvent:ApplicationContext已创建并完成初始化ApplicationPreparedEvent: Bean定义已加载,但Context尚未refreshApplicationStartedEvent: Context已refresh,但Runner尚未执行AvailabilityChangeEvent<LivenessState>: 应用进入存活状态ApplicationReadyEvent: Runner执行完成,应用启动完成AvailabilityChangeEvent<ReadinessState>: 应用进入可接收流量状态ApplicationFailedEvent: 启动失败时发布

ApplicationListener和Spring Boot启动事件的关系
Spring Boot在启动时,SpringApplication会发布一系列额外的Application Event。因此你可以针对不同阶段写不同的ApplicationListener
例如:
public class StartingListener
implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
System.out.println("Spring Boot 正准备启动");
}
}
也就是说,ApplicationListener本身不是声明周期,而是监听生命周期过程中产生的事件的一种标准机制。
为什么有些ApplicationListener不能直接写成@Component
这是Spring Boot官方文档中特别强调的一点。例如:
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
发送得非常早,这时候ApplicationContext甚至还没有创建完成。
因此这种方式:
@Component
public class StartingListener
implements ApplicationListener<ApplicationStartingEvent> {
}
存在一个逻辑问题
ApplicationStartingEvent 已经发生
↓
ApplicationContext 此时还没有创建
↓
@Component 当然还没被扫描、实例化
↓
Listener 不可能收到这个事件
Spring Boot官方因此明确说明:
一些事件发生在
ApplicationContext创建之前,因此不能把监听器简单注册成@Bean;应通过SpringApplication.addListener(...)或SpringApplicationBuilder.listeners(...)注册
例如:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication application =
new SpringApplication(Application.class);
application.addListeners(new StartingListener());
application.run(args);
}
}
这样Listener在run()之前就已经存在:
new SpringApplication()
↓
addListeners()
↓
Listener 已注册
↓
run()
↓
ApplicationStartingEvent
↓
Listener 能收到
还可以通过spring.factories 注册
Spring Boot官方还给出了另一种方式:
META-INF/spring.factories
配置:
org.springframework.context.ApplicationListener=\
com.example.project.MyListener
Spring Boot会自动加载这个Listener
这种方式加载主要在于:
普通业务代码
↓
@Component / @Bean
非常早期的启动监听
↓
SpringApplication.addListeners()
框架、Starter、公共组件
↓
spring.factories
例如某个公共组件需要在很多Spring Boot项目启动早期工作,就比较适合自动注册Listener。
ApplicationListener和@EventListener的关系
平时业务开发中你还会经常看到:
@Component
public class MyListener {
@EventListener
public void handle(ApplicationReadyEvent event) {
System.out.println("应用启动完成");
}
}
它与:
@Component
public class MyListener
implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("应用启动完成");
}
}
最终都是在使用Spring的Application Event机制。
Spring Framwork官方文档也明确说明,除了直接实现ApplicaitonListener,还可以通过@EventListener注解注册监听方法。
主要区别在于表达形式:
| 方式 | 特点 |
|---|---|
ApplicationListener<E> |
接口式、类型明确、适合底层组件和框架 |
@EventListener |
注解式、代码更简洁、业务开发更常见 |
所以业务代码里通常写:
@EventListener
public void handle(ApplicationReadyEvent event) {
}
而在理解Spring Boot底层启动机制时,ApplicationListener更值得重点理解。
自定义注册ApplicationListener来监听Spring Boot启动过程中产生的事件
1.编写SpringApplicationRunListener实现类
/**
* ApplicationListener是Spring Framework 提供的事件监听机制中的核心接口,用于监听Spring应用中发布的事件(ApplicationEvent),
* 当某个事件发生时,Spring会自动调用对应监听器处理逻辑
*
* ApplicationListener = Spring中观察者模式(Observer Pattern)的实现,用于监听和响应应用生命周期事件或业务事件。
* 核心作用:
* 监听Spring Boot启动过程中的事件
* 监听Spring Context生命周期事件
* 监听自定义业务事件
* 在特定事件发生时执行逻辑
*
* Spring Boot启动过程中的九个事件
* 1.ApplicationStartingEvent: SpringBoot刚开始启动
* 2.ApplicationEnvironmentPreparedEvent: Environment已准备完成
* 3.ApplicationContextInitializedEvent: ApplicationContext已创建并完成初始化
* 4.ApplicationPreparedEvent: Bean定义已加载,但Context尚未refresh
* 5.ApplicationStartedEvent: Context已refresh,但Runner尚未执行
* 6.AvailabilityChangeEvent<LivenessState>: 应用进入存活状态
* 7.ApplicationReadyEvent: Runner执行完成,应用启动完成
* 8.AvailabilityChangeEvent<ReadinessState>: 应用进入可接收流量状态
* 9.ApplicationFailedEvent: 启动失败时发布
*
* ContextRefreshedEvent和WebServerInitializedEvent两个事件是有意没有纳入Spring Boot九大事件当中,
* 因为它们属于更底层的Spring容器生命周期和Web Server生命周期
*/
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("=====事件到达=====" + event);
}
}
2.在META-INF/spring.factory注册自定义监听器
src/main/resources/
└── META-INF/
└── spring.factories
org.springframework.context.ApplicationListener=com.demo.core.listener.MyApplicationListener
3.启动应用程序,检查日志打印
由于监听器监听的类型为ApplicationEvent,那么只有是ApplicationEvent这个抽象类的具体实现类都会被打印
F:\environment\java\jdk-21.0.3\bin\java.exe com.demo.core.CoreApplication
Connected to the target VM, address: '127.0.0.1:4623', transport: 'socket'
=====事件到达=====org.springframework.boot.context.event.ApplicationStartingEvent[source=org.springframework.boot.SpringApplication@4b2c5e02]
=====事件到达=====org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent[source=org.springframework.boot.SpringApplication@4b2c5e02]
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.5.9)
=====事件到达=====org.springframework.boot.context.event.ApplicationContextInitializedEvent[source=org.springframework.boot.SpringApplication@4b2c5e02]
2026-09-23T23:26:24.369+08:00 INFO 4904 --- [ main] com.demo.core.CoreApplication : Starting CoreApplication using Java 21.0.3 with PID 4904 (F:\environment\dev\git-project\springboot3-demo\boot3-09-core\target\classes started by 11720 in F:\environment\dev\git-project\springboot3-demo)
2026-09-23T23:26:24.371+08:00 INFO 4904 --- [ main] com.demo.core.CoreApplication : No active profile set, falling back to 1 default profile: "default"
=====事件到达=====org.springframework.boot.context.event.ApplicationPreparedEvent[source=org.springframework.boot.SpringApplication@4b2c5e02]
2026-09-23T23:26:25.173+08:00 INFO 4904 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2026-09-23T23:26:25.188+08:00 INFO 4904 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2026-09-23T23:26:25.188+08:00 INFO 4904 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.50]
2026-09-23T23:26:25.234+08:00 INFO 4904 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2026-09-23T23:26:25.234+08:00 INFO 4904 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 821 ms
2026-09-23T23:26:25.613+08:00 INFO 4904 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
=====事件到达=====org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@5ff00507]
=====事件到达=====org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@34645867, started on Wed Sep 23 23:26:24 CST 2026]
2026-09-23T23:26:25.619+08:00 INFO 4904 --- [ main] com.demo.core.CoreApplication : Started CoreApplication in 1.731 seconds (process running for 3.299)
=====事件到达=====org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@4b2c5e02]
=====事件到达=====org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@34645867, started on Wed Sep 23 23:26:24 CST 2026]
===ApplicationRunner starting===
===commandLineRunner starting===
=====事件到达=====org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@4b2c5e02]
=====事件到达=====org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@34645867, started on Wed Sep 23 23:26:24 CST 2026]
SpringApplicationRunListener和ApplicationListener的区别
| SpringApplicationRunListener | ApplicationListener | |
|---|---|---|
| 属于 | Spring Boot | Spring Framework |
| 核心性质 | 生命周期回调SPI | 事件监听器 |
| 监听对象 | SpringApplication.run()启动过程 |
ApplicationEvent |
| 触发方式 | SpringApplication主动调用方法 |
事件发布后有事件机制分发 |
| 典型方法 | starting()、started()、ready()等 |
onApplicationEvent() |
| 工作层级 | Spring Boot启动流程 | Spring事件机制 |
| 是需要Event | 不需要 | 需要事件 |
| 加载方式 | SpringFactoriesLoader |
Spring事件机制/Bean等 |
| 主要用途 | Boot启动流程扩展 | 应用事件处理 |
顺带说一下SPI与生命周期回调SPI
SPI(Service Provider Interface)的核心理念是:
框架定义扩展接口,具体实现由外部提供,运行时由框架自动发现并加载
本质就是一种解耦+可拔插扩展机制。
比如:
框架定义接口
↓
外部提供实现
↓
注册实现
↓
框架运行时加载并调用
而生命周期回调SPI,就是把SPI用在框架生命周期中:
当框架运行到某个生命周期阶段时,自动回调外部提供的实现。
例如SpringApplicationRunListener:
Spring Boot 启动
↓
到达某个生命周期阶段
↓
回调 SpringApplicationRunListener
Spring Boot事件驱动开发
可以把Spring Boot事件驱动开发理解成:在Spring的ApplicationContext事件机制继承上,让业务组件之间通过"发布事件"->"监听事件"的方式协作,从而降低直接依赖。
Spring Boot本身也大量使用这套机制,例如启动过程中会发布ApplicationStartingEvent、ApplicationStartedEvent、ApplicationReadyEvent、ApplicationFailedEvent等事件。官方文档明确说明:SpringBoot的应用事件本质上仍然通过Spring Framework的事件发布机制实现。
什么是事件驱动开发
传统写法通常是业务对象之间之间直接调用,例如账户登录成功以后以后:
/**
* 账户登录控制层接口
*/
@RestController
public class LoginController {
@Autowired
private AccountService accountService;
@Autowired
private CouponService couponService;
@Autowired
private SysService sysService;
@GetMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
//1.账户服务自动签到加积分
accountService.addAccountScore(username);
//2.优惠服务随机发放优化劵
couponService.sendCoupon(username);
//3.系统服务登记用户登录的信息
sysService.recordLogin(username);
return username + "登录成功";
}
}
这种方式的问题是业务类需要直接知道后续有哪些处理逻辑。
事件驱动之后变成:

登录服务只需要表达一件事情:账户登录成功,至于谁关心这个事件,由各监听器自行决定。
这实际上就是经典的Observer/观察者模式。Spring Framework官方文档也明确把ApplicationEvent+ApplicationListener机制描述为标准观察者模式的一种实现。
Spring事件驱动的三个核心角色
整个机制可以浓缩成三个对象:事件-Event、发布者-Publisher、监听器-Listener
Event:发生了什么
事件是一个对象,用来描述:系统中发生了一件值得其他模块关注的事情。
Spring中自定义事件通常继承:ApplicationEvent
例如:
/**
* 登录成功事件,所有事件都推荐继承 ApplicationEvent
*/
public class LoginSuccessEvent extends ApplicationEvent {
/**
*
* @param source 代表是谁登录成功
*/
public LoginSuccessEvent(UserEntity source) {
super(source);
}
}
从设计角度上,更推荐把事件理解成:过去式的业务事实
Publisher:事件发布者
Spring提供的核心接口式:ApplicationEventPublisher
官方文档中说明,ApplicationContext本身具备事件发布能力,并通过ApplicationEventPublisher暴露这一机制。
典型写法是通过继承ApplicationEventPublisherAware接口将ApplicationEventPublisher注入给我们来发布事件。
/**
* 事件发布者,通过继承ApplicationEventPublisherAware
*/
@Service
public class EventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public void sendEvent(ApplicationEvent event) {
applicationEventPublisher.publishEvent(event);
}
/**
* 底层发送事件用的组件,SpringBoot会通过ApplicationEventPublisherAware接口自动注入给我们
* 事件是广播出去的,所有监听这一事件的监听器都能收到
* @param applicationEventPublisher event publisher to be used by this object
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
其核心就是:
applicationEventPublisher.publishEvent(event);
可以把它理解成:
ApplicationEventPublisher
↓
发布事件
↓
ApplicationContext
↓
查找匹配 Listener
↓
调用监听方法
因此publishEvent()并不是发MQ,它首先是Spring容器内部的事件机制。
Listener:事件监听器
Spring提供两种写法
方式一:ApplicationListener
Spring最经典的方式
@Component
public class OrderCreatedListener
implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
System.out.println(
"监听到订单创建:" + event.orderId()
);
}
}
Spring Framework官方文档中描述的基本模型就是:
ApplicationEvent
+
ApplicationListener
当事件发布到ApplicationContext时,符合事件类型的Listener就会收到通知。
方式二:@EventListener
现在业务开发中更常用
@Component
public class OrderListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println(
"订单创建事件:" + event.orderId()
);
}
}
官方文档同样推荐这种基于注解的监听方式。Spring会根据方法参数确定监听的事件类型。
因此这两个方式用途相同,只是表达形式不同。
事件驱动开发示例
以一个用户登录的业务操作演示事件驱动开发是如何实现的
用户信息实体类
/**
* 用户信息实体类
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserEntity {
private String name;
private String password;
}
事件发布者
/**
* 事件发布者,通过继承ApplicationEventPublisherAware
*/
@Service
public class EventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public void sendEvent(ApplicationEvent event) {
applicationEventPublisher.publishEvent(event);
}
/**
* 底层发送事件用的组件,SpringBoot会通过ApplicationEventPublisherAware接口自动注入给我们
* 事件是广播出去的,所有监听这一事件的监听器都能收到
* @param applicationEventPublisher event publisher to be used by this object
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
登陆成功事件
/**
* 登录成功事件,所有事件都推荐继承 ApplicationEvent
*/
public class LoginSuccessEvent extends ApplicationEvent {
/**
*
* @param source 代表是谁登录成功
*/
public LoginSuccessEvent(UserEntity source) {
super(source);
}
}
控制层接口处理并发布事件
/**
* 账户登录控制层接口
*/
@RestController
public class LoginController {
@Autowired
private EventPublisher eventPublisher;
@GetMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
//业务处理登录
// 1.创建事件信息
LoginSuccessEvent loginSuccessEvent = new LoginSuccessEvent(new UserEntity(username, password));
// 2.发布事件
eventPublisher.sendEvent(loginSuccessEvent);
return username + "登录成功";
}
}
各个服务类监听登录成功事件,接收到事件后执行相应的业务逻辑
/**
* 账户服务类
* 监听事件后执行操作,通过继承监听器的方式
*/
@Service
public class AccountService implements ApplicationListener<LoginSuccessEvent> {
public void addAccountScore(String username) {
System.out.println(username + "用户登录:积分+1");
}
@Order(3)
@Override
public void onApplicationEvent(LoginSuccessEvent event) {
System.out.println("===AccountService收到事件==="+event.toString());
UserEntity source = (UserEntity) event.getSource();
addAccountScore(source.getName());
}
}
/**
* 优惠服务类
*/
@Service
public class CouponService {
@Order(2)
@EventListener
public void onEvent(LoginSuccessEvent event) {
System.out.println("===CouponService收到事件==="+ event.toString());
UserEntity source = (UserEntity) event.getSource();
sendCoupon(source.getName());
}
public void sendCoupon(String username) {
System.out.println(username+ "用户随机获得一张优惠券");
}
}
@Service
public class SysService {
@Order(1)
@EventListener
public void eventHandler(LoginSuccessEvent event) {
System.out.println("===SysService收到事件==="+ event.toString());
UserEntity source = (UserEntity) event.getSource();
recordLogin(source.getName());
}
public void recordLogin(String username) {
System.out.println(username + "用户登录信息已被记录");
}
}
自动配置原理
可以把Spring Boot的自动配置(Auto-configuration)理解成一句话:
Spring Boot根据当前项目的依赖、已有Bean、配置属性、应用类型等运行环境,自动决定哪些及配置应该生效,并把相应Bean注册到Spring IoC容器。
Spring Boot官方文档明确说明:自动配置会根据classpath中的jar包依赖推断应用需要什么配置,并且它是非侵入式的--当你自己提供Bean时,默认自动配置通常会"back away(退让)"。
自动配置的整体流程
一个典型Spring Boot应用:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
自动配置的大致经历如下图所示:

这就是自动配置的核心链路
核心入口:@SpringBootApplication
一切的起点都在启动类上的@SpringBootApplication注解。这个注解是一个组合注解,核心包含以下三个部分

- @SpringBootConfiguration:表示这是一个配置类
- @EnableAutoConfiguration:核心-开启自动配置
- @ComponentScan:开启组件扫描
核心机制:@EnableAutoConfiguration的运作流程
这个注解通过@Import导入了一个关键的选择器:AutoConfigurationImportSelector,它作用流程如下:

1.加载候选配置类(读取文件)
当Spring Boot启动时,AutoConfigurationImportSelector会去读取classpath下所有的jar包中的特定文件。
特定文件路径为:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
在这个文件中列出来所有可能需要自动配置的类的全限定名


注:此时只是读取了名单,并没有真正创建Bean
2.条件过滤(按需装配)
拿到候选名单后,Spring Boot会根据一系列条件注解进行筛选。只有满足条件的配置类才会生效。这是自动配置最核心的逻辑
这里列举一些常见的条件注解:
| 条件注解 | 作用 | 示例 |
|---|---|---|
| @ConditionalOnClass | classpath中存在指定的类时生效 | 只有引入了spring-webmvc的包,WebMvcAutoConfiguration才生效 |
| @ConditionalOnMissingBean | 容器中不存在指定的Bean时生效 | 如果你自己定义了一个DataSource,Spring Boot就不会再自动配置默认的数据源 |
| @ConditionalOnProperty | 配置文件中存在特定属性时生效 | 只有在application.yml中配置了相关属性,才会触发某些配置 |
| @ConditionalOnWebApplication | 当前时Web应用时生效 | 只有在Web环境下才会配置Servlet相关的Bean |
3.注册Bean
通过帅选的配置类会被Spring加载,配置类中通过@Bean注解定义的方法会被执行,从而将对象注入到Spring容器中
实战案例:HttpEncodingAutoConfiguration
以HTTP编码自动配置(HttpEncodingAutoConfiguration)为例,理解其工作原理
//这里仅截取了一小部分代码,实际该配置类没有这么少的代码
// 1.这是一个配置类
@AutoConfiguration
// 2.开启属性绑定
@EnableConfigurationProperties(ServerProperties.class)
// 3.必须是Servlet Web环境
@ConditionalOnWebApplication(type = Type.SERVLET)
// 4.必须存在这个类(引入了Spring Web依赖就存在)
@ConditionalOnClass(CharacterEncodingFilter.class)
// 5.配置文件中默认enabled=true
@ConditionalOnBooleanProperty(name = "server.servlet.encoding.enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
// 从配置文件中读取属性
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
// 6.只有在容器中没有CharacterEncodingFilter时才创建
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
}
- Spring Boot读取到了这个配置类
- 检查:是不是Web项目?是的(引入了web-starter)
- 检查:有没有CharacterEncodingFilter类?有的(web-starter里面有)
- 检查:配置文件里面有没有禁用编码配置?默认没禁用
- 条件通过:Spring执行characterEncodingFilter方法,场景一个Bean并注入容器
用户自定义覆盖机制
Spring Boot的自动配置非常智能,它永远以用户的配置优先
这是通过@ConditionalOnMissingBean实现的。绝大多少自动配置类在定义@Bean时都会加上这个注解。
例如:
Spring Boot默认配置了一个ObjectMapper(用于JSON序列化)。如果你在代码中自己定义了一个ObjectMapper的Bean
@Bean
public ObjectMapper objectMapper() {
// 自定义配置
return new ObjectMapper();
}
Spring Boot在执行自动配置时,发现容器中已经有了ObjectMapper,就会跳过默认配置。这就是为什么我们只需要写少量代码就能覆盖默认行为的原因
属性绑定:配置文件如何生效?
自动配置类通常配合
@EnableConfigurationProperties和@ConfigurationProperties使用
这里以ServerProperties属性类为例

先通过@ConfigurationProperties注解,将外部配置(如application.yml或application.properties)绑定到ServerProperties的字段上

之后用@EnableConfigurationProperties注解将ServerProperties类显示注册到容器中
自定义starter
Spring Boot官方对自定义Starter的核心定义可以概括为一句话:
Starter = 自动配置能力 + 一组推荐依赖的统一入口
Spring Boot官方建议,一个典型的自定义Starter可以拆成两个模块:
acme-spring-boot
└── 自动配置代码
acme-spring-boot-starter
└── 依赖聚合
├── acme-spring-boot
├── Spring Boot
└── 其他必要第三方依赖
不过官方也明确说明:两个模块不是强制要求。如果功能比较简单,也可以合并成一个acme-spring-boot-starter。
starter是什么
我们平时使用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
为什么只引入了一个依赖,就能获得:
- Spring Data JPA
- Hibernate
- JDBC
- 事务
- 自动配置
等一系列能力?因为starter本身承担了两个作用:
Starter
│
├── ① 依赖聚合
│
│ ├── spring-boot-starter
│ ├── xxx-sdk
│ └── 其他依赖
│
└── ② 引入自动配置
│
↓
XxxAutoConfiguration
│
↓
自动创建 Bean
官方将starter描述为一种方便的dependency desciptor(依赖描述符):开发者只需要加入starter,而不需要自己寻找和组合一大堆依赖。
因此需要特别区分:
starter ≠ 自动配置
starter负责依赖聚合与间接带入AutoConfiguration
AutoConfiguration负责真正创建Bean。
一个自定义starter的完整工作流程
假设我们开发一个hello-spring-starter
希望业务项目只需要引入依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
</dependency>
然后配置自定义参数:
hello:
prefix: Hello
就可以直接依赖注入业务类:
@Autowired
private HelloService helloService;
整个执行链路实际上是:
业务项目
│
│ 引入
▼
hello-spring-boot-starter
│
│ Maven传递依赖
▼
hello-spring-boot
│
├── HelloProperties
├── HelloAutoConfiguration
└── META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
│
▼
Spring Boot发现自动配置类
│
▼
判断 @Conditional 条件
│
条件成立
│
▼
执行 HelloAutoConfiguration
│
▼
创建 HelloService
│
▼
放入 Spring IOC 容器
参照官方文档实现自定义starter
官方建议的项目结构
按照SpringBoot官方推荐方式,可以设计为:
hello-parent
│
├── hello-spring-boot
│ │
│ └── src/main
│ ├── java
│ │ └── com.example.hello
│ │ ├── HelloService.java
│ │ ├── HelloProperties.java
│ │ └── HelloAutoConfiguration.java
│ │
│ └── resources
│ └── META-INF
│ └── spring
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│
└── hello-spring-boot-starter
└── pom.xml
官方认为,当组件存在多个可选能力或依赖组合是,拆成autoconfigure + starter更有意义;简单场景则可以合并。
编写普通业务组件
public class HelloService {
private final String prefix;
public HelloService(String prefix) {
this.prefix = Objects.requireNonNull(prefix, "prefix must not be null");
}
public String sayHello(String name) {
return prefix + ", " + name;
}
}
注意:
这里的HelloService本质上只是普通Java类,不需要加@Component注解,因为后面由AutoConfiguration创建它。
提供配置属性
starter一般都允许业务系统通过:
hello:
prefix: Hello
进行配置,因此可以使用:
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
/** Text placed before the person's name. */
private String prefix = "Hello";
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}
Spring Boot官方特别建议:自定义starter应该使用自己独立的配置命名空间。
比如:
hello.*
company.*
不要占用:
spring.*
server.*
因为这些命名空间属于Spring Boot。
编写AutoConfiguration
这一步才是整个starter最核心的部分
@AutoConfiguration
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix = "hello", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService(HelloProperties properties) {
return new HelloService(properties.getPrefix());
}
}
这里有三个关键的地方:
@AutoConfiguration
Spring Boot 3.x 官方推荐使用@AutoConfiguration表示该类为Spring BootZ自动配置类,该注解本身又建立在Spring的@Configuration机制之上。
可以把它理解为:
@Configuration
↓
普通Spring配置类
@AutoConfiguration
↓
Spring Boot自动配置类
区别主要在于后者参与Spring Boot的自动配置加载体系。
@ConditionalOnMissingBean
@ConditionalOnMissingBean是starter设计的关键,该注解放在HeloService的构建方法上的含义是:
Spring容器有没有HelloService?
┌──── 有 ────→ 不自动创建
│
↓
没有
│
↓
自动创建默认HelloService
为什么这样设计?因为Spring Boot自动配置有一个非常重要的设计思想:
Convention over Configuration(允许用户覆盖默认行为)
比如用户觉得默认实现不好,可以自己注册构建:
@Configuration
public class MyConfiguration {
@Bean
public HelloService helloService() {
return new MyHelloService();
}
}
由于已经存在HelloService 这一个Bean组件,于是@ConditionalOnMissingBean注解不成立,start自动配置就会退让。
官方也专门将@ConditionalOnMissingBean作为典型自动配置条件,用于允许应用开发者覆盖默认配置。
这里也列出几个starter中常见的条件注解:
| 注解 | 含义 |
|---|---|
| @ConditionalOnClass | ClassPath有某个类才生效 |
| @ConditionalOnMissingClass | 没有某个类才生效 |
| @ConditionalOnBean | 容器存在某Bean才生效 |
| @ConditionalOnMissingBean | 容器没有某Bean才生效 |
| @ConditionalOnProperty | 配置项满足条件才生效 |
| @ConditionalOnWebApplication | Web应用才生效 |
| @ConditionalOnResource | 存在特定资源才生效 |
配置开关
很多官方starter都支持:
xxx:
enabled: true
因此在编写配置类时添加了这个注解
@ConditionalOnProperty(prefix = "hello", name = "enabled", havingValue = "true", matchIfMissing = true)
如果配置参数时为:
hello:
enabled: false
或者没有配置(因为matchIfMissing = true)则条件不成立,HelloService不创建。
注册AutoConfiguration
最容易遗漏的一点
虽然写了
@AutoConfiguration
public class HelloAutoConfiguration {
}
但SpringBoot并不会因为它有这个注解就会自动扫描到它。因为该starter是作为依赖引入项目,虽然HelloAutoConfiguration确实进入了classpath,但类在classpath中不等于它已注册到Spring容器。而且业务项目的@ComponentScan通常只扫描应用主类所在包及其子包,不会因为引入了JAR就扫描其中所有包。因此必须注册。
Spring Boot 3.x使用:
META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容为对应配置类的全类名:
com.example.hello.HelloAutoConfiguration
官方明确说明,Spring Boot会在发布Jar中查找:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
其中每一行是一个自动配置类。
使用方引用
业务项目只需要引入:
<dependency>
<groupId>com.example</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
配置:
hello:
enabled: true
prefix: Welcome
然后依赖注入业务组件即可:
@RestController
public class HelloController {
private final HelloService helloService;
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
@GetMapping("/hello")
public String hello() {
return helloService.sayHello("Spring Boot");
}
}
业务项目不需要创建:
@Bean
public HelloService helloService() {
...
}
或是导入:
@Import(HelloAutoConfiguration.class)
这就是starter最核心的使用体验:
加依赖
+
写配置
=
功能可用
参考资料
SpringApplication :: Spring Boot

浙公网安备 33010602011771号