Backend Development 15 min read

Designing a Message Processing Solution with MVEL Expressions and the Chain of Responsibility Pattern

This article explains how to combine MVEL expressions with the Chain of Responsibility design pattern to build a decoupled, extensible message‑processing framework, using an order‑splitting MQ example to illustrate the architecture, code implementation, and benefits for backend Java services.

JD Retail Technology
JD Retail Technology
JD Retail Technology
Designing a Message Processing Solution with MVEL Expressions and the Chain of Responsibility Pattern

The article introduces a solution that integrates MVEL expressions with the Chain of Responsibility design pattern to simplify and decouple message processing in backend systems, using an order‑splitting MQ scenario as a concrete example.

Background : In large distributed systems, numerous MQ messages are dispatched to various business services, leading to tangled, hard‑to‑maintain code when handling each message type.

MVEL Expression : MVEL (MVFLEX Expression Language) is an embeddable expression language for the Java platform that can evaluate strings as Java code at runtime, enabling dynamic rule evaluation.

Chain of Responsibility : This behavioral pattern creates a linked list of handler objects where each handler can process a request or pass it to the next handler, promoting loose coupling and extensibility.

Apache Commons Chain implements the pattern with three core components— Context , Command , and Filter —and a ChainBase class that orchestrates command execution.

Code Example – Message Handler :

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) { return; }
        String sendPay = msg.getSendPay();
        boolean isShop = CAR_O2O.equals(String.valueOf(sendPay.charAt(XXX)));
        boolean isCar = CAR_ADDED_SERVICE.equals(String.valueOf(sendPay.charAt(XXX)));
        String waybillSign = msg.getWaybillSign();
        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) {
                List
verOrderSmsCarList = verOrderSmsCarDao.getCarOrderListByOrderId(orderIdLong);
                if (CollectionUtils.isEmpty(verOrderSmsCarList)) {
                    dealTemplateThreeOrder(tid,orderId);
                } else {
                    dealTemplateOneOrder(tid, orderIdLong, verOrderSmsCarList);
                    this.sendShopSms(verOrderSmsCarList);
                }
            } else {
                List
verOrderSmsCarList = verOrderSmsCarDao.getSmsCarOrderByOrderId(orderId);
                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);
    }
}

Code Example – Apache ChainBase Execution :

public boolean execute(Context context) throws Exception {
    if (context == null) { throw new IllegalArgumentException(); }
    this.frozen = true;
    boolean saveResult = false;
    Exception saveException = null;
    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; }
}

Practical Case – Order MQ Processing : The workflow includes three parts—(1) a handler that converts the incoming MQ message to a Java bean, (2) a chain of handler nodes that perform business logic such as technician dispatch, and (3) an XML configuration (application‑chain.xml) that assembles the chain.

Code Example – OdcDivideOrderHandler.java :

@Service("odcDivideOrderHandler")
public class OdcDivideOrderHandler extends BaseOrderHandler implements MqMessageHandler
> {
    @Resource(name="odcDivideOrderChain")
    private Chain odcDivideOrderChain;
    private final Map
expressionMap = new HashMap
() {{
        put("tech_dispatch_rule", "return sendPayMap.get(\"XXX\") == X && sendPayMap.get(\"XXX\") == X;");
    }};
    private boolean handleMessage(long tid, MqMessageDTO
> messageDTO) throws Exception {
        List
verOrderList = messageDTO.getObject();
        try {
            Context context = new ContextBase();
            context.put(Constants.TID, tid);
            context.put(Constants.VER_ORDER_LIST, carOrderList);
            context.put(Constants.EXPRESSION_RULE_MAP, expressionMap);
            odcDivideOrderChain.execute(context);
        } catch (Exception ex) { /* omitted */ }
        return true;
    }
}

Code Example – AddedTechDispatchHandler.java :

@Service("addedTechDispatchHandler")
public class AddedTechDispatchHandler implements Command {
    @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
orders = (List
) 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;
    }
    public void sendTechDispatch(Object tid, VerOrder verOrder, String expression) {
        try {
            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) { /* omitted */ } catch (Exception e) { /* omitted */ } finally { /* omitted */ }
    }
}

Utility – SendPayUtil.isExpression :

public static boolean isExpression(String expression, String sendPayMap) {
    if (StringUtils.isBlank(sendPayMap)) { return false; }
    Map map = null;
    try { map = JSON.parseObject(sendPayMap, Map.class); }
    catch (Exception ex) { LOGGER.error("sendPayMap格式转化错误", ex); }
    if (map == null || map.isEmpty()) { return false; }
    Map
param = new HashMap<>(1);
    param.put(Constant.SEND_PAY_MAP, map);
    return (Boolean) MVEL.eval(expression, param);
}

XML Configuration – 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="odcDivideOrderChain" class="org.apache.commons.chain.impl.ChainBase">
        <constructor-arg>
            <array>
                <ref bean="addedTechDispatchHandler"/>
            </array>
        </constructor-arg>
    </bean>
    <!-- other chain definitions omitted for brevity -->
</beans>

Conclusion : By applying Apache Commons Chain’s responsibility chain and MVEL’s dynamic rule evaluation, the message‑processing code becomes loosely coupled, easily extensible, and more maintainable, allowing developers to add, remove, or reorder handlers without modifying existing logic.

chain of responsibilitydesign patternsJavaSpringBootmvelApache Commons ChainMessage Processing
JD Retail Technology
Written by

JD Retail Technology

Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.