Spring 的事件监听机制

项目中常用消息队列来解耦,但是引入消息队列会增加系统的复杂性。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 表达式过滤事件
  • 泛型事件

原文:Spring 的事件监听机制 | kun's blog
作者: kun’s blog