Spring 使用事件监听来解耦代码
应用场景
我就不先放一大堆概念介绍了,先来个示例,一步一步理解。
如有个需求在用户注册成功后给其注册邮箱发送一封邮件,你可能会这样写代码:
public class UserService {
@Resource
private MailService mailService;
public void register(User user) {
// 1. 保存用户信息
userRepository.save(user);
// 2. 发送邮件
mailService.sendMail(user.getEmail(), "注册成功", "欢迎注册");
}
}但是这样有一些问题,UserService 依赖了 MailService,且如果以后要增加其他的操作,比如发送短信,那么 UserService 就要再次修改,修改为:
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:
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 来发布一个事件:
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,如:
@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());
}
}@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 为例:
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());
}
}如果你需要一个方法监听多个事件,或你想要一个不带参数的方法,也可以在注解中指定事件类型,如:
@EventListener({AEvent.class, BEvent.class})
public void onEvent() {
// doSomething
}还可以使用注解中的 condition 属性写 SpEL 表达式来过滤事件,如:
@EventListener(condition = "userRegisterEvent.user.phone.startsWith(\"138\")")
public void onUserRegisterEvent(UserRegisterEvent userRegisterEvent) {
// doSomething
}这样我们就可以只处理手机号以 138 开头的用户注册事件。
如果你想要在一个事件结束后触发另一个或多个其他事件,可以修改方法签名,将其返回值改为要发布的事件,如:
@EventListener
public UserRegisterAfterEvent onUserRegisterEvent(UserRegisterEvent event) {
// doSomething
return new UserRegisterAfterEvent(this, user);
}需要发布多个事件的话,可以通过返回 Collection 或数组来实现。
事件异步监听器
如果你想要一个特定的监听器以异步的方式处理事件,可以在其方法上增加 @Async,如:
@Async
@EventListener
public void onUserRegisterEvent(UserRegisterEvent userRegisterEvent) {
// doSomething
}但是这样有几个限制:
- 如果一个异步的事件监听器发生异常,他不会被传播给调用者。
- 异步事件监听器不能通过 return 事件来发布其他事件,但可以通过
ApplicationEventPublisher手动发布。 - 如果事件发布者在是事务性的,异步事件监听器不会在同一个事务上下文中执行。
事件监听顺序
如果你有多个监听器监听同一个事件,你可能会想要控制它们的执行顺序,可以使用 @Order 注解,如:
@Order(10)
@EventListener
public void onUserRegisterEvent(UserRegisterEvent userRegisterEvent) {
// doSomething
}每个 Spring Bean 默认的 Order 值是 Integer.MAX_VALUE,你可以使用 @Order 注解来指定一个值,值越小,优先级越高。
事务监听的泛型限制
你可以使用泛型来限制事件监听器只监听特定类型的事件,如有一个 EntityCreatedEvent<T>,你想要只监听 EntityCreatedEvent<User>,可以使用:
@EventListener
public void onUserCreated(EntityCreatedEvent<User> event) {
// ...
}不过由于 Java 的泛型擦除,你需要这样创建 class UserCreatedEvent extends EntityCreatedEvent<User> { … },然后发布事件时使用 UserCreatedEvent,才能被上面的监听器监听到。
但如果每个泛型事件都需要创建一个新的类,这样会显得很麻烦,所以 Spring 提供了一个 ResolvableType 类来解决这个问题,如:
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 多了一些属性,如:
@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 枚举:
public enum TransactionPhase {
// 指定目标方法在事务commit之前执行
BEFORE_COMMIT,
// 指定目标方法在事务commit之后执行
AFTER_COMMIT,
// 指定目标方法在事务rollback之后执行
AFTER_ROLLBACK,
// 指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了
AFTER_COMPLETION
}我们使用 @EventListener 注解时,效果等同于 BEFORE_COMMIT,也就是执行完所有事件才提交事务。现在我们可以自己控制事件的执行时机了。
但是因为它是基于事务的,所以如果没有事务在运行,事件是不会被处理的,除非你设置了 @TransactionalEventListener 的 fallbackExecution 属性为 true,即表示没有事务在运行,也会执行事件。否则事件将会被丢弃,不会执行。