Spring Boot实战:Event事件机制解析与实战

Spring Boot实战:Event事件机制解析与实战

😄生命不息,写作不止

🔥 继续踏上学习之路,学之分享笔记

👊 总有一天我也能像各位大佬一样

🏆 博客首页 @怒放吧德德 To记录领地 @一个有梦有戏的人

🌝分享学习心得,欢迎指正,大家一起学习成长!

转发请携带作者信息 @怒放吧德德(掘金) @一个有梦有戏的人(CSDN)

前言

在Spring Boot应用开发中,业务逻辑的紧耦合是代码难以维护和扩展的主要根源。当核心业务与众多拓展逻辑交织在一起时,任何微小的改动都可能牵一发而动全身。Spring事件机制基于经典的观察者模式,为这一痛点提供了优雅的解决方案------它让核心业务只需专注自身,通过发布事件即可"通知"各方,而拓展业务则通过监听事件自主响应,真正实现解耦与隔离。本文将从设计思想到实战应用,由浅入深地解析Spring Boot事件机制的核心原理与进阶技巧,帮助你在实际项目中写出更优雅、更可维护的代码。

本次案例代码仓库:gitee.com/liyongde/ja...

1. 概述

在设计模式体系中,观察者模式是实现业务解耦的经典设计模式,维基百科对其定义为:一个目标对象管理所有依赖的观察者对象,在自身状态改变时主动发出通知,通过调用观察者的方法完成交互,广泛用于实时事件处理系统。

在日常业务开发中,观察者模式的核心价值就是解耦核心业务与拓展业务 。以电商用户下单场景为例,下单成功后通常需要完成发放优惠券、发送短信通知、增加用户积分、记录运营日志等操作,传统写法会将所有逻辑耦合在一个方法中:

plain 复制代码
// 传统耦合写法
public void placeOrder(Order order) {
    // 核心逻辑:创建订单
    orderMapper.insert(order);
    // 拓展逻辑:一堆业务调用
    couponService.sendCoupon(order.getUserId());
    smsService.sendNotify(order.getPhone());
    pointsService.addPoints(order.getUserId(), order.getAmount());
    opsLogService.recordLog(order);
}

这种写法的问题显而易见:新增/删除拓展逻辑必须修改核心方法、某个拓展服务异常会阻塞核心下单流程、拓展逻辑无法单独测试。

使用Spring事件机制(基于观察者模式实现)后,代码会变得异常优雅:

OrderService完成核心的下单逻辑后,只需发布一个OrderCreatedEvent事件,无需关注任何拓展逻辑;其他服务只需订阅该事件,即可实现自定义的业务处理,完美实现核心业务与拓展业务的解耦

观察者模式 vs 发布订阅模式

很多胖友会混淆这两个模式,简单来说:发布订阅模式是广义上的观察者模式 ,在观察者模式的Subject(目标)和Observer(观察者)之间引入了Event Channel(事件通道)作为中介,进一步解耦发布者和订阅者,而Spring事件机制是标准的观察者模式实现,发布者直接与观察者交互。

2. Spring 事件机制核心三要素

Spring基于JDK内置的事件机制(java.util.EventObject/java.util.EventListener)进行拓展,实现了轻量级、易使用的事件机制,核心由事件、事件发布者、事件监听器三部分组成,三者各司其职、协同工作。

2.1 事件(ApplicationEvent)

  • 所有自定义事件的基类 ,继承自JDK的java.util.EventObject
  • 内置source属性(获取事件源,即发布事件的对象)和timestamp属性(获取事件发生时间)
  • 自定义事件只需继承该类,添加业务所需的成员变量和getter方法即可

2.2 事件发布者(ApplicationEventPublisher)

  • 负责发布事件 的核心接口,提供publishEvent()方法发布事件
  • 注入方式:实现ApplicationEventPublisherAware接口(推荐)或直接@Autowired注入
  • Spring容器本身就是一个ApplicationEventPublisher实现类,可直接在容器中使用

