从spring源码汲取营养:模仿spring事件发布机制,解耦业务代码
前言
最近在项目中做了一项优化,对业务代码进行解耦。我们部门做的是警用系统,通俗的说,可理解为110报警。一条警情,会先后经过接警员、处警调度员、一线警员,警情是需要记录每一步的日志,是要可追溯的,比如报警人张小三在2019-12-02 00:02:01时间报警,接警员A在1分钟后,将该警情记录完成,并分派给处警调度员B,调度员B在5分钟后,分派给一线警员C,C赶到现场后,花了1个小时处理完成。
这中间,每一个接口,需要做的事情,可能就包括了:警情日志记录;警员当前任务数统计,包括待处置的任务和已经处置完成的任务;我们其实还有一个操作,就是发mq,去通知其他相关人,比如接警员A接警完成后,要发mq通知其主管。
以前的代码可能是这样的:
## 接口1里, 接收警情service里完成以下操作 void 接收警情(xxxReqVo reqVo){ 1:写库 2:记录警情跟踪日志 3:增加当前接警员的接警数 4:发mq通知其他相关人 } ##接口2里,分派警情的service里完成以下操作 void 分派警情(xxxReqVo reqVo){ 1:写库 2:记录警情跟踪日志 3:增加当前处警调度警员的处警数 4:发mq通知其他相关人 }
这样的问题是什么呢?
- 在每一个相关接口里,都要“显式”调用:记录跟踪日志的相关方法、统计相关的方法、发mq相关的方法;但凡有一个地方忘记了,都会导致问题,比如统计数量不准,mq忘发,跟踪日志遗漏等。
- 业务逻辑和这类通用业务揉在一起,假设下次又需要给报警人发个短信,岂不是又得去改核心代码?这不符合我们“对修改关闭,对扩展开放”的开闭原则啊;假设脑残的产品经理,这次说要给报警人发短信,过两天又不要了,难道每个接口,挨个挨个改吗,想想都想打死产品经理,但是这个又犯法,还是想想其他办法?
这个问题,我们可以用类似mq的方法来解决,即,发送消息,各个消费者去消费。一般,mq的方式适用于微服务之间,而我们这里,将使用事件-发布机制来解决这个问题。
源码地址(直接dubug跟一下,很简单,比看文章来得快):
https://gitee.com/ckl111/spring-event-publish-demo
先说说ApplicationListener
在spring boot
之前的spring
时代,想必一些同学用过org.springframework.context.ApplicationListener
,正好我手里有一个老项目,也用到了这个东西,我就拿这个举个例子:
在我们的项目中,需要在启动后,初始化一些东西,比如预热缓存,最早的代码呢,可能是大家各自实现org.springframework.beans.factory.InitializingBean
,但是这样呢,初始化代码散落在各个service中;还有一些直接使用@PostContruct
注解,然后在对应方法里去完成一些初始化操作。但是总体来说,这些方式,在spring的启动过程中,被调用的时机比较靠前,有些候某些bean可能还没初始化完成,而导致一些奇怪的问题。
所以,我们后来统一去掉了这些初始化代码,全部采用以下机制来实现:
import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; @Service public class InitRunner implements ApplicationListener<ContextRefreshedEvent> { @Autowired private InitService initService; @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { //root application context,因为是web项目, if (contextRefreshedEvent.getApplicationContext().getParent() == null) { initService.init(); } }
在这个类中,我们实现了org.springframework.context.ApplicationListener<ContextRefreshedEvent>
,这个 listener的定义如下:
public interface ApplicationListener<E extends ApplicationEvent>