提升代码简洁,MVEL 表达式和责任链设计模式应用实践

导读

本文主要讲解了MVEL表达式和责任链设计模式相结合一起的消息处理解决方案设计、解耦消息处理节点以及方便代码维护扩展。通过“订单拆单消息”的接入作为具体实践案例,简要阐述了MVEL表达式和Apache Chain职责链设计模式应用场景。希望通过本文,读者可以对MVEL表达式和责任链模式相关概念有一定的认识,并且能够将它们应用到具体的业务场景之中,帮助大家在实际代码研发的时候,降低代码复杂度和提升代码的复用率。

1、背景

互联网的头部公司,各个后台系统应用交互主链路之中,会下发大量MQ消息给分支业务差异化应用。业务系统应用收到MQ消息后结合实际业务处理,但是往往大家在处理逻辑代码的时候会进行不断的叠加代码,造成代码臃肿、复杂和可读性差等问题。例如:

public void handleMessage(String message) throws Exception {
    CallerInfo callerInfo = Profiler.registerInfo(UmpKey.KEY_BD_DLOK_FLAG_GHOST_HANDLER, "xxx", false, true);
    try {
        DeliveredMessage msg = parseMessage(message);
        if (null == msg) {
            return;
        }
        String id = msg.getOrderId();
        if (null == id) {
            //监听到的订单消息 id不应为空
            return;
        }
        String sendPay = msg.getSendPay();
        //是否XXX
        boolean isShop = CAR_O2O.equals(String.valueOf(sendPay.charAt(XXX)));
        //是否是XXX
        boolean isCar = CAR_ADDED_SERVICE.equals(String.valueOf(sendPay.charAt(XXX)));
        String waybillSign = msg.getWaybillSign();
        //是否是XX
        boolean isSelf = SELF_ORDER.equals(String.valueOf(waybillSign.charAt(XXX)));

        long tid = System.nanoTime();
        Long orderId = Long.parseLong(id);

        //监听到订单后,更改订单状态表中的订单状态
        if (isCar && isSelf) {
            verOrderCarService.updateVerOrderCarStatusByOrderId(tid, orderIdLong, UPDATE_PIN);
        }
        if (isShop && isCar) {
            if (isSelf) {
                // 若在新表ver_order_sms_car中存在发送模板1短信,否则,发送原短信(模板3)
                List<VerOrderSmsCar> verOrderSmsCarList = verOrderSmsCarDao.getCarOrderListByOrderId(orderIdLong);
                if (CollectionUtils.isEmpty(verOrderSmsCarList)) {
                    dealTemplateThreeOrder(tid,orderId);
                } else {
                    dealTemplateOneOrder(tid, orderIdLong, verOrderSmsCarList);
                    this.sendShopSms(verOrderSmsCarList);
                }
            } else {
                // 满足条件的订单  即原订单流程没有走完,发送模板3
                List<VerOrderSmsCar> verOrderSmsCarList = verOrderSmsCarDao.getSmsCarOrderByOrderId(orderId);
                //返回数据字段id
                if (CollectionUtils.isNotEmpty(verOrderSmsCarList)) {
                    return;
                }
                dealTemplateThreeOrder(tid, orderId);
            }
        }
        // 发送状态变更消息
        if(isCar){
            this.sendVerStore(orderId, isShop ? 1 : 0);
        }
    } catch (Exception e) {
        LOGGER.error("监听MQ消息处理异常 : {}", e);
        Profiler.functionError(callerInfo);
    } finally {
        Profiler.registerInfoEnd(callerInfo);
    }
}

总结:代码片段逻辑嵌套复杂、各个处理节点耦合(例如:dealTemplateThreeOrder方法、sendShopSms方法)、新增节点不方便(例如:dealTemplateOneOrder(tid, orderId, verOrderSmsCarList))以及代码行数1000+等一系列问题。

2、MVEL表达式

