SpringBoot Event事件机制,轻松实现业务解耦

前言

  相信很多人对它的认知,只停留在"监听 SpringBoot 启动"的浅层用法,甚至觉得"日常开发用不上"。但实际上,它是 Spring 全家桶中最强大的解耦神器,更是 SpringBoot 自身底层实现的核心依赖(比如自动配置、启动流程、上下文刷新,都靠事件机制驱动)。日常开发中,无论是用户注册后的通知推送、订单支付后的后续处理,还是系统初始化、日志收集、监控告警,用事件机制都能让代码变得简洁、解耦、可扩展,还能轻松实现异步处理,避免阻塞主线程。

一、事件机制的核心本质

1.1 概述

  在分布式系统开发中,事件驱动架构是解耦业务逻辑的关键技术。以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作,如下图所示:

1.2 Spring 事件机制

  Spring 事件机制,本质是标准的观察者模式(发布-订阅模式) ,核心目的是解耦发布者与订阅者,让两者之间没有直接依赖,仅通过"事件"进行通信。Spring 事件机制完美对应观察者模式的三个核心角色,搞懂这三个角色,就掌握了事件机制的核心逻辑:

事件(Event) 事件本身,是发布者与监听器之间通信的载体,包含事件相关的所有数据。 Spring 中所有事件都必须继承 ApplicationEvent 类。
发布者(Publisher) 事件的发起者,负责创建事件、并将事件发布到 Spring 容器中。 Spring 中通过 ApplicationEventPublisher 接口实现事件发布。
监听器(Listener) 事件的订阅者,负责监听指定类型的事件,当事件被发布时,自动执行对应的处理逻辑。 Spring 中所有监听器都需要实现 ApplicationListener 接口,或通过注解标注。

1.3 事件机制的核心流程

  ApplcationEvent 是 Spring 为我们提供的一个事件监听、订阅的实现,在一些与业务无关的、通用的操作方法,我们可以把它设计成事件监听器,事件发布者不需要考虑谁去监听、监听的具体内容是什么,发布者的工作只是为了发布事件而已。

graph LR A[发布者] A-->B[创建事件] B-->C[&#34;发布事件<br/>(通过 ApplicationEventPublisher)&#34;] C-->D[Spring 容器转发事件] D-->E[监听该事件的监听器<br/>自动执行处理逻辑]

✅ 核心优势:发布者只需要关注"发布事件",不用关心谁会监听、如何处理;监听器只需要关注"监听事件",不用关心事件是谁发布的、从哪来的。两者完全解耦,后续修改任何一方的逻辑,都不会影响另一方。

二、核心 API 详解

  Spring 事件机制的核心,就是三个核心 API:ApplicationEvent(事件)、ApplicationListener(监听器)、ApplicationEventPublisher(发布者)。

2.1 事件(ApplicationEvent)

  ApplicationEvent 是 Spring 中所有事件的顶层抽象类,位于 org.springframework.context 包下,所有自定义事件都必须继承它。

java 复制代码
public abstract class ApplicationEvent extends EventObject {
    // 事件发生的时间戳(自动赋值)
    private final long timestamp;

	  // 构造方法:必须传入事件源(source),即事件的发布者
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }

    public ApplicationEvent(Object source, Clock clock) {
        super(source);
        this.timestamp = clock.millis();
    }

	  // 获取事件发生时间
    public final long getTimestamp() {
        return this.timestamp;
    }
}

  ApplicationEvent 继承于 JDK 的 EventObject,该类维护了一个 source 字段,用于存储事件源(发布者)。内置 timestamp 字段,自动记录事件发生的时间戳,无需手动赋值。

2.2 事件监听器(ApplicationListener)

  ApplicationListener 是 Spring 中所有监听器的顶层接口,用于定义"事件触发时的处理逻辑",所有监听器都需要实现该接口(或通过注解替代)。

java 复制代码
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}

  ApplicationListener 是一个函数式接口,只有一个抽象方法onApplicationEvent(E event),该方法会在事件被发布时自动调用。泛型 E 必须是 ApplicationEvent 的子类,表示该监听器只监听指定类型的事件。注意监听器必须被 Spring 管理(添加@Component、@Service 等注解),否则 Spring 容器无法识别,无法触发事件处理。