2.3 事件监听器(ApplicationListener / @EventListener)

  • 负责监听并处理指定事件 ,有两种实现方式:
    • 实现ApplicationListener接口(继承自JDK的java.util.EventListener),通过泛型指定监听的事件类型
    • 在方法上添加@EventListener注解(Spring 4.2+推荐),无需实现接口,更简洁灵活
  • 监听器会自动感知容器中的事件,当发布对应类型的事件时,会触发监听器的处理方法

3. 入门实战示例

本次实战以电商用户下单 为业务场景,实现核心下单逻辑与拓展逻辑的解耦,技术栈为Spring Boot 2.7.10 + Spring MVC,实现下单成功后发放优惠券、发送短信通知、增加用户积分三个拓展功能。(结构关系如图)

3.1 引入依赖

创建Spring Boot项目,在pom.xml中引入核心依赖,只需引入spring-boot-starter-web即可(内置Spring事件机制相关依赖):

plain 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.10</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>spring-boot-event-demo</artifactId>
    <dependencies>
        <!-- Spring Web 核心依赖,提供容器和接口能力 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 日志依赖,方便打印执行日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
    </dependencies>
</project>

3.2 自定义事件:OrderCreatedEvent

创建下单成功事件类,继承ApplicationEvent,封装下单后的核心业务数据(订单ID、用户ID、手机号、订单金额),作为事件传递的载体:

java 复制代码
/**
 * 下单成功事件
 */
public class OrderCreatedEvent extends ApplicationEvent {
    /**
     * 订单ID
     */
    private Long orderId;
    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 用户手机号
     */
    private String phone;
    /**
     * 订单金额
     */
    private BigDecimal amount;

    // 构造方法必须传入source(事件源)
    public OrderCreatedEvent(Object source, Long orderId, Long userId, String phone, BigDecimal amount) {
        super(source);
        this.orderId = orderId;
        this.userId = userId;
        this.phone = phone;
        this.amount = amount;
    }
    // ...
}

3.3 事件发布者:OrderService

创建订单服务,实现ApplicationEventPublisherAware接口,注入事件发布器,在核心下单逻辑完成后发布下单成功事件

java 复制代码
/**
 * 订单服务(事件发布者)
 */
@Service
public class OrderService implements ApplicationEventPublisherAware {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
    // 事件发布器
    private ApplicationEventPublisher applicationEventPublisher;

    // 注入事件发布器
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 核心下单方法
     * @param userId 用户ID
     * @param phone 手机号
     * @param amount 订单金额
     * @return 订单ID
     */
    public Long placeOrder(Long userId, String phone, BigDecimal amount) {
        // 1. 核心业务逻辑:生成订单(模拟,实际为数据库插入)
        Long orderId = System.currentTimeMillis(); // 用时间戳模拟订单ID
        logger.info("[placeOrder][核心逻辑] 下单成功,订单ID:{},用户ID:{}", orderId, userId);

        // 2. 发布下单成功事件
        applicationEventPublisher.publishEvent(
            new OrderCreatedEvent(this, orderId, userId, phone, amount)
        );
        return orderId;
    }
}

3.4 事件监听器:实现拓展业务

实现三个拓展业务的监听器,分别使用接口实现注解 两种方式,演示Spring事件监听器的灵活用法,并为短信服务添加异步执行(非核心逻辑不阻塞主线程)。

3.4.1 短信服务:实现ApplicationListener接口(异步)
plain 复制代码
package cn.sourcecode.event.service;

import cn.sourcecode.event.event.OrderCreatedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * 短信服务(事件监听器:接口实现方式)
 */
@Service
public class SmsService implements ApplicationListener<OrderCreatedEvent> {
    private static final Logger logger = LoggerFactory.getLogger(SmsService.class);

    // 异步执行:添加@Async注解,避免短信发送阻塞下单流程
    @Override
    @Async
    public void onApplicationEvent(OrderCreatedEvent event) {
        logger.info("[onApplicationEvent][短信服务] 给用户{}({})发送下单通知,订单ID:{}",
                event.getUserId(), event.getPhone(), event.getOrderId());
        // 实际业务:调用短信接口发送通知
    }
}
3.4.2 优惠券服务:@EventListener注解方式(指定执行顺序)
java 复制代码
@Slf4j
@Service
public class CouponService implements Ordered {