MVEL为 MVFLEX Expression Language(MVFLEX表达式语言)的缩写,它是一种动态/静态的可嵌入的表达式语言和为Java平台提供Runtime(运行时)的语言。它也可以用来解析简单的JavaBean表达式。Runtime(运行时)允许MVEL表达式通过解释执行或者预编译生成字节码后执行。简单一句话,MVEL可以将字符串内容,转化为Java程序来运行,具体细节内容大家可以参考 https://blog.51cto.com/u_16091571/6271830。

3、责任链设计模式

定义:

责任链模式(Chain of Responsibility)又名 职责链模式,是一种行为设计模式,它允许你构建一个由多个对象组成的链,每个对象都有机会处理请求,或者将请求传递给链中的下一个对象。这种模式常用于处理请求的对象之间存在 层次关系 的情况。责任链模式的主要目的是解耦发送者和接收者,使多个对象都有机会处理请求,而不是将请求发送者与接收者硬编码在一起。

结构:

抽象处理者(Handler): 定义一个处理请求的接口,包含抽象处理方法并维护一个对下一个处理者的引用。

具体处理者(Concrete Handler): 实现处理请求的接口,判断能否处理本次请求,如果能够处理则处理,否则将请求传递给下一个处理者。

客户端类(Client): 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

优缺点:

1)优点

a.松散耦合: 责任链模式使得请求发送者和接收者解耦,每个处理者仅需关心自己能否处理请求,而不需要知道整个处理流程的细节。

b.灵活性: 可以动态地改变处理者之间的关系和顺序,新增或删除处理者,以适应不同的需求和场景。

c.可扩展性: 容易添加新的处理者,无需修改现有的代码,符合开闭原则。

d.单一职责原则: 每个具体处理者只负责处理特定类型的请求,符合单一职责原则,使得代码更清晰和可维护。

2)缺点

a.性能问题: 在责任链比较长的情况下,请求可能需要遍历整个链条才能找到合适的处理者,可能影响性能。

Apache Chain 职责链:

整个Apache Chain职责链,包括Context、Command和Filter三个核心组件以及ChainBase类。

1)Context 接口

Context 表示命令执行的上下文,在命令间实现共享信息的传递,父接口是 Map,它只是一个标记接口。

2)Command 接口

Commons Chain 中最重要的接口,表示在 Chain 中的具体某一步要执行的命令。它只有一个方法:boolean execute(Context context),如果返回 true,那么表示 Chain 的处理结束,Chain 中的其他命令不会被调用;返回 false,则 Chain 会继续调用下一个 Command,直到 Chain 的末尾或抛出异常。

3)Filter 接口

它是一种特殊的 Command,除了 Command 的 execute 方法之外,还包括了一个方法:boolean postProcess(Context context, Exception exception),Commons Chain 会在执行了 Filter 的 execute 方法之后,执行 postprocess(不论 Chain 以何种方式结束);Filter 执行 execute 的顺序与 Filter 出现在 Chain 中出现的位置一致,但是执行 postprocess 顺序与之相反。如:execute 的执行顺序是:filter1 -> filter2;而 postprocess 的执行顺序是:filter2 -> filter1。

4)ChainBase

ChainBase 实现 Chain 接口。Chain表示“命令链”,要在其中执行的命令,需要先添加到 Chain 中,Chain 的父接口是 Command。ChainBase类可以直接在Spring使用。它的具体执行方法:

public boolean execute(Context context) throws Exception {
    if (context == null) {
        throw new IllegalArgumentException();
    } else {
        this.frozen = true;
        boolean saveResult = false;
        Exception saveException = null;
        int i = false;
        int n = this.commands.length;
        int i;
        for(i = 0; i < n; ++i) {
            try {
                saveResult = this.commands[i].execute(context);
                if (saveResult) {
                    break;
                }
            } catch (Exception var11) {
                saveException = var11;
                break;
            }
        }
        if (i >= n) {
            --i;
        }
        boolean handled = false;
        boolean result = false;
        for(int j = i; j >= 0; --j) {
            if (this.commands[j] instanceof Filter) {
                try {
                    result = ((Filter)this.commands[j]).postprocess(context, saveException);
                    if (result) {
                        handled = true;
                    }
                } catch (Exception var10) {
                }
            }
        }
        if (saveException != null && !handled) {
            throw saveException;
        } else {
            return saveResult;
        }
    }
}

