网站Logo 苏叶的belog

Spring Event

wdadwa
4
2026-02-23

一,什么是 Spring Event?

Spring Event 是 Spring 框架提供的一种事件驱动编程的实现,它基于经典的观察者模式。简单来说,就是当系统中某个对象的状态发生变化时,它会发布一个“事件”,而其他对这个事件感兴趣的对象(监听器)就会收到通知并执行相应的处理。

这种机制的好处是解耦:事件的发布者和处理者不需要直接相互依赖,从而让代码更加灵活、易于扩展。比如:

  • 用户注册成功后,需要发送欢迎邮件、赠送积分、记录日志等。
  • 订单支付完成后,需要更新库存、发送短信通知、生成财务流水等。

如果不使用事件,你可能会在注册服务里依次调用邮件、积分、日志服务,导致代码臃肿且耦合。使用事件后,注册服务只需发布一个“用户注册成功事件”,然后由各自的监听器去处理后续逻辑,注册服务本身可以专注于核心业务。

二,Spring Event 的核心组件

Spring 事件机制主要由三个部分组成:

  1. 事件(Event)
    继承自 ApplicationEvent 的普通 Java 对象,用来封装事件中携带的数据。例如 UserRegisteredEvent 可以包含用户信息。
  2. 监听器(Listener)
    对特定事件做出响应的组件。当事件被发布后,符合条件的监听器会被触发执行。Spring 提供了两种定义监听器的方式:
    • 实现 ApplicationListener 接口
    • 使用 @EventListener 注解(更推荐,更简洁)
  3. 发布器(Publisher)
    负责发布事件的对象,通常是 ApplicationEventPublisher 接口的实现(Spring 容器会自动注入)。你只需要在需要发布事件的地方注入 ApplicationEventPublisher,然后调用 publishEvent 方法即可。

三,基础用法

3.1 准备工作

确保你的项目已经引入了 Spring 框架(Spring Boot 最方便)。如果是 Spring Boot,默认已经包含了所需依赖。

3.2 定义事件类

创建一个事件类,继承 ApplicationEvent(也可以不继承,Spring 4.2+ 支持任意对象作为事件,但通常继承以便于区分)。事件类可以包含你需要传递的数据。

import org.springframework.context.ApplicationEvent;

public class UserRegisteredEvent extends ApplicationEvent {
    private final String username;
    private final String email;

    public UserRegisteredEvent(Object source, String username, String email) {
        super(source);
        this.username = username;
        this.email = email;
    }

    // getters
    public String getUsername() { return username; }
    public String getEmail() { return email; }
}

注意:source 通常是发布事件的对象(比如 UserService),也可以传入 null,但建议传入有意义的来源。

3.3 创建监听器

我们可以用两种方式定义监听器:

方式一 :实现 ApplicationListener 接口

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class EmailNotificationListener implements ApplicationListener<UserRegisteredEvent> {
    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        System.out.println("发送欢迎邮件给:" + event.getEmail());
        // 实际发送邮件的代码...
    }
}

方式二: 使用 @EventListener 注解(推荐)

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class UserEventListener {

    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
        System.out.println("发送欢迎邮件给:" + event.getEmail());
    }

    // 也可以定义多个监听方法处理同一个事件,或者处理不同事件
    @EventListener
    public void handleAnother(UserRegisteredEvent event) {
        System.out.println("记录注册日志:" + event.getUsername());
    }
}

使用注解的方式更加灵活,一个类里可以定义多个监听方法,而且方法名可以自定义。

3.4 发布事件

在需要发布事件的地方(例如用户注册的业务逻辑中),注入 ApplicationEventPublisher 并调用 publishEvent

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final ApplicationEventPublisher eventPublisher;

    public UserService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void registerUser(String username, String email) {
        // 1. 保存用户到数据库
        System.out.println("保存用户:" + username);

        // 2. 发布事件
        UserRegisteredEvent event = new UserRegisteredEvent(this, username, email);
        eventPublisher.publishEvent(event);
    }
}

3.5 测试

创建一个简单的启动类或测试类,调用 registerUser 方法,你会看到监听器中的输出。

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        UserService userService = context.getBean(UserService.class);
        userService.registerUser("张三", "zhangsan@example.com");
    }
}