    // 监听下单成功事件
    @EventListener
    public void sendCoupon(OrderCreatedEvent event) {
        log.info("[sendCoupon][优惠券服务] 给用户{}发放满减优惠券,订单金额:{}",
                 event.getUserId(), event.getAmount());
        // 实际业务:发放优惠券并写入数据库
    }

    @Override
    public int getOrder() {
        // 指定执行顺序为1(优先级最高)
        return 1;
    }
}
3.4.3 积分服务:@EventListener注解方式
java 复制代码
@Slf4j
@Service
public class PointsService implements Ordered {
    // 监听下单成功事件
    @EventListener
    public void addPoints(OrderCreatedEvent event) {
        // 模拟:订单金额1元=1积分
        int points = event.getAmount().intValue();
        log.info("[addPoints][积分服务] 给用户{}增加{}积分,订单ID:{}",
                 event.getUserId(), points, event.getOrderId());
        // 实际业务:增加用户积分并更新
    }

    @Override
    public int getOrder() {
        // 指定执行顺序为2
        return 2;
    }
}

3.5 控制层:OrderController

创建接口层,提供HTTP接口用于测试下单功能,调用订单服务的核心方法:

java 复制代码
/**
 * 订单控制层
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    /**
     * 下单接口
     * @param userId 用户ID
     * @param phone 手机号
     * @param amount 订单金额
     * @return 下单结果
     */
    @GetMapping("/place")
    public String placeOrder(@RequestParam Long userId,
                             @RequestParam String phone,
                             @RequestParam BigDecimal amount) {
        Long orderId = orderService.placeOrder(userId, phone, amount);
        return "下单成功,订单ID:" + orderId;
    }
}

3.6 应用启动类:EventDemoApplication

启动类需添加@EnableAsync注解,开启Spring异步功能 (支持监听器的@Async注解):

plain 复制代码
/**
 * 应用启动类
 */
@SpringBootApplication
@EnableAsync // 开启异步功能,支持@Async注解
public class EventDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(EventDemoApplication.class, args);
    }
}

3.7 简单测试

执行完毕之后可以看到以下日志

plain 复制代码
[placeOrder][核心逻辑] 下单成功,订单ID:1719999999999,用户ID:1001
[sendCoupon][优惠券服务] 给用户1001发放满减优惠券,订单金额:99
[addPoints][积分服务] 给用户1001增加99积分,订单ID:1719999999999
[onApplicationEvent][短信服务] 给用户1001(13800138000)发送下单通知,订单ID:1719999999999

日志说明:

  1. 核心下单逻辑先执行,完成后发布事件
  2. 优惠券服务(order=1)优先于积分服务(order=2)执行,实现了监听器顺序控制
  3. 短信服务在独立线程执行,不阻塞主线程的同步逻辑

4. Spring 内置核心事件

Spring框架和Spring Boot在启动、运行、关闭的整个生命周期中,会自动发布大量内置事件,我们可以通过监听这些事件,实现对容器和应用的生命周期拓展,无需自定义事件即可完成个性化需求。

4.1 ApplicationContextEvent:容器生命周期事件

图来自芋道 Spring Boot 事件机制 Event 入门 | 芋道源码 ------ 纯源码解析博客

这是Spring IoC容器的核心事件基类,所有容器相关事件都继承自它,对应容器的创建、刷新、启动、停止、关闭等生命周期阶段,核心实现类:

  • ContextRefreshedEvent:容器初始化/刷新完成事件(最常用,容器启动完成后触发)
  • ContextStartedEvent :容器启动完成事件(调用start()方法后触发)
  • ContextStoppedEvent :容器停止完成事件(调用stop()方法后触发)
  • ContextClosedEvent :容器关闭开始事件(调用close()方法后触发)

应用场景 :在ContextRefreshedEvent事件中初始化系统参数、加载本地缓存、连接第三方服务等。

4.2 SpringApplicationEvent:应用生命周期事件