4、实践案例(订单MQ消息处理流程)

在汽车线下安装履约服务的业务场景之中,除开主站黄金流量流程以外,需要在接到中台订单拆单消息、订单出库消息之后给门店技师派单、发送核销码短信等定制化业务流程。此过程中存在接入多个消息处理同一个事件的相同点,也有同一个消息处理不同事件差异点。具体处理层级结构图如下:





相关类图





实现代码

基于SpringBoot框架实现,消息处理链路中,核心内容包含三部分。第一部分消息处理Handler,接收到消息后将消息内容转化为Java Bean,例如:订单拆单消息(需要拆分订单)OdcDivideOrderhandler。第二部分处理节点Handler,它是职责链的处理节点,按照业务需求进行具体业务代码的实现,例如:技师派单消息发送节点(AddedTechDispatchHandler)。第三部分职责链配置文件,application-chain.xml,以下用订单拆分消息(拆单)处理流程为例。

第一部分(OdcDivideOrderHandler.java):

/**
 * 订单拆分消息(拆单消息)
 
 * @author xxx
 * @date xxxx-xx-xx xx:xx
 */
@Service("odcDivideOrderHandler")
public class OdcDivideOrderHandler extends BaseOrderHandler implements MqMessageHandler<List<VerOrder>> {
    /**
     * 消息分派处理
     */
    @Resource(name="odcDivideOrderChain")
    private Chain odcDivideOrderChain;
    /**
     * 基于MVEL表达式过滤执行器的筛选规则
     */
    private final Map<String, String> expressionMap = new HashMap<String, String>() {
        {
            //派单过滤规则
            put("tech_dispatch_rule", "return sendPayMap.get(\"XXX\") == X && sendPayMap.get(\"XXX\") == X;");
        }
    };
    /**
     * @param tid        处理事件
     * @param messageDTO MQ消息
     * @return 处理结果
     * @throws Exception 处理异常
     */
    private boolean handleMessage(long tid, MqMessageDTO<List<VerOrder>> messageDTO) throws Exception {
         List<VerOrder> verOrderList = messageDTO.getObject();
        try {
            //上下文内容
            Context context = new ContextBase();
            //1.处理时间
            context.put(Constants.TID, tid);
            //2.派单列表
            context.put(Constants.VER_ORDER_LIST,carOrderList);
            //3.操作过滤规则
            context.put(Constants.EXPRESSION_RULE_MAP,expressionMap);
            odcDivideOrderChain.execute(context);
        } catch (Exception ex) {
            //此次代码省略........
        }
        return true;
    }
}

解析:消息处理Handler主要是将接收到消息转化Java Bean,再将具体的上下文内容下传给后续事件处理Handler。参数expressionMap存储的是MVEL表达式需要处理的内容,具体内容结合实际业务场景差异化设置,对于后续节点处理Handler扩展性有很大帮助。odcDivideOrderChain职责链的命令链类,后续各个节点的流转全靠它。

第二部分(AddedTechDispatchHandler.java):

/**
 * 派单Handler
 *
 * @author xxx 
 * @date xxxx-xx-xx xx:xx
 */