2.3 事件发布者(ApplicationEventPublisher)

  ApplicationEventPublisher 是 Spring 提供的事件发布接口,用于发布事件,所有事件都必须通过该接口发布,Spring 容器会自动将事件转发给对应的监听器。

java 复制代码
@FunctionalInterface
public interface ApplicationEventPublisher {
    default void publishEvent(ApplicationEvent event) {
        this.publishEvent((Object)event);
    }

    void publishEvent(Object event);
}

三、代码实现

3.1 依赖

  在这里只是简单的讲解如何使用ApplicationEvent以及使用Listen来完成业务逻辑的解耦,不涉及到复杂的数据交互,所有需要引入的依赖很少,项目Pom.xml配置文件如下:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

3.2 自定义事件源

  首先要创建一个事件,监听都是围绕着事件来进行的。创建 UserRegisterEvent 事件类,只需继承 ApplicationEvent,必须重写构造方法,传入事件源,除此之外,还可以添加自身需要的业务字段(如用户信息、订单信息)以及Getter方法。

java 复制代码
@Getter
public class UserRegisterEvent extends ApplicationEvent {
    // 自定义业务字段
    private UserInfo user;

    public UserRegisterEvent(Object source, UserInfo user) {
        super(source);
        this.user = user;
    }
}

3.3 事件发布

  事件发布是由 ApplicationContext 对象管控的,在事件发布之前需要注入 ApplicationContext 对象,然后通过 publishEvent 方法完成事件发布。日常开发中,发布事件有两种常用方式;

  • 通过实现 ApplicationEventPublisherAware 接口,Spring 会自动注入 ApplicationEventPublisher 对象:

    java 复制代码
    @Service
    public class UserServiceImpl implements ApplicationEventPublisherAware {
        // 事件发布器(Spring 自动注入)
        private ApplicationEventPublisher publisher;
    
        @Override
        public void userRegister(UserInfo user) {
            publisher.publishEvent(new UserRegisterEvent(this, user));
        }
    }
  • 直接 @Autowired 或者 @Resource 注入 ApplicationEventPublisher,这种方式简单直接,适合不需要实现接口的场景,上面方式一致。

    java 复制代码
    @Service
    public class UserServiceImpl implements UserService {
        // 直接注入事件发布器
        @Resource
        private ApplicationEventPublisher publisher;
    
        @Override
        public void userRegister(UserInfo user) {
            publisher.publishEvent(new UserRegisterEvent(this, user));
        }
    }

3.4 事件监听与实现

3.4.1 实现 ApplicationListener 接口

  最基础的方式,适用于所有场景,尤其是需要自定义复杂处理逻辑的情况:

java 复制代码
@Component
@Slf4j
public class UserRegisterListener implements ApplicationListener<UserRegisterEvent> {

    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        // 1. 获取事件中的业务数据
        UserInfo user = event.getUser();
        // 2. 处理业务逻辑(如发送通知)
        log.info("监听器1(接口方式):用户 {} 注册,发送短信通知...", user.getUsername());
    }
}

✅ 特点:通用、灵活,可处理复杂逻辑,支持泛型指定监听的事件类型,避免接收无关事件。

3.4.2 使用 @EventListener 注解

  Spring 4.2+ 新增注解方式,无需实现接口,只需在方法上添加 @EventListener 注解,指定监听的事件类型,简洁高效,可监听多个事件,是日常开发中最推荐的方式。

java 复制代码
@Slf4j
@Component
public class UserRegisterAnnotationListener {
    /**
     * 注解指定监听的事件类型,方法参数为事件对象
     */
    @EventListener(UserRegisterEvent.class)
    public void handleUserRegisterEvent(UserRegisterEvent event) {
        UserInfo user = event.getUser();
        log.info("监听器2(注解方式):用户 {}注册,记录注册日志...", user.getUsername());
    }

    /**
     * 一个方法可监听多个事件(用数组指定)
     */
    @EventListener({UserRegisterEvent.class, OrderPayEvent.class})
    public void handleMultiEvent(ApplicationEvent event) {
        if (event instanceof UserRegisterEvent) {
            log.info("监听器2(注解方式):处理用户注册事件");
        } else if (event instanceof OrderPayEvent) {
            log.info("监听器2(注解方式):处理订单支付事件");
        }
    }
}