图来自芋道 Spring Boot 事件机制 Event 入门 | 芋道源码 ------ 纯源码解析博客

这是Spring Boot应用的核心事件基类,对应应用的启动、准备、运行、失败等生命周期阶段,核心实现类:

  • ApplicationStartingEvent:应用启动开始事件(仅SpringApplication.run()方法刚执行时触发)
  • ApplicationEnvironmentPreparedEvent:应用环境准备完成事件(配置文件、环境变量加载完成)
  • ApplicationContextInitializedEvent :Spring Context 准备完成,但是 Bean Definition未加载时的事件
  • ApplicationPreparedEvent:Spring Context 准备完成,但是未刷新时的事件。
  • ApplicationReadyEvent:应用启动成功事件(所有Bean初始化完成,可处理请求,最常用)
  • ApplicationFailedEvent:应用启动失败事件(Bean初始化、容器启动异常时触发)

应用场景 :在ApplicationReadyEvent中打印应用启动成功日志、记录启动时间;在ApplicationFailedEvent中实现启动失败的告警通知。

4.3 实战常用内置事件

除了生命周期事件,Spring Cloud生态中还有很多实用的内置事件:

  • RefreshRoutesEvent:Spring Cloud Gateway网关路由刷新事件,结合Nacos可实现动态路由
  • RefreshRemoteApplicationEvent:Spring Cloud Config配置刷新事件,结合RabbitMQ可实现分布式配置动态刷新
  • RefreshEvent:Spring Cloud上下文刷新事件,触发配置重新加载

5. 高级拓展技巧

掌握基础用法后,胖友们可以尝试这些高级技巧,让Spring事件机制更贴合复杂的业务场景,以下是实际开发中最常用的4个拓展点,附核心代码示例。

5.1 控制监听器执行顺序

当多个监听器监听同一个事件时,默认执行顺序是不确定的,可通过3种方式指定顺序(数值越小,优先级越高 ),注意:异步监听器(@Async)不支持顺序控制

  1. @Order注解 (类级别控制):在监听器类上添加@Order(1)
  2. 实现Ordered接口 (动态控制):实现getOrder()方法,支持运行时动态返回顺序值
plain 复制代码
// 实现Ordered接口示例
@Service
public class PointsService implements Ordered {
    @EventListener
    public void addPoints(OrderCreatedEvent event) {
        // 业务逻辑
    }
    // 动态指定顺序为2
    @Override
    public int getOrder() {
        return 2;
    }
}

5.2 事务绑定事件:@TransactionalEventListener

在实际业务中,核心业务(如下单)通常带有事务,若监听器在事务提交前执行,可能出现数据未持久化导致监听器读取不到数据 的问题。Spring 4.2+提供@TransactionalEventListener注解,可将监听器绑定到事务的指定阶段

核心属性
  • phase :事务阶段,可选值:
    • TransactionPhase.BEFORE_COMMIT:事务提交前执行
    • TransactionPhase.AFTER_COMMIT(默认):事务提交成功后执行
    • TransactionPhase.AFTER_ROLLBACK:事务回滚后执行
    • TransactionPhase.AFTER_COMPLETION:事务完成后执行(无论提交/回滚)
  • fallbackExecution :无事务时是否执行,默认false(无事务则不执行)
代码示例
java 复制代码
// 订单服务添加事务
@Transactional
public Long placeOrder(Long userId, String phone, BigDecimal amount) {
    // 核心业务逻辑
}

// 监听器绑定到事务提交后执行
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendCoupon(OrderCreatedEvent event) {
// 事务提交成功后,再发放优惠券,避免数据不一致
}

5.3 智能监听器:SmartApplicationListener

若需要一个监听器同时监听多种事件 ,或根据事件源过滤事件 ,可实现SmartApplicationListener接口,替代传统的ApplicationListener接口,实现更灵活的事件监听。

