前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >事件监听思考

事件监听思考

作者头像
路行的亚洲
发布2022-11-16 14:46:14
2.1K0
发布2022-11-16 14:46:14
举报
文章被收录于专栏:后端技术学习后端技术学习

在整合在项目中,我们通常需要基于事件去触发另外的业务逻辑动作的完成。也即在我们做需求时,通常会基于不同的事件码来完成业务处理,此时可以考虑将其单独处理,基于观察者模式+策略模式。还有一种如果当Spring完成Bean的初始化,需要做一些特殊处理,此时除了使用InitializingBean,还可以使用监听完成一些定制化的初始化动作,实现ApplicationListener<ContextRefreshedEvent>。

也即一种方式是实现一个上下文,基于不同的事件码去实现对应的业务处理场景,此时可以基于监听,分不同的策略实现处理。另一种则是通常是完成Bean初始化后,如果当前需要自定义的配置信息或者服务没有完成的场景。如果你看过dubbo3.0的源码的话,你会发现dubbo-config-spring模块,使用到了事件监听的方式执行发布和监听。

一、实现ApplicationListener<ContextRefreshedEvent>的场景

防止重复触发:

比如:ContextRefreshedEvent是上下文刷新事件,在初始化后执行的事件,当完成初始化后触发,从而执行监听。这个事件在完成初始化执行。同时其避免了重复触发的情况。下面的代码是在Spring完成Bean的初始化后,经过上下文刷新事件后,执行的,同时可以根据监听对象的顺序依次进行触发。

代码语言:javascript
复制
@Component
public class MqBootstrapListener implements ApplicationListener<ContextRefreshedEvent>, Ordered {

    private static boolean isInit = false;
    @Autowired
    private ReportService reportService;
    @Override
    public int getOrder() {
        // 执行顺序定义
        return Ordered.LOWEST_PRECEDENCE;
}

    //触发的事件启动相关业务,同时将初始化变成true
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isInit) {
            try {
                startTimer();
                startPortalTimer();
                reportService.registerReport();
                isInit = true;
                log.info("mq初始化成功!");
            } catch (Exception e) {
                log.error("mq初始化异常", e);
                throw e;
            }
        }

}

这种效果类似于实现InitializingBean接口,会调用afterPropertiesSet方法,也即执行Bean初始化后自己实现的方法。此时的发布事件是基于Spring的。

类似的过程在Spring做xml的默认解析时候,就会使用到。完成xml解析的时候,会做一个事件的发布。后续的操作留给了业务实现。

那么我们能不能自己发布事件,然后自己去监听呢?答案当然是可以的。

二、基于自己实现的事件发布和事件监听

通常一种常用的做法是先定义好类型,这样的话,可以基于类型进行key是类型,value为具体的接口实现,基于当前的key和value放到map中。这样在需要获取的时候,可以基于code获取对应的渠道服务,从而实现对不同类型的key,做到对应的业务处理。

比如: 我的业务服务订单服务中,需要基于订单提交后审批流做一个订单对应状态的更新和业务的处理。此时由于审核的情况不同,会出现不同的订单状态。那么,我们对于不同的订单状态,处理的业务方式是不同的。因此,可以考虑基于策略者模式+观察者模式完成完成订单状态和业务的处理。

审批流完成后,由于审批的结果,导致订单的状态不同,而进行财务对应服务更新时,做的处理时不一样的。

订单出现的状态:

订单审批通过

订单审批取消

更新订单

新增订单

因此就需要我们能够在不影响后续逻辑处理的情况下可以将一部分繁杂的逻辑基于事件发布的方式独立发布出来进行处理,采用异步的方式处理。这样做一个时方便后续的业务的处理,同时对应业务繁杂处理时,能够快速处理。

首先执行事件发布:

代码语言:javascript
复制
  @Override
    public Integer auditUpdateStatus(TaskResearchDTO taskResearchVO) {
        OrderApprovalPO orderApprovalPO = orderApprovalMapper.selectById(taskResearchVO.getBusinessKey());
        SalesOrdersPO salesOrdersPO = salesOrdersMapper.selectById(orderApprovalPO.getSoNoId());
        QueryWrapper<SalesOrderVinPO> queryWra = new QueryWrapper<>();
        queryWra.lambda().eq(SalesOrderVinPO::getViNo, salesOrdersPO.getSoNo());
        SalesOrderVinPO salesVinPO = salesOrderVinMapper.selectOne(queryWra);
        salesOrdersPO.setUpdatedAt(LocalDateTime.now());
        salesVinPO.setUpdatedAt(LocalDateTime.now());
        //发布事件
        applicationEventPublisher.publishEvent(new MessageEvent(this, orderApprovalPO.getAuditType(),orderApprovalPO,salesOrdersPO,salesVinPO));
        salesOrderVinMapper.updateById(salesVinPO);
        int result = salesOrdersMapper.updateById(salesOrdersPO);
        Integer auditType = orderApprovalPO.getAuditType();
        if(SalesCommonDictConstants.APPROVAL_TYPE_ADD_ORDER.equals(auditType) || SalesCommonDictConstants.APPROVAL_TYPE_UPDATE_ORDER.equals(auditType)){
            leadTimesSnapshotService.saveCompensation(salesOrdersPO);
        }
        return result;
    }

基于发布,我们可以写一个自己的监听,然后处理

代码语言:javascript
复制
Component
public class OrderListener implements ApplicationListener<MessageEvent> {

    @Autowired
    private AuditContext saveContext;

    @Override
    public void onApplicationEvent(MessageEvent event) {
       saveContext.resolveFile(event.getAuditType(), event.getOrderApprovalPO(), event.getSalesOrdersPO(), event.getSalesOrderVinPO());
    }
}

然后基于不同的type,做类型的路由:

代码语言:javascript
复制
@Component
public class AuditContext implements ApplicationContextAware {
    public static final Logger log = LoggerFactory.getLogger(AuditContext.class);
    /**
     * 将策略存放在map中
     */
    private Map<Integer, AuditTypeStrategy> activityStrategyMap = new ConcurrentHashMap<>();

    /**
     * 获取对应策略
     */
    public void resolveFile(int type, OrderApprovalPO orderApprovalPO, SalesOrdersPO salesOrdersPO, SalesOrderVinPO salesVinPO) {
        AuditTypeStrategy auditTypeStrategy = activityStrategyMap.get(type);
        if (auditTypeStrategy != null) {
            auditTypeStrategy.auditUpdateStatus(orderApprovalPO, salesOrdersPO, salesVinPO);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //获取AuditTypeStrategy接口所有的实现
        Map<String, AuditTypeStrategy> tmepMap = applicationContext.getBeansOfType(AuditTypeStrategy.class);
        //存入上面的map中
        tmepMap.values().forEach(strategyService -> activityStrategyMap.put(strategyService.getAuditType(), strategyService));
    }
}

当传入对应的type的时候,可以拿到具体的service实现,从而对具体的业务进行处理。

通常第一种使用的场景比如:初始化字典的相关信息、redis缓存的相关信息、系统信息或者定时任务等等。第二种使用场景,比如需要发布一个事件,可以方便业务的处理,或者处理的过程较为繁琐的时候,就可以使用,easyExcel的导入使用的就是基于监听的方式进行处理的,或者当前业务处理情况有多种的时候,就可以考虑使用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-10-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端技术学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档