@Service("addedTechDispatchHandler")
public class AddedTechDispatchHandler implements Command {
    /**
     * 派单消息topic
     */
    @Value("${xxx}")
    private String topic;
    /**
     * 消息生产
     */
    @Resource(name = "xxxxx")
    private MessageProducer messageProducer;
    @Override
    public boolean execute(Context context) throws Exception {
        Object tid = context.get(Constants.TID);
        Object object = context.get(Constants.VER_ORDER_LIST);
        if (!(object instanceof List)) {
            return false;
        }
        //订单列表
        List<VerOrder> orders = (List<VerOrder>) object;
        //列表为空
        if (CollectionUtils.isEmpty(orders)) {
            return false;
        }
        //过滤规则
        Object ruleObj = context.get(Constants.EXPRESSION_RULE_MAP);
        if (!(ruleObj instanceof Map)) {
            return false;
        }
        //派单规则
        Object obj = ((Map) ruleObj).get(Constants.TECH_DISPATCH_RULE);
        //没有配置规则直接返回
        if (!(obj instanceof String)) {
            return false;
        }
        String expression = (String) obj;
        if (StringUtils.isBlank(expression)) {
            return false;
        }
        for (VerOrder verOrder : orders) {
            //发送派单消息
            this.sendTechDispatch(tid, verOrder, expression);
        }
        return false;
    }

    /**
     * 发送技师派单消息
     *
     * @param tid      处理时间
     * @param verOrder 订单
     */
    public void sendTechDispatch(Object tid, VerOrder verOrder, String expression) {
        try {
            //派单规则判断,false-不派单,true-需要派单
            if (!SendPayUtil.isExpression(expression, verOrder.getSendPayMap())) {
                return;
            }
            String cxt = JSON.toJSONString(verOrder);
            Message message = new Message(topic, cxt, verOrder.getOrderId().toString());
            messageProducer.send(message);
        } catch (JMQException e) {
           //此次代码省略........
        } catch (Exception e) {
           //此次代码省略........
        } finally {
           //此次代码省略........
        }
    }
}

解析:事件节点Handler主要是解析上下内容,执行需要处理的事项内容。特别是SendPayUtil.isExpression(expression, verOrder.getSendPayMap())方法,识别了MVEL表达式,使得即使同一个事件处理节点(例如:派单节点)也可以根据不同MQ消息,设置不同的规则。

/**
 *  sendPayMap表达式解析
 * @param expression 表达式
 * @param sendPayMap 订单SendPayMap值
 * @return 解析结果
 */
public static boolean isExpression(String expression,String sendPayMap){
    //sendPayMap为空
    if (StringUtils.isBlank(sendPayMap)) {
        return false;
    }
    Map map = null;
    try {
        map = JSON.parseObject(sendPayMap, Map.class);
    } catch (Exception ex) {
        LOGGER.error("sendPayMap格式转化错误", ex);
    }
    //map
    if (map == null || map.isEmpty()) {
        return false;
    }
    Map<String,Map> param = new HashMap<>(1);
    param.put(Constant.SEND_PAY_MAP,map);
    return (Boolean)MVEL.eval(expression,param);
}