输出:

保存用户:张三
发送欢迎邮件给:zhangsan@example.com
记录注册日志:张三
发送欢迎邮件给:zhangsan@example.com

四,同步与异步

默认情况下,Spring 事件是同步的:发布事件后,发布者线程会阻塞等待所有监听器执行完毕,然后才继续往下执行。如果监听器中有耗时操作(如发送邮件、调用外部 API),会影响主流程的性能。

解决办法:让监听器异步执行。Spring 提供了简单的异步支持:

  1. 在配置类上添加 @EnableAsync 启用异步功能。
  2. 在监听器方法上添加 @Async 注解。

示例:

@EnableAsync
@SpringBootApplication
public class DemoApplication { ... }
@Component
public class UserEventListener {

    @Async
    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
        // 模拟耗时操作
        Thread.sleep(2000);
        System.out.println("发送欢迎邮件给:" + event.getEmail());
    }
}

此时发布事件的方法会立即返回,监听器在独立的线程中执行。注意:异步需要配置线程池,否则会使用默认的 SimpleAsyncTaskExecutor(不推荐生产环境)。建议自定义线程池:

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        return executor;
    }
}

这里加了配置后,就不需要在启动器类里面加了

五,事务事件监听器

5.1 什么是事务事件监听器?

事务事件监听器(@TransactionalEventListener)是 Spring 4.2 引入的一个特性,它是对普通事件监听器的扩展。它允许你将事件的监听绑定到当前事务的某个特定阶段,比如事务提交前、提交后、回滚后等。

简单来说,就是“当事务进行到某个时刻,再执行这个监听器”。

5.2 什么需要它?

来看一个典型场景:用户注册成功后,需要保存用户到数据库,然后发送一封欢迎邮件。

如果按照之前学的普通事件监听器,代码可能这样:

@Transactional
public void register(User user) {
    userDao.save(user);                     // 数据库操作
    eventPublisher.publishEvent(new UserRegisteredEvent(user));
}

监听器:

@EventListener
public void sendEmail(UserRegisteredEvent event) {
    // 发送邮件
}

这里有个隐患:邮件发送是在事务提交之前执行的(默认同步)。如果邮件发送成功,但随后事务提交失败(例如数据库约束冲突),用户实际上没有注册成功,但邮件已经发出去了 —— 这就产生了数据不一致。

如果我们改成异步发送邮件,虽然主事务不受影响,但同样可能发生事务回滚后邮件依然发送的情况。

我们希望的是:只有当数据库事务真正提交成功后,才去发送邮件。如果事务回滚了,就不发邮件。这正是事务事件监听器要解决的问题。

5.3 @TransactionalEventListener 详解

5.3.1 核心注解属性

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {

    /**
     * 指定监听器在事务的哪个阶段执行
     */
    TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;

    /**
     * 如果没有事务时,是否仍然执行监听器(默认false,即没有事务时不执行)
     */
    boolean fallbackExecution() default false;

    /**
     * 支持 SpEL 条件表达式,与 @EventListener 的 condition 类似
     */
    String condition() default "";
}

5.3.2 事务阶段(TransactionPhase)

phase 可以取以下四个值:

阶段说明适用场景
TransactionPhase.BEFORE_COMMIT事务提交之前需要在事务提交前做一些准备,比如二次校验,如果校验失败还能回滚事务
TransactionPhase.AFTER_COMMIT(默认)事务提交成功后最常见,如发邮件、发消息、记录日志,要求事务成功后执行
TransactionPhase.AFTER_ROLLBACK事务回滚后用于回滚后的补偿操作,如记录失败日志、告警
TransactionPhase.AFTER_COMPLETION事务完成后(无论提交还是回滚)需要清理资源等,无论成功失败都要执行

5.4 示例

还是用户注册的例子,我们用事务事件监听器改造:

5.4.1 定义事件

public class UserRegisteredEvent extends ApplicationEvent {
    private final User user;
    public UserRegisteredEvent(Object source, User user) {
        super(source);
        this.user = user;
    }
    public User getUser() { return user; }
}

5.4.2 发布事件(在事务方法内)

