目录
为什么需要使用事件这种模式
事件模式中的几个概念
使用事件模式实现上面用户注册的业务
事件对象
事件监听器
事件广播器
事件广播默认实现
自定义用户注册成功事件类
用户注册服务
下面我们使用spring来将上面的对象组装起来
测试用例模拟用户注册
添加注册成功发送邮件功能
小结
Spring中实现事件模式
事件相关的几个类
硬编码的方式使用spring事件3步骤
案例
小结
面向接口的方式
面向@EventListener注解方式
监听器支持排序功能
通过接口实现监听器的情况
通过@EventListener注解实现监听器的情况
监听器异步模式
事件使用的建议
先来看一个业务场景:
产品经理:路人,这两天你帮我实现一个注册的功能 我:注册功能比较简单,将用户信息入库就可以了,伪代码如下:
public void registerUser(UserModel user){
//插入用户信息到db,完成注册
this.insertUser(user);
}
过了几天,产品经理:路人,注册成功之后,给用户发送一封注册成功的邮件 我:修改了一下上面注册代码,如下:
public void registerUser(UserModel user){
//插入用户信息到db,完成注册
this.insertUser(user);
//发送邮件
this.sendEmailToUser(user);
}
由于修改了注册接口,所以所有调用这个方法的地方都需要重新测试一遍,让测试的兄弟们帮忙跑了一 遍。 又过了几天,产品经理:路人,注册成功之后,给用户发一下优惠券 我:好的,又调整了一下代码:
public void registerUser(UserModel user){
//插入用户信息到db,完成注册
this.insertUser(user);
//发送邮件
this.sendEmailToUser(user);
//发送优惠券
this.sendCouponToUser(user);
}
我:测试的兄弟们,辛苦一下大家,注册接口又修改了,帮忙再过一遍。 过了一段时间,公司效益太好,产品经理:路人,注册的时候,取消给用户发送优惠券的功能。 我:又跑去调整了一下上面代码,将发送优惠券的功能干掉了,如下
public void registerUser(UserModel user){
//插入用户信息到db,完成注册
this.insertUser(user);
//发送邮件
this.sendEmailToUser(user);
}
由于调整了代码,而注册功能又属于核心业务,所以需要让测试再次帮忙过一遍,又要麻烦测试来一遍了。
突然有一天,产品经理:路人,注册接口怎么这么慢啊,并且还经常失败?你这让公司要损失多少用户 啊 我:赶紧跑去查看了一下运行日志,发现注册的时候给用户发送邮件不稳定,依赖于第三方邮件服务 器,耗时比较长,并且容易失败。 跑去给产品经理说:由于邮件服务器不稳定的原因,导致注册不稳定。 产品经理:邮件你可以不发,但是你得确保注册功能必须可以用啊。 我想了想,将上面代码改成了下面这样,发送邮件放在了子线程中执行:
public void registerUser(UserModel user){
//插入用户信息到db,完成注册
this.insertUser(user);
//发送邮件,放在子线程中执行,邮件的发送结果对注册逻辑不会有干扰作用
new Thread(()->{
this.sendEmailToUser(user);
}).start();
}
又过了几天,产品经理又跑来了说:路人,最近效益不好,需要刺激用户消费,注册的时候继续发送优 惠券。 我:倒,这是玩我么,反反复复让我调整注册的代码,让我改还好,让测试也反反复复来回搞,这是要 玩死我们啊。 花了点时间,好好复盘整理了一下:发现问题不在于产品经理,从业务上来看,产品提的这些需求都是 需求合理的,而结果代码反复调整、测试反复测试,以及一些次要的功能导致注册接口不稳定,这些问 题归根到底,主要还是我的设计不合理导致的,将注册功能中的一些次要的功能耦合到注册的方法中 了,并且这些功能可能会经常调整,导致了注册接口的不稳定性。 其实上面代码可以这么做:
找3个人:注册器、路人A、路人B。
注册器:负责将用户信息落库,落库成功之后,喊一声:用户XXX注册成功了。
路人A和路人B,竖起耳朵,当听到有人喊:XXX注册成功 的声音之后,立即行动做出下面反应: 路人A:负责给XXX发送一封注册邮件 路人B:负责给XXX发送优惠券
我们来看一下: 注册器只负责将用户信息落库,及广播一条用户注册成功的消息。 A和B相当于一个监听者,只负责监听用户注册成功的消息,当听到有这个消息产生的时候,A和B就去做 自己的事情。
这里面注册器是感知不到A/B存在的,A和B也不用感知注册器的存在,A/B只用关注是否有人广播: XXX 注册成功了 的消息,当AB听到有人广播注册成功的消息,他们才做出反应,其他时间闲着休息。
这种方式就非常好: 当不想给用户发送优惠券的时候,只需要将B去掉就行了,此时基本上也不用测试,注册一下B的代码就行了。 若注册成功之后需要更多业务,比如还需要给用户增加积分,只需新增一个监听者C,监听到注册成功 消息后,负责给用户添加积分,此时根本不用去调整注册的代码,开发者和测试人员只需要确保监听者 C中的正确性就可以了。
上面这种模式就是事件模式。
事件源:事件的触发者,比如上面的注册器就是事件源。
事件:描述发生了什么事情的对象,比如上面的:xxx注册成功的事件
事件监听器:监听到事件发生的时候,做一些处理,比如上面的:路人A、路人B
表示所有事件的父类,内部有个source字段,表示事件源;我们自定义的事件需要继承这个类。
public abstract class AbstractEvent {
private Object source;
public AbstractEvent(Object source) {
this.source = source;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
}
我们使用一个接口来表示事件监听器,是个泛型接口,后面的类型 E 表示当前监听器需要监听的 事件类型,此接口中只有一个方法,用来实现处理事件的业务;其定义的监听器需要实现这个接 口。
public interface EventListener {
void onEvent(E e);
}
负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)
负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)
public interface EventMulticaster {
/**
* 广播事件给所有的监听器,对该事件感兴趣的监听器会处理该事件
*
* @param event
*/
void multicastEvent(AbstractEvent event);
/**
* 添加一个事件监听器(监听器中包含了监听器中能够处理的事件)
*
* @param listener 需要添加监听器
*/
void addEventListener(EventListener> listener);
/**
* 将事件监听器移除
*
* @param listener 需要移除的监听器
*/
void removeEventListener(EventListener> listener);
}
/**
* 事件广播器简单实现
*/
public class SimpleEventMulticaster implements EventMulticaster {
private Map, List> eventObjectEventListenerMap = new HashMap<>();
@Override
public void multicastEvent(AbstractEvent event) {
List eventListeners = this.eventObjectEventListenerMap.get(event.getClass());
if (eventListeners != null) {
for (EventListener eventListener : eventListeners) {
eventListener.onEvent(event);
}
}
}
@Override
public void addEventListener(EventListener> listener) {
Class> eventType = this.getEventType(listener);
List eventListeners = this.eventObjectEventListenerMap.get(eventType);
if (eventListeners == null) {
eventListeners = new ArrayList<>();
this.eventObjectEventListenerMap.put(eventType, eventListeners);
}
eventListeners.add(listener);
}
@Override
public void removeEventListener(EventListener> listener) {
Class> eventType = this.getEventType(listener);
List eventListeners =
this.eventObjectEventListenerMap.get(eventType);
if (eventListeners != null) {
eventListeners.remove(listener);
}
}
/**
* 获取事件监听器需要监听的事件类型
*
* @param listener
* @return
*/
protected Class> getEventType(EventListener listener) {
ParameterizedType parameterizedType = (ParameterizedType) listener.getClass().getGenericInterfaces()[0];
Type eventType = parameterizedType.getActualTypeArguments()[0];
return (Class>) eventType;
}
}
上面3个类支撑了整个事件模型,下面我们使用上面三个类来实现注册的功能,目标是:高内聚低耦合, 让注册逻辑方便扩展。
/**
* 用户注册成功事件
*/
public class UserRegisterSuccessEvent extends AbstractEvent {
//用户名
private String userName;
/**
* 创建用户注册成功事件对象
*
* @param source 事件源
* @param userName 当前注册的用户名
*/
public UserRegisterSuccessEvent(Object source, String userName) {
super(source);
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
/**
* 用户注册服务
*/
public class UserRegisterService {
//事件发布者
private EventMulticaster eventMulticaster; //@0
/**
* 注册用户
*
* @param userName 用户名
*/
public void registerUser(String userName) { //@1
//用户注册(将用户信息入库等操作)
System.out.println(String.format("用户【%s】注册成功", userName)); //@2
//广播事件
this.eventMulticaster.multicastEvent(new UserRegisterSuccessEvent(this, userName)); //@3
}
public EventMulticaster getEventMulticaster() {
return eventMulticaster;
}
public void setEventMulticaster(EventMulticaster eventMulticaster) {
this.eventMulticaster = eventMulticaster;
}
}
@0:事件发布者
@1:registerUser这个方法负责用户注册,内部主要做了2个事情
@2:模拟将用户信息落库
@3:使用事件发布者eventPublisher发布用户注册成功的消息:
@Configuration
@ComponentScan
public class MainConfig {
/**
* 注册一个bean:事件发布者
*
* @param eventListeners
* @return
*/
@Bean
@Autowired(required = false)
public EventMulticaster eventMulticaster(List eventListeners) { //@1
EventMulticaster eventPublisher = new SimpleEventMulticaster();
if (eventListeners != null) {
eventListeners.forEach(eventPublisher::addEventListener);
}
return eventPublisher;
}
/**
* 注册一个bean:用户注册服务
*
* @param eventMulticaster
* @return
*/
@Bean
public UserRegisterService userRegisterService(EventMulticaster eventMulticaster) { //@2
UserRegisterService userRegisterService = new UserRegisterService();
userRegisterService.setEventMulticaster(eventMulticaster);
return userRegisterService;
}
}
上面有2个方法,负责向spring容器中注册2个bean。
@1:向spring容器中注册了一个bean: 事件发布者 ,方法传入了 EventListener 类型的List,这 个地方会将容器中所有的事件监听器注入进来,丢到 EventMulticaster 中。
@2:向spring容器中注册了一个bean: 用户注册服务
public class EventTest {
@Test
public void test0() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
//获取用户注册服务
UserRegisterService userRegisterService = context.getBean(UserRegisterService.class);
//模拟用户注册
userRegisterService.registerUser("测试用户1");
}
}
运行输出:
用户【测试用户1】注册成功
下面添加一个注册成功发送邮件的功能,只需要自定义一个监听用户注册成功事件的监听器就可以了, 其他代码不需要任何改动,如下
@Component
public class SendEmailOnUserRegisterSuccessListener implements EventListener {
@Override
public void onEvent(UserRegisterSuccessEvent event) {
System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
}
}
运行输出:
用户【测试用户1】注册成功
给用户【测试用户1】发送注册成功邮件!
上面将注册的主要逻辑(用户信息落库)和次要的业务逻辑(发送邮件)通过事件的方式解耦了。次要的业务做成了可插拔的方式,比如不想发送邮件了,只需要将邮件监听器上面的 @Component 注释就可 以了,非常方便扩展。 上面用到的和事件相关的几个类,都是我们自己实现的,其实这些功能在spring中已经帮我们实现好 了,用起来更容易一些,下面带大家来体验一下。
Spring中事件相关的几个类需要先了解一下,下面来个表格,将spring中事件相关的类和我们上面自定 义的类做个对比,方便大家理解
步骤1:定义事件
自定义事件,需要继承 ApplicationEvent 类
步骤2:定义监听器
自定义事件监听器,需要实现 ApplicationListener 接口,这个接口有个方法 onApplicationEvent 需要实现,用来处理感兴趣的事件。
@FunctionalInterface
public interface ApplicationListener extends
EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
步骤3:创建事件广播器
创建事件广播器 ApplicationEventMulticaster ,这是个接口,你可以自己实现这个接口,也可以直 接使用系统给我们提供的 SimpleApplicationEventMulticaster ,如下:
ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
步骤4:向广播器中注册事件监听器
将事件监听器注册到广播器 ApplicationEventMulticaster 中,如:
ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());
步骤5:通过广播器发布事件
广播事件,调用 ApplicationEventMulticaster#multicastEvent方法,此时广播器中对这 个事件感兴趣的监听器会处理这个事件。
applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));
实现功能:电商中订单创建成功之后,给下单人发送一封邮件,发送邮件的功能放在监听器中实现。
事件类:订单创建成功事件
/**
* 订单创建事件
*/
public class OrderCreateEvent extends ApplicationEvent {
//订单id
private Long orderId;
/**
* @param source 事件源
* @param orderId 订单id
*/
public OrderCreateEvent(Object source, Long orderId) {
super(source);
this.orderId = orderId;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
}
监听器:负责监听订单成功事件,发送邮件
/**
* 订单创建成功给用户发送邮件
*/
@Component
public class SendEmailOnOrderCreateListener implements ApplicationListener {
@Override
public void onApplicationEvent(OrderCreateEvent event) {
System.out.println(String.format("订单【%d】创建成功,给下单人发送邮件通知!", event.getOrderId()));
}
}
测试
@Test
public void test2() throws InterruptedException {
//创建事件广播器
ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
//注册事件监听器
applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());
//广播事件订单创建事件
applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));
}
运行输出:
订单【1】创建成功,给下单人发送邮件通知!
用上面spring集成好的事件处理的方式,简化了不少。但是,我们还可以进一步简化,另外两种方式来实现,面向接口的方式和面向@EventListener注解的方式
来一个案例:实现用户注册成功后发布事件,然后在监听器中发送邮件的功能,更好的展现怎么使用。
用户注册事件
需要继承 ApplicationEvent
/**
* 用户注册事件
*/
public class UserRegisterEvent extends ApplicationEvent {
//用户名
private String userName;
public UserRegisterEvent(Object source, String userName) {
super(source);
this.userName = userName;
}
public String getUserName() {
return userName;
}
}
发送邮件监听器
需实现 ApplicationListener 接口
/**
* 用户注册成功发送邮件
*/
@Component
public class SendEmailListener implements ApplicationListener {
@Override
public void onApplicationEvent(UserRegisterEvent event) {
System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
}
}
用户注册服务
内部提供用户注册的功能,并发布用户注册事件
/**
* 用户注册服务
*/
@Component
public class UserRegisterService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
/**
* 负责用户注册及发布事件的功能
*
* @param userName 用户名
*/
public void registerUser(String userName) {
//用户注册(将用户信息入库等操作)
System.out.println(String.format("用户【%s】注册成功", userName));
//发布注册成功事件
this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
this.applicationEventPublisher = applicationEventPublisher;
}
}
测试
@Test
public void test3() throws InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig1.class);
context.refresh();
//获取用户注册服务
com.example.Way1.UserRegisterService userRegisterService = context.getBean(com.example.Way1.UserRegisterService.class);
//模拟用户注册
userRegisterService.registerUser("test");
}
运行输出
用户【test】注册成功
给用户【test】发送注册成功邮件!
其实和上面的面向接口的差不多,唯一的区别就是监听器不用实现ApplicationListener接口了,而是用@EventListener注解来代替,其它部分都一样。
/**
* 用户注册事件
*/
public class UserRegisterEvent extends ApplicationEvent {
//用户名
private String userName;
public UserRegisterEvent(Object source, String userName) {
super(source);
this.userName = userName;
}
public String getUserName() {
return userName;
}
}
/**
* 用户注册监听器
*/
@Component
public class UserRegisterListener {
@EventListener
public void sendMail(UserRegisterEvent event) {
System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
}
@EventListener
public void sendCompon(UserRegisterEvent event) {
System.out.println(String.format("给用户【%s】发送优惠券!", event.getUserName()));
}
}
/**
* 用户注册服务
*/
@Component
public class UserRegisterService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
/**
* 负责用户注册及发布事件的功能
*
* @param userName 用户名
*/
public void registerUser(String userName) {
//用户注册(将用户信息入库等操作)
System.out.println(String.format("用户【%s】注册成功", userName));
//发布注册成功事件
this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
this.applicationEventPublisher = applicationEventPublisher;
}
}
测试:
@Test
public void test4() throws InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig2.class);
context.refresh();
//获取用户注册服务
com.example.Way2.UserRegisterService userRegisterService = context.getBean(com.example.Way2.UserRegisterService.class);
//模拟用户注册
userRegisterService.registerUser("test");
}
用户【test】注册成功
给用户【test】发送注册成功邮件!
给用户【test】发送优惠券!
如果某个事件有多个监听器,默认情况下,监听器执行顺序是无序的,不过我们可以为监听器指定顺序。
指定监听器的顺序有三种方式
方式一:
实现org.springframework.core.Ordered接口,需要实现一个getOrder方法,返回顺序值,值越小,顺序越高。
方式二:
实现org.springframework.core.PriorityOrdered接口
PriorityOrdered接口继承了方式一中的Ordered接口,所以如果你实现PriorityOrdered接口,也需要实 现getOrder方法。
方式三:
类上使用@org.springframework.core.annotation.Order注解
在标注 @EventListener 的方法上面使用 @Order(顺序值) 注解来标注顺序
具体实现如下:
@ComponentScan
@Configuration
public class MainConfig5 {
@Bean
public ApplicationEventMulticaster applicationEventMulticaster() { //@1
//创建一个事件广播器
SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();
//给广播器提供一个线程池,通过这个线程池来调用事件监听器
Executor executor = this.applicationEventMulticasterThreadPool().getObject();
//设置异步执行器
result.setTaskExecutor(executor);//@1
return result;
}
@Bean
public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool(){
ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
result.setThreadNamePrefix("applicationEventMulticasterThreadPool-");
result.setCorePoolSize(5);
return result;
}
}
@1:定义了一个名称为 applicationEventMulticaster 的事件广播器,内部设置了一个线程池 用来异步调用监听器