⚠️ 注意:方法参数必须是事件对象(或事件的 payload),否则 Spring 无法识别监听的事件类型。

3.4.3 使用 @TransactionalEventListener 注解

  日常开发中,经常需要"事务提交后,再执行事件处理逻辑"(如订单支付事务提交后,再发送支付通知,避免事务未提交,通知已发送,出现数据不一致)。此时就需要用 @TransactionalEventListener 注解,它是 @EventListener 的子类,支持事务绑定。

xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>
java 复制代码
@Component
@Slf4j
public class UserRegisterTransactionalListener {
    /**
     * 事务提交后,再执行事件处理
     */
    @TransactionalEventListener(value = UserRegisterEvent.class, phase = TransactionPhase.AFTER_COMMIT)
    public void handleUserRegisterAfterCommit(UserRegisterEvent event) {
        UserInfo user = event.getUser();
        log.info("监听器3(事务绑定):用户 {} 注册事务已提交,发送邮件通知...", user.getUsername());
    }

    /**
     * 事务提交前执行(如校验数据)
     */
    @TransactionalEventListener(value = UserRegisterEvent.class, phase = TransactionPhase.BEFORE_COMMIT)
    public void handleBeforeCommit(UserRegisterEvent event) {
        UserInfo user = event.getUser();
        log.info("监听器3(事务绑定):用户 {} 注册事务即将提交...", user.getUsername());
    }
}
核心参数 配置项 简要说明
value
phase
BEFORE_COMMIT 事务提交前触发
AFTER_COMMIT 事务提交后触发(最常用)
AFTER_ROLLBACK 事务回滚后触发
AFTER_COMPLETION 事务完成后触发(无论提交还是回滚)

⚠️ 注意:该注解只有在"发布事件的方法被 @Transactional 标注"时才生效,否则事务阶段不生效。

3.4.4 实现 SmartApplicationListener 接口

  上述几种方式,监听器的执行顺序是不确定的。如果需要指定多个监听器的执行顺序(如先记录日志,再发送通知,最后赠送积分),就需要实现SmartApplicationListener 接口,它是ApplicationListener 的子接口,支持指定监听顺序。

java 复制代码
@Component
public class RegisterLogSmartListener implements SmartApplicationListener {