代码示例
java 复制代码
@Service
public class SmartBizListener implements SmartApplicationListener {
    // 指定监听的事件类型(可添加多个)
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return OrderCreatedEvent.class.isAssignableFrom(eventType) 
        || UserRegisterEvent.class.isAssignableFrom(eventType);
    }

    // 指定监听的事件源类型(仅处理指定发布者的事件)
    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return OrderService.class.isAssignableFrom(sourceType) 
        || UserService.class.isAssignableFrom(sourceType);
    }

    // 事件处理逻辑
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof OrderCreatedEvent) {
            // 处理下单事件
        } else if (event instanceof UserRegisterEvent) {
            // 处理注册事件
        }
    }

    // 执行顺序
    @Override
    public int getOrder() {
        return 1;
    }
}

5.4 自定义事件广播器:ApplicationEventMulticaster

Spring默认使用SimpleApplicationEventMulticaster作为事件广播器,负责将事件推送给所有匹配的监听器。若需要自定义事件广播规则(如自定义线程池、过滤监听器、异步策略),可实现ApplicationEventMulticaster接口,或继承SimpleApplicationEventMulticaster进行拓展。

应用场景:为所有异步监听器指定自定义线程池,替代默认的SimpleAsyncTaskExecutor。

总结

Spring事件机制是基于观察者模式的轻量级解耦方案,无需引入额外中间件,即可实现核心业务与拓展业务的解耦,其核心优势体现在:

  1. 解耦:发布者无需知道监听器的存在,新增/删除拓展逻辑无需修改核心代码
  2. 灵活:支持同步/异步执行、事务绑定、顺序控制,适配各种业务场景
  3. 轻量:基于Spring原生能力,无需引入MQ等中间件,学习成本低
  4. 可拓展:支持自定义事件、监听器、广播器,贴合复杂业务需求

实际开发建议

  • 核心业务(如下单、注册)保持纯净,只处理核心逻辑,拓展逻辑通过事件实现
  • 非核心、耗时的拓展逻辑(如发送短信、邮件、调用第三方接口)使用@Async异步执行
  • 涉及数据库操作的拓展逻辑,使用@TransactionalEventListener绑定事务,保证数据一致性
  • 多个监听器存在依赖关系时,通过order属性指定执行顺序

Spring事件机制适合单体应用或轻量级分布式应用的解耦,若需要跨服务、跨节点的事件通知,可结合RabbitMQ/Kafka等消息中间件实现,形成「本地事件+分布式事件」的双层解耦方案。

附录

1\]. 文章大致参考芋道(其更为丰富,可以看一下):[芋道 Spring Boot 事件机制 Event 入门 \| 芋道源码 ------ 纯源码解析博客](https://link.juejin.cn?target=https%3A%2F%2Fwww.iocoder.cn%2FSpring-Boot%2FEvent%2F%3Fgithub "https://www.iocoder.cn/Spring-Boot/Event/?github") \[2\]. 代码仓库地址:[Trial-8-Spring-Event-Demo](https://link.juejin.cn?target=https%3A%2F%2Fgitee.com%2Fliyongde%2Fjava-trial%2Ftree%2Fmaster%2FTrial8-Spring-Event%2FTrial-8-Spring-Event-Demo "https://gitee.com/liyongde/java-trial/tree/master/Trial8-Spring-Event/Trial-8-Spring-Event-Demo") *** ** * ** *** *转发请携带作者信息* **@怒放吧德德 @一个有梦有戏的人** 持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。**转载请携带链接,转载到微信公众号请勿选择原创,谢谢!** 👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍 谢谢支持!

相关推荐
梦无矶2 小时前
快速设置uv默认源为国内镜像
数据库·redis·后端·python·uv
wsx_iot2 小时前
arthas使用
java·arthas
㳺三才人子2 小时前
SpringDoc OpenAPI 配置問題
服务器·spring boot
lifallen3 小时前
Flink Watermark 设计分析
java·大数据·flink
yoyo_zzm3 小时前
SpringBoot Test详解
spring boot·后端·log4j
AKA__Zas3 小时前
初识 事务
java·开发语言·数据库·sql
kongba0073 小时前
2026年4月19日 kimi记忆备份
java·前端·数据库
AI人工智能+电脑小能手3 小时前
【大白话说Java面试题】【Java基础篇】01_说说ArrayList的底层原理/扩容规则
java·后端·面试·list