@Service
public class UserService {
    @Autowired
    private ApplicationEventPublisher publisher;
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void register(User user) {
        userRepository.save(user);  // 数据库操作
        // 发布事件,此时事务还未提交
        publisher.publishEvent(new UserRegisteredEvent(this, user));
    }
}

5.4.3 创建事务事件监听器

@Component
public class UserEventListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleAfterCommit(UserRegisteredEvent event) {
        User user = event.getUser();
        System.out.println("事务已提交,发送邮件给:" + user.getEmail());
        // 实际发邮件逻辑
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleAfterRollback(UserRegisteredEvent event) {
        User user = event.getUser();
        System.out.println("事务回滚了,记录失败日志:" + user.getUsername());
    }

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void handleBeforeCommit(UserRegisteredEvent event) {
        // 可以在提交前做一些额外校验
        System.out.println("即将提交事务,检查数据...");
    }
}

这样,无论事务提交还是回滚,监听器都会在正确的时机执行。

5.5 工作原理(简单理解)

Spring 的事务事件监听器是基于 ** 事务同步(Transaction Synchronization)** 机制实现的。大致流程:

  1. 当你在事务方法中发布事件时,Spring 不会立即调用监听器,而是将事件和监听器信息注册到当前事务的同步管理中。
  2. Spring 会在事务的不同阶段(提交前、提交后、回滚后)触发对应的同步回调。
  3. 在回调中,Spring 会取出对应阶段的事件,并执行监听器。

因此,事务事件监听器必须依赖于一个活跃的事务。如果发布事件时没有事务,默认监听器不会执行(除非设置 fallbackExecution = true)。

5.6 注意事项

  1. 事件必须在事务方法内发布

    这是最关键的。如果你的 publishEvent 调用不在事务方法中,那么事务事件监听器不会触发(除非设置 fallbackExecution = true)。

  2. 事务管理器的配置

    要使用事务事件,需要确保 Spring 配置了事务管理器(如 PlatformTransactionManagerReactiveTransactionManager),并且启用了事务管理(@EnableTransactionManagement)。Spring Boot 通常会自动配置

  3. 同步 vs 异步

    事务事件监听器默认也是同步执行的,即它们会在事务线程中执行(阻塞事务的后续流程,比如 AFTER_COMMIT 在事务提交后、返回前执行)。如果监听器是耗时操作,建议结合 @Async 异步执行,但要注意:异步后,监听器执行不再受原事务控制,但因为是事务提交后才异步执行,所以数据一致性已经保证了。

  4. 回滚情况下的监听器

    如果事务最终回滚了,那么 AFTER_COMMIT 监听器不会执行,但 AFTER_ROLLBACKAFTER_COMPLETION 会执行。这正好满足业务需求。

  5. 多个监听器的执行顺序

    同一阶段内的多个监听器,默认顺序不确定。如果需要控制顺序,可以使用 @Order 注解。

  6. fallbackExecution 的作用

    当事件发布不在事务中时,如果设置 fallbackExecution = true,监听器会立即执行(相当于普通 @EventListener)。这可以应对某些无事务的情况。

  7. 事件发布后修改数据的问题

 在 `BEFORE_COMMIT` 监听器中,你仍然可以修改数据库(因为事务还没提交),这些修改会包含在当前事务中。在 `AFTER_COMMIT` 监听器中,事务已提交,不能再进行数据库写操作(除非开启新事务),但可以读数据。

六,注意事项

  • 事件类:建议继承 ApplicationEvent 虽然不是强制,但这样更规范,也能利用一些框架特性(如事务事件)。
  • 监听器异常:默认同步执行时,如果一个监听器抛出异常,会影响其他监听器(后续监听器可能不会执行)。异步执行时,异常不会影响主线程,但需要自行处理。
  • 避免循环依赖:如果监听器又去发布事件,可能导致无限循环,要小心设计。
  • 事件继承:监听器可以监听父类事件,所有子类事件也会被触发。例如监听 ApplicationEvent 会收到所有事件。
  • 条件过滤@EventListener 支持 SpEL 表达式 condition,可以根据事件属性决定是否执行。

示例:只对特定用户名的监听:

@EventListener(condition = "#event.username == 'admin'")
public void handleAdmin(UserRegisteredEvent event) { ... }
动物装饰