    /**
     * 指定监听的事件类型
     */
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        // 只监听 UserRegisterEvent 事件
        return UserRegisterEvent.class.isAssignableFrom(eventType);
    }

    /**
     * 指定监听的事件源类型(可选,可省略)
     */
    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        // 只监听 UserService 发布的事件
        return UserService.class.isAssignableFrom(sourceType);
    }

    /**
     * 指定执行顺序(值越小,优先级越高)
     */
    @Override
    public int getOrder() {
        return SmartApplicationListener.super.getOrder();
    }

    /**
     * 事件处理逻辑
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        UserRegisterEvent registerEvent = (UserRegisterEvent) event;
        UserInfo user = registerEvent.getUser();
        log.info("监听器4(顺序1):用户 {} 注册,记录日志...", user.getUsername());
    }
}

四、高级特性实现

4.1 监听器的执行方式

  Spring 事件机制默认是同步执行 的,发布者发布事件后,会阻塞主线程,等待所有监听器执行完成,才会继续执行后续代码。但很多场景下,我们需要异步执行(如发送通知、记录日志,不阻塞核心业务),此时就需要配置异步监听器。通过配置 @EnableAsync 注解和 @Async 注解,实现监听器异步执行,不阻塞主线程。

4.1.1 开启异步执行

  在我们的启动类上增加一个 @EnableAsync 注解,开启异步支持,如下所示:

java 复制代码
@SpringBootApplication
@EnableAsync // 开启异步
public class EventDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(EventDemoApplication.class, args);
    }
}

4.1.2 异步调用

  在事件处理的方法上增加 @Async 异步调用注解

java 复制代码
@Component
public class UserRegisterAsyncListener {
 
    @Async
    @EventListener(UserRegisterEvent.class)
    public void handleUserRegisterAsync(UserRegisterEvent event) {
        try {
            Thread.sleep(2000); // 模拟耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步监听器执行:" + Thread.currentThread().getName());
    }
}

✅ 优势:主线程无需等待监听器执行完成,直接继续执行后续逻辑,提升核心业务响应速度;监听器在子线程中执行,不影响主线程。

4.1.3 自定义线程池

  默认情况下,Spring 会使用默认的线程池(SimpleAsyncTaskExecutor),该线程池每次都会创建新线程,性能较差。实际开发中,建议自定义线程池,优化异步性能。

go 复制代码
// 自定义异步线程池配置
@Configuration
@EnableAsync
public class AsyncConfig {
 
    // 自定义线程池
    @Bean(name = "eventAsyncPool")
    public Executor eventAsyncPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(5);
        // 最大线程数
        executor.setMaxPoolSize(10);
        // 队列容量
        executor.setQueueCapacity(20);
        // 线程空闲时间(秒)
        executor.setKeepAliveSeconds(60);
        // 线程名称前缀
        executor.setThreadNamePrefix("event-async-");
        // 拒绝策略(队列满时,直接抛出异常)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化线程池
        executor.initialize();
        return executor;
    }
}
 
// 监听器指定使用自定义线程池
@Component
public class UserRegisterAsyncListener {
 
    // 指定线程池名称,使用自定义线程池
    @Async("eventAsyncPool")
    @EventListener(UserRegisterEvent.class)
    public void handleUserRegisterAsync(UserRegisterEvent event) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步监听器执行:" + Thread.currentThread().getName());
    }
}

4.2 Spring 内置事件

  在 Spring 框架中,自定义了非常多的自定义事件,让我们更容易的进行拓展。ApplicationContextEvent 是 Spring Context 相关的事件基类,如下图所示:

4.3 SpringBoot 内置事件

  SpringBoot 提供了多个内置事件,SpringBoot 自身的启动流程、自动配置、上下文刷新等功能,都是通过事件机制驱动的。我们也可以监听这些事件,实现自定义逻辑(如项目启动后初始化缓存、启动完成后发送通知)。

内置事件 触发时机 核心用途
ApplicationStartingEvent SpringBoot 应用启动开始时(最早触发,上下文未初始化) 初始化一些启动前的配置(如系统参数初始化)
ApplicationEnvironmentPreparedEvent 环境配置(Environment)准备完成后,上下文未创建 修改环境配置、添加自定义配置源
ApplicationContextInitializedEvent 应用上下文(ApplicationContext)初始化完成,Bean 未加载 上下文相关的初始化操作
ApplicationPreparedEvent 应用上下文准备完成,Bean 已加载,未刷新 Bean 加载后的自定义操作
ContextRefreshedEvent 应用上下文刷新完成(所有 Bean 已初始化) 初始化缓存、加载字典数据、启动定时任务
ApplicationStartedEvent SpringBoot 应用启动完成(所有 Bean 已初始化,服务未就绪) 启动完成后的通知、监控告警
ApplicationReadyEvent 应用启动完成,服务已就绪,可接收请求 发送应用启动成功通知、执行初始化任务
ApplicationFailedEvent 应用启动失败时 记录启动失败日志、发送告警通知、清理资源
相关推荐
小鹿的周先生39 分钟前
Spring-AI-第2篇-ChatClient 实战:使用 DeepSeek 完成第一次 AI 对话
java·人工智能·spring
悲且狂1 小时前
SpringBoot项目改造注意事项(旧项目框架复用)
java·spring boot·后端
敲代码的嘎仔2 小时前
28届后端开发-海康威视日常实习一面(已OC)
java·开发语言·后端·面试·海康威视·实习·大厂
IT_陈寒2 小时前
JavaScript数组排序踩的坑,差点让我加班到凌晨
前端·人工智能·后端
接着奏乐接着舞。2 小时前
【2026】73道Redis 常见面试题与参考答案
数据库·redis·后端·缓存
掘金者阿豪3 小时前
Seedance 2.0/2.5 虚拟素材能跨 Key 共用吗?一次讲清 Asset ID、账号隔离与 SaaS 素材架构
前端·后端
程序员黎剑3 小时前
Spring-Bean生命周期-构造器访问Autowired字段为null
java·后端·spring
泡海椒3 小时前
JQuick-Curl 拦截器实战:统一 Token、日志、请求预处理,让第三方接口调用真正工程化
后端
东方小月3 小时前
一篇文章带你深入拆解Skill的本质与工程实现,让你不再滥用Skill
前端·人工智能·后端