项目中常用消息队列来解耦,但是引入消息队列会增加系统的复杂性。Spring 的事件监听机制也可以用来实现解耦,而且不需要引入额外的组件,很适合在并发不高的业务中使用。
Spring Events
实现原理
Spring 的事件监听机制有以下三个部分组成:
- 事件发布者:调用 ApplicationEventPublisher.publishEvent(ApplicationEvent event)
- 事件:继承 ApplicationEvent
- 事件监听者:继承 ApplicationListener
Spring 的事件监听机制是一个标准的观察者模式。Spring 在启动的时候会把继承了 ApplicationListener 接口的 listenner 都注册到 Spring 上下文中去。当事件发布者调用 ApplicationContext (Spring 上下文都继承了 ApplicationEventPublisher 接口)的 publishEvent() 方法发布事件时,会同步的遍历监听此事件的监听器,执行监听器中的 onApplicationEvent() 方法。
代码示例
假如我们要监听登陆接口,用户登陆时我们需要执行两个操作:
- 记录登陆日志
- 发送登陆消息
Tip: 在监听器上加上@Order
注解,可以对监听器的执行顺序进行调整。
先定义登陆用户登陆事件:
public class UserLoginEvent extends ApplicationEvent {
private String userName;
public UserLoginEvent(String userName) {
super(userName);
this.userName = userName;
}
@Override
public String toString() {
return "userName:" + userName;
}
}
登陆接口代码:
@Autowired
private ApplicationEventPublisher publisher;
@PostMapping(value = "/login")
public String login(String userName) {
//todo 登录逻辑
//发布登录事件
System.out.println(String.format("login api,username is :%s,current thread name :%s",userName,Thread.currentThread().getName()));
publisher.publishEvent(new UserLoginEvent(userName));
return "ok";
}
定义记录登陆日志的监听器
@Component
@Order(100)
public class LoginLogListener implements ApplicationListener<UserLoginEvent> {
//@Async
@Override
public void onApplicationEvent(UserLoginEvent event) {
String threadName = Thread.currentThread()
.getName();
System.out.println(String.format("login log listener accept event:%s,current thread name :%s", event.toString(), threadName));
//throw new RuntimeException("just test");
}
}
定义发送登陆消息的监听器
@Component
@Order(110)
public class LoginMsgListener implements ApplicationListener<UserLoginEvent> {
@Override
public void onApplicationEvent(UserLoginEvent event) {
String threadName = Thread.currentThread()
.getName();
System.out.println(String.format("login message listener accept event:%s,current thread name :%s", event.toString(), threadName));
// throw new RuntimeException("just test");
}
}
运行结果如下:
login api,username is :xhh,current thread name :http-nio-8080-exec-7
login log listener accept event:userName:xhh,current thread name :http-nio-8080-exec-7
login message listener accept event:userName:xhh,current thread name :http-nio-8080-exec-7
监听器按照 @Order
注解的顺序执行,且与业务逻辑在同一线程下执行的。
注意: 因为是监听逻辑同步执行的,所以如果监听逻辑出现异常会影响主线程的结果返回。如果想异步执行,可以在 onApplicationEvent() 方法上加 @Async
注解。
基于注解的监听方法
从 Spring 4.2 开始提供了基于注解的监听器实现。不在需要定义专门的监听器类,只需要在 Bean 的方法上增加 @EventLitenner 注解即可在对应 event 被发布的时候触发执行对应的方法。
增加两个基于注解的监听方法:
//@Async
@Order(105)
@EventListener(classes = UserLoginEvent.class)
public void listener3(UserLoginEvent event) {
String threadName = Thread.currentThread()
.getName();
System.out.println(String.format("login log listener3 accept event:%s,current thread name :%s", event.toString(), threadName));
//throw new RuntimeException("just test");
}
//@Async
@Order(115)
@EventListener(classes = UserLoginEvent.class)
public void listener4(UserLoginEvent event) {
String threadName = Thread.currentThread()
.getName();
System.out.println(String.format("login log listener4 accept event:%s,current thread name :%s", event.toString(), threadName));
//throw new RuntimeException("just test");
}
执行结果如下:
login api,username is :xhh,current thread name :http-nio-8080-exec-4
login log listener accept event:userName:xhh,current thread name :http-nio-8080-exec-4
login log listener3 accept event:userName:xhh,current thread name :http-nio-8080-exec-4
login message listener accept event:userName:xhh,current thread name :http-nio-8080-exec-4
login log listener4 accept event:userName:xhh,current thread name :http-nio-8080-exec-4
监听类和注解定义的监听方法按 @Order 注解顺序执行,且都是在同一个线程内。
@TransactionalEventListener
@TransactionalEventListener
是一个事务监听注解,用法与 @EventListener
类似。除 classes 属性外需额外指定一个 phase 值,phase 是个枚举,代表的是在事务的什么阶段进行事件发布。
TransactionPhase 枚举如下:
public enum TransactionPhase {
BEFORE_COMMIT,
AFTER_COMMIT,
AFTER_ROLLBACK,
AFTER_COMPLETION;
private TransactionPhase() {
}
}
总结
以上只是 Spring 事件发布机制的简单使用,更多内容可以参考 Spring 文档:Standard and Custom Events 。包括:
- 侦听多个事件
- 用 SpEL 表达式过滤事件
- 泛型事件
原文:https://blog.fengxiuge.top/2021/2021-07-08-spring-events.html
作者: kun’s blog