第三部分(application-chain.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-lazy-init="false" default-autowire="byName">
    <bean id="odcOutStockFullOrderChain" class="org.apache.commons.chain.impl.ChainBase">
        <constructor-arg>
            <array>
                <ref bean="addedOrderWangShiFuHandler"/>
                <ref bean="addedTechDispatchHandler"/>
            </array>
        </constructor-arg>
    </bean>
    <bean id="odcDivideOrderChain" class="org.apache.commons.chain.impl.ChainBase">
        <constructor-arg>
            <array>
                <ref bean="addedTechDispatchHandler"/>
            </array>
        </constructor-arg>
    </bean>
    <bean id="odcUndividedOrderChain" class="org.apache.commons.chain.impl.ChainBase">
        <constructor-arg>
            <array>
                <ref bean="addedTechDispatchHandler"/>
            </array>
        </constructor-arg>
    </bean>
</beans>

解析:命令链配置文件,实现各个事件处理节点进行配置化,聚合各个分散的节点业务逻辑内,后续注入到对应的消息解析Handler。

5、总结

整个消息处理过程中采用Apache Chain职责链模式来降低代码层面的耦合度以及可以动态地改变处理者之间的关系和顺序,新增或删除处理者,以适应不同的需求和场景。MVEL表达式增强了同一事件处理节点的复用性,最大限度的提升了代码的简洁性。希望此文对大家后续设计类似场景有一定的帮助和启发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/635043.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实战Java虚拟机-高级篇

一、GraalVM 什么是GraalVM GraalVM是Oracle官方推出的一款高性能JDK&#xff0c;使用它享受比OpenJDK或者OracleJDK更好的性能。GraalVM的官方网址&#xff1a;https://www.graalvm.org/官方标语&#xff1a;Build faster, smaller, leaner applications。 更低的CPU、内存…

力扣第206题-反转链表

反转链表的效果示意图 要改变链表结构时&#xff0c;通常加入一个创建的临时头结点会更容易操作 时间复杂度&#xff1a;遍历2遍&#xff0c;2n 空间复杂度&#xff1a;额外创建一个栈&#xff0c;n (空间创建一个数组长度最大为5000&#xff0c;你说这个数组是栈也可以&…

5.17 作业+思维导图+模拟面试

// tcp_ser.c #include <myheader.h>#define SER_PORT 8888 #define SER_IP "192.168.125.109"int newfd, sfd;int main(int argc, const char *argv[]) {//1、为通信创建一个端点sfd socket(AF_INET, SOCK_STREAM, 0);//参数1&#xff1a;说明使用的是ipv4通…

2024中青杯数学建模C题:“X 疾病”在人群中的传播代码论文思路分析

2024中青杯数学建模C题论文和代码已完成&#xff0c;代码为C题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求解&#xff09;、模型的评价…

激光雷达在AGV(自动化导引车)中的应用

激光雷达在AGV&#xff08;自动化导引车&#xff09;中的应用主要体现在智能导航和避障功能上&#xff0c;具体来说有以下几个方面&#xff1a; 精确导航&#xff1a;激光雷达能够实时扫描周围环境&#xff0c;建立详细的三维地图&#xff0c;并与AGV的定位系统相结合&#xf…

如何利用Ubuntu服务器运行深度学习项目?

一、整体思路 先配置好服务器端的软件环境&#xff08;工程源码&#xff0c;miniconda&#xff0c;cuda&#xff0c;显卡驱动等&#xff09;&#xff0c;然后用自己电脑的pycharm远程连接服务器运行代码。一句话总结&#xff1a;借用服务器资源运行代码&#xff0c;本地pycharm…

Linux —— 线程同步

Linux —— 线程同步 死锁线程同步条件变量pthread_cond_waitpthread_cond_signal初始状态为什么之后会“阻塞”如何修改以持续运行 pthread_cond_broadcast 条件变量的接口抢票模拟 我们今天接着来了解线程&#xff1a; 死锁 死锁&#xff08;Deadlock&#xff09;是计算机科…

配置旁挂二层组网直接转发示例(命令行)

业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff0c;不影响用户的业务使用。 组网需求 AC组网方式&#xff1a;旁挂二层组网。DHCP部署方式&#xff1a; AC作为DHCP服务器为AP分配IP地址。汇聚交换机SwitchB作…

《Effective Objective-C 2.0》读书笔记——熟悉Objective-C

目录 第一章&#xff1a;熟悉Objective-C第1条&#xff1a;了解Objective-C语言的起源第2条&#xff1a;在类的头文件中尽量少引入其他头文件第3条&#xff1a;多用字面量语法&#xff0c;少用与之等价的方法第4条&#xff1a;多用类型常量&#xff0c;少用#define预处理指令第…

记录docker ps查找指定容器的几个命令

1.docker ps | grep registry 查询包含registry的容器 2.docker ps | grep -E "reigistry\s" 开启正则匹配模式&#xff0c;匹配registry后面为空格的容器&#xff0c;若是匹配一整行可以这样写docker ps | grep -E "^([0-9a-f]{12})\sregistry\s.*" 这…

Nacos 2.x 系列【2】单机部署

文章目录 1. 准备工作2. Windows2.1 下载2.2 目录 & 文件2.3 启动2.4 控制台 3. Linux&#xff08;CentOS&#xff09; 1. 准备工作 Nacos服务端支持三种部署模式&#xff1a; 单机模式&#xff1a;用于测试和单机试用。集群模式&#xff1a;用于生产环境&#xff0c;确保…

Elasticsearch集群和Logstash、Kibana部署

1、 Elasticsearch集群部署 服务器 安装软件主机名IP地址系统版本配置ElasticsearchElk10.3.145.14centos7.5.18042核4GElasticsearchEs110.3.145.56centos7.5.18042核3GElasticsearchEs210.3.145.57centos7.5.18042核3G 软件版本&#xff1a;elasticsearch-7.13.2.tar.gz 示…

Little Snitch for Mac(小飞贼防火墙软件)v5.7.6注册激活版

Little Snitch for Mac&#xff0c;也被称为“小飞贼”防火墙软件&#xff0c;是一款专为Mac用户设计的网络安全工具。以下是关于Little Snitch for Mac的一些主要特点&#xff1a; Little Snitch for Mac(小飞贼防火墙软件)v5.7.6注册激活版下载 强大的监控能力&#xff1a;Li…

Spring框架中获取方法参数名称:DefaultParameterNameDiscoverer

DefaultParameterNameDiscoverer 是Spring框架中用于获取方法参数名称的一个类。在Java中&#xff0c;方法的参数名称通常在编译时会丢失&#xff0c;因为Java字节码并不强制要求保留这些信息。Spring提供了一种机制来恢复这些参数名称&#xff0c;这就是通过DefaultParameterN…

【C++】 单例设计模式的讲解

前言 在我们的学习中不免会遇到一些要设计一些特殊的类&#xff0c;要求这些类只能在内存中特定的位置创建对象&#xff0c;这就需要我们对类进行一些特殊的处理&#xff0c;那我们该如何解决呢&#xff1f; 目录 1. 特殊类的设计1.1 设计一个类&#xff0c;不能被拷贝&#xf…

阿木实验室联合openEuler开源社区-Embedded SlG组(海思项目)参加第五届「开源之夏」,参赛学生火热招募中...

开源之夏是中国科学院软件研究所发起的“开源软件供应链点亮计划”系列暑期活动&#xff0c;旨在鼓励高校学生积极参与开源软件的开发维护&#xff0c;促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区&#xff0c;针对重要开源软件的开发与维护提供项目开发任务&#xf…

bugku 网络安全事件应急响应

开启靶场&#xff1a; 开始实验&#xff1a; 使用Xshell登录服务器&#xff0c;账号及密码如上图。 1、提交攻击者的IP地址 WP: 找到服务器日志路径&#xff0c;通常是在/var/log/&#xff0c;使用cd /var/log/&#xff0c;ls查看此路径下的文件. 找到nginx文件夹。 进入ng…

LabVIEW超高温高压流变仪测试系统

LabVIEW超高温高压流变仪测试系统 超高温高压流变仪广泛应用于石油、天然气、化工等行业&#xff0c;用于测量材料在极端条件下的流变特性。随着计算机技术、测试技术和电子仪器技术的快速发展&#xff0c;传统的流变仪测试方式已无法满足现代工业的需求。因此&#xff0c;开发…

Java——通过方法交换实参值

想写一个方法来交换main函数中的两个变量值&#xff0c;代码如下&#xff1a; public class Test {public static void swap(int x,int y) {int tmp x;x y;y tmp;}public static void main(String[] args) {int a 10;int b 20;System.out.println("交换前&#xff1…

有没有软件可以监控电脑软件?监控电脑软件的系统

有没有软件可以监控电脑软件&#xff1f;监控电脑软件的系统 电脑软件如果不合规也会给企业带来安全危害&#xff0c;比如盗版软件&#xff0c;比如游戏软件耽误工作等&#xff0c;所以需要对电脑软件的监控。下面我将详细介绍几款代表性的电脑监控软件及其功能&#xff0c;帮…