Spring事件驱动
一、什么是事件驱动
说到事件驱动,我们可能会立刻联想到如此众多的概念:观察者模式、发布/订阅模式、消息队列MQ、消息驱动、事件、EventSourcing…
;为了不产生歧义,笔者把自己所了解的这些模棱两可的概念都列了出来,再开始今天的分享:
- 观察者模式:在设计模式中,观察者模式可以算得上是一个非常经典的行为型设计模式,”猫叫了,主人醒了,老鼠跑了“这一经典的例子,是事件驱动模型在设计层面的体现
- 发布/订阅模式:另一模式,发布/订阅模式往往被人们等同于观察者模式,但我的理解是,两者唯一的区别是发布/订阅模式需要有一个调度中心,且订阅方不需要时刻在线,而观察者模式不需要调度中心,例如观察者的列表可以直接由被观察者维护,但必须保持观察者和被观察者同时在线;不过两者即使被混用、互相替代,通常也不影响表达
- MQ:中间件级别的消息队列(
e.g. ActiveMQ、RabbitMQ
),可以认为是发布/订阅模式的一个具体体现;事件驱动 -> 发布/订阅 -> MQ,从抽象到具体- Event:
Java
和Spring
中都有Event
的抽象,分别代表了语言级别和第三方框架级别的事件支持- EventSourcing:这个概念就要关联到领域驱动设计,
DDD
对事件驱动也是非常地青睐,领域对象的状态完全是由事件驱动来控制,由其衍生出了CQRS
架构,具体实现框架有AxonFramework
- Other:
Nginx
可以作为高性能的应用服务器(e.g. openResty
),以及Node.js
事件驱动的特性,这些也是都是事件驱动的体现
二、Spring对Event的支持
三、Spring自定义事件
Step1:新建缓存刷新事件类CacheRefreshEvent
,包含必要的事件参数
/**
* 功能描述
*
* @author liuyiyang
* @since 2020-08-06
*/
public class CacheRefreshEvent extends ApplicationEvent {
private static final long serialVersionUID = -7592287305789142789L;
private String beCode;
private Long pageId;
private String dataSourceCode;
public CacheRefreshEvent(Object source) {
super(source);
}
public String getBeCode() {
return beCode;
}
public void setBeCode(String beCode) {
this.beCode = beCode;
}
public Long getPageId() {
return pageId;
}
public void setPageId(Long pageId) {
this.pageId = pageId;
}
public String getDataSourceCode() {
return dataSourceCode;
}
public void setDataSourceCode(String dataSourceCode) {
this.dataSourceCode = dataSourceCode;
}
}
Step2:通过Spring事件发布者ApplicationEventPublisher
发布事件
/**
* 功能描述
*
* @author liuyiyang
* @since 2020-08-14
*/
@Named
public class CacheRefreshAdminImpl {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
private Boolean cacheRefreshEnable = false;
@Value("${cms.cache.refresh.enable}")
public void setCacheRefreshEnable(Boolean cacheRefreshEnable) {
this.cacheRefreshEnable = cacheRefreshEnable;
}
/**
* 立即发布 - 发布缓存刷新事件
*
* @param beCode 区域码
* @param pageId 页面ID
* @param dataSourceCode 数据源编号
* @return ResponseResult 响应
*/
public void immediatePublish(String beCode, Long pageId, String dataSourceCode) {
if (!cacheRefreshEnable) {
return;
}
CacheRefreshEvent event = new CacheRefreshEvent(this);
event.setBeCode(beCode);
event.setPageId(pageId);
event.setDataSourceCode(dataSourceCode);
applicationEventPublisher.publishEvent(event);
}
}
Step3:声明事件监听器,异步@Async
监听CacheRefreshEvent
类型的事件,注意需要开启Spring异步支持
/**
* listenCacheRefreshEvent 事件监听器
*
* @param cacheRefreshEvent cacheRefreshEvent
*/
@Async
@EventListener
public void listenCacheRefreshEvent(CacheRefreshEvent cacheRefreshEvent) {
String beCode = cacheRefreshEvent.getBeCode();
Long pageId = cacheRefreshEvent.getPageId();
String dataSourceCode = cacheRefreshEvent.getDataSourceCode();
LOGGER.info("Start to process the cache refresh event, event={}", JSON.toJSONString(cacheRefreshEvent));
// etc...
}
四、Spring事件的其他应用场景
如果需要在现有的代码逻辑上,添加额外的操作,且不想让这些额外操作影响到原有的逻辑,那么使用事件驱动就再适合不过了,例如在用户注册的业务流程中,添加一个操作”当用户使用电子邮箱注册成功时,发送欢迎邮件“,这其中,发送邮件是一个重操作,当用户注册成功以后,注册接口本应直接返回”成功“,如果添加发邮件的动作,一是会延长接口响应时间,二是注册和发邮件两个动作之间本就没有逻辑粘黏性,将逻辑隔离开来,不要糅合在一起,会让代码结构更清晰、更优雅、更符合CleanCode
的理念,三是如果发邮件的动作产生异常、发送不成功,那么代码需要做相应处理,逻辑糅合在一起时,很容易漏掉这种异常的场景,从而造成“用户明明已经注册成功了,但界面却提示失败”的BUG