SpringBoot-核心原理

SpringBoot-核心原理

事件和监听器

什么是事件和监听器

可以先用一个简单模型进行理解

1

这里面有三个核心角色:

角色 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(...),实际上背后要经历:

  1. 准备启动
  2. 准备Environment
  3. 创建ApplicationContext
  4. 加载BeanDefinition
  5. refresh ApplicationContext
  6. 启动 WebServer
  7. 执行 Runner
  8. 应用完全启动

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理解成下面这条生命周期主线

2

自定义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自己也是这么注册默认实现的

3

也就是说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启动过程中的九大事件

  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: 启动失败时发布

4

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 + "登录成功";
    }

}

这种方式的问题是业务类需要直接知道后续有哪些处理逻辑。

事件驱动之后变成:

5

登录服务只需要表达一件事情:账户登录成功,至于谁关心这个事件,由各监听器自行决定。

这实际上就是经典的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);
    }
}

自动配置的大致经历如下图所示:

6

这就是自动配置的核心链路

核心入口:@SpringBootApplication

一切的起点都在启动类上的@SpringBootApplication注解。这个注解是一个组合注解,核心包含以下三个部分

7

  • @SpringBootConfiguration:表示这是一个配置类
  • @EnableAutoConfiguration:核心-开启自动配置
  • @ComponentScan:开启组件扫描

核心机制:@EnableAutoConfiguration的运作流程

这个注解通过@Import导入了一个关键的选择器:AutoConfigurationImportSelector,它作用流程如下:

9

1.加载候选配置类(读取文件)

当Spring Boot启动时,AutoConfigurationImportSelector会去读取classpath下所有的jar包中的特定文件。

特定文件路径为:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

在这个文件中列出来所有可能需要自动配置的类的全限定名

9

11

注:此时只是读取了名单,并没有真正创建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;
	}
}
  1. Spring Boot读取到了这个配置类
  2. 检查:是不是Web项目?是的(引入了web-starter)
  3. 检查:有没有CharacterEncodingFilter类?有的(web-starter里面有)
  4. 检查:配置文件里面有没有禁用编码配置?默认没禁用
  5. 条件通过: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属性类为例

11

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

12

之后用@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

Auto-configuration :: Spring Boot

Creating Your Own Auto-configuration :: Spring Boot

posted @ 2026-09-27 19:12  柯南。道尔  阅读(7)  评论(0)    收藏  举报