Loading... ## 应用场景 我就不先放一大堆概念介绍了,先来个示例,一步一步理解。 如有个需求`在用户注册成功后给其注册邮箱发送一封邮件`,你可能会这样写代码: ```java public class UserService { @Resource private MailService mailService; public void register(User user) { // 1. 保存用户信息 userRepository.save(user); // 2. 发送邮件 mailService.sendMail(user.getEmail(), "注册成功", "欢迎注册"); } } ``` 但是这样有一些问题,`UserService` 依赖了 `MailService`,且如果以后要增加其他的操作,比如发送短信,那么 `UserService` 就要再次修改,修改为: ```java public class UserService { @Resource private MailService mailService; @Resource private SmsService smsService; public void register(User user) { // 1. 保存用户信息 userRepository.save(user); // 2. 发送邮件 mailService.sendMail(user.getEmail(), "注册成功", "欢迎注册"); // 3. 发送短信 smsService.sendSms(user.getPhone(), "注册成功"); } } ``` 这样代码就都耦合在一起了,不过我们可以使用 `Spring Event` 来解耦代码,简单的说就是不再直接调用 `MailService` 和 `SmsService`,而是发布一个 `用户注册` 事件,`MailService` 和 `SmsService` 监听这个事件,当事件发生时,执行相应的操作。 首先自定义定义一个事件类 `UserRegisterEvent` 表示,需要继承 `ApplicationEvent`: ```java public class UserRegisterEvent extends ApplicationEvent { private final User user; public UserRegisterEvent(Object source, User user) { super(source); this.user = user; } public User getUser() { return user; } } ``` 我们自定义这个事件中,包含一个 `User` 对象,用户存储注册的用户信息,其中还有个需要传递个父类 `ApplicationEvent` 构造函数的 `source` 参数,这个参数表示事件的来源或事件相关的对象,一会我们会介绍。 然后在 `UserService` 中使用 `ApplicationEventPublisher#publishEvent` 来发布一个事件: ```java public class UserService { @Resource private ApplicationEventPublisher applicationEventPublisher; public void register(User user) { // 1. 保存用户信息 userRepository.save(user); // 2. 发布事件 applicationEventPublisher.publishEvent(new UserRegisterEvent(this, user)); } } ``` 这里我们将 `source` 参数传递为 `this`,表示事件的来源是 `UserService`。 要接收发生的事件,可以创建一个实现 `ApplicationListener` 接口的类,并注册为 `Spring Bean`,如: ```java @Slf4j @Service public class MailService implements ApplicationListener<UserRegisterEvent> { @Override public void onApplicationEvent(UserRegisterEvent event) { User user = event.getUser(); log.info("MailService: send register mail to {}", user.getEmail()); } } ``` ```java @Slf4j @Service public class SmsService implements ApplicationListener<UserRegisterEvent> { @Override public void onApplicationEvent(UserRegisterEvent event) { User user = event.getUser(); log.info("SmsService: send register sms to {}", user.getPhone()); } } ``` `ApplicationListener` 的泛型表示了要接收的事件类型。现在调用 `UserService#register` 方法,其中发布了 `UserRegisterEvent` 事件,因为 `MailService` 和 `SmsService` 监听了这个事件,也会自动执行其 `onApplicationEvent` 方法中内容。 对于一个事件可以创建任意数量个监听者,但需要注意默认情况下事件监听器会同步接收事件,也就是会阻塞 `publishEvent` 方法,直到所有事件监听器都执行完毕。这样做有一个好处是当事件发布者需要事务支持时,可以保证所有事件监听器在同一个事务上下文中。 ## 基于注解的事件监听器 前面我们的事件监听器都是通过实现 `ApplicationListener` 接口来实现的,但是如果一个类要监听多个事件(如再监听用户登录事件),现有的方法就不太适用了,这时可以改为使用 `@EventListener` 注解来监听事件。 以 `MailService` 为例: ```java import org.springframework.context.event.EventListener; @Slf4j @Service public class MailService { @EventListener public void onUserRegisterEvent(UserRegisterEvent event) { User user = event.getUser(); log.info("MailService: send register mail to {}", user.getEmail()); } } ``` 如果你需要一个方法监听多个事件,或你想要一个不带参数的方法,也可以在注解中指定事件类型,如: ```java @EventListener({AEvent.class, BEvent.class}) public void onEvent() { // doSomething } ``` 还可以使用注解中的 `condition` 属性写 `SpEL` 表达式来过滤事件,如: ```java @EventListener(condition = "userRegisterEvent.user.phone.startsWith(\"138\")") public void onUserRegisterEvent(UserRegisterEvent userRegisterEvent) { // doSomething } ``` 这样我们就可以只处理手机号以 `138` 开头的用户注册事件。 如果你想要在一个事件结束后触发另一个或多个其他事件,可以修改方法签名,将其返回值改为要发布的事件,如: ```java @EventListener public UserRegisterAfterEvent onUserRegisterEvent(UserRegisterEvent event) { // doSomething return new UserRegisterAfterEvent(this, user); } ``` 需要发布多个事件的话,可以通过返回 `Collection` 或数组来实现。 ## 事件异步监听器 如果你想要一个特定的监听器以异步的方式处理事件,可以在其方法上增加 `@Async`,如: ```java @Async @EventListener public void onUserRegisterEvent(UserRegisterEvent userRegisterEvent) { // doSomething } ``` 但是这样有几个限制: - 如果一个异步的事件监听器发生异常,他不会被传播给调用者。 - 异步事件监听器不能通过 return 事件来发布其他事件,但可以通过 `ApplicationEventPublisher` 手动发布。 - 如果事件发布者在是事务性的,异步事件监听器不会在同一个事务上下文中执行。 ## 事件监听顺序 如果你有多个监听器监听同一个事件,你可能会想要控制它们的执行顺序,可以使用 `@Order` 注解,如: ```java @Order(10) @EventListener public void onUserRegisterEvent(UserRegisterEvent userRegisterEvent) { // doSomething } ``` 每个 `Spring Bean` 默认的 `Order` 值是 `Integer.MAX_VALUE`,你可以使用 `@Order` 注解来指定一个值,值越小,优先级越高。 ## 事务监听的泛型限制 你可以使用泛型来限制事件监听器只监听特定类型的事件,如有一个 `EntityCreatedEvent<T>`,你想要只监听 `EntityCreatedEvent<User>`,可以使用: ```java @EventListener public void onUserCreated(EntityCreatedEvent<User> event) { // ... } ``` 不过由于 Java 的泛型擦除,你需要这样创建 `class UserCreatedEvent extends EntityCreatedEvent<User> { … }`,然后发布事件时使用 `UserCreatedEvent`,才能被上面的监听器监听到。 但如果每个泛型事件都需要创建一个新的类,这样会显得很麻烦,所以 `Spring` 提供了一个 `ResolvableType` 类来解决这个问题,如: ```java public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { public EntityCreatedEvent(T entity) { super(entity); } @Override public ResolvableType getResolvableType() { return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource())); } } ``` ## 基于事务不同阶段的事件 前面的例子中,我们的事件和调用方在同一个事务中执行,但是有时我们想在事务提交后执行一些操作,这时可以使用 `@TransactionalEventListener` 注解,它相较于 `@EventListener` 多了一些属性,如: ```java @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @EventListener public @interface TransactionalEventListener { /** * Phase to bind the handling of an event to. * <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}. * <p>If no transaction is in progress, the event is not processed at * all unless {@link #fallbackExecution} has been enabled explicitly. */ TransactionPhase phase() default TransactionPhase.AFTER_COMMIT; /** * Whether the event should be handled if no transaction is running. */ boolean fallbackExecution() default false; 省略其他和 @EventListener 一样的属性...... } ``` 先来看看 `TransactionPhase` 枚举: ```java public enum TransactionPhase { // 指定目标方法在事务commit之前执行 BEFORE_COMMIT, // 指定目标方法在事务commit之后执行 AFTER_COMMIT, // 指定目标方法在事务rollback之后执行 AFTER_ROLLBACK, // 指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了 AFTER_COMPLETION } ``` 我们使用 `@EventListener` 注解时,效果等同于 `BEFORE_COMMIT`,也就是执行完所有事件才提交事务。现在我们可以自己控制事件的执行时机了。 但是因为它是基于事务的,所以如果没有事务在运行,事件是不会被处理的,除非你设置了 `@TransactionalEventListener` 的 `fallbackExecution` 属性为 `true`,即表示没有事务在运行,也会执行事件。否则事件将会被丢弃,不会执行。 ## 参考资料 - [Spring Docs - Standard and Custom Events](https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events) - [Spring解决泛型擦除的思路不错,现在它是我的了。](https://zhuanlan.zhihu.com/p/677880235) - [@TransactionalEventListener的使用和实现原理](https://blog.csdn.net/qq_41378597/article/details/105748703) - [TransactionalEventListener使用场景以及实现原理,最后要躲个大坑](https://juejin.cn/post/7011685509567086606) 最后修改:2024 年 02 月 27 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请我喝杯咖啡吧。