You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解析JEXL递归表达式并动态向上下文添加关联变量?

动态为JEXL表达式添加递归变量上下文的实现方案

这个问题我之前做类似业务需求的时候研究过,两种方案都能实现你的需求——用JEXL解析器主动遍历表达式(推荐)和通过捕获异常被动补全上下文,下面分别详细说明并给出示例:

方案一:利用JEXL解析器主动遍历表达式(优雅高效)

JEXL本身提供了AST(抽象语法树)解析能力,我们可以遍历表达式的语法树,主动收集所有用到的变量(包括递归嵌套的VAR1这类变量里的依赖),然后一次性把所有变量的计算结果注入上下文,避免重复执行表达式。

核心思路

  1. 定义一个递归方法,用于遍历JEXL表达式的AST节点,收集所有未定义的变量名;
  2. 对每个收集到的变量,解析它对应的子表达式,递归收集子表达式里的依赖变量;
  3. 按依赖顺序(先底层变量,再上层变量)计算所有变量的值,注入到JEXL上下文;
  4. 最后执行初始表达式。

Java代码示例

import org.apache.commons.jexl3.*;
import org.apache.commons.jexl3.parser.ASTIdentifier;
import org.apache.commons.jexl3.parser.JexlNode;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class JexlDynamicContext {
    private static final JexlEngine JEXL_ENGINE = new JexlBuilder().create();

    // 递归收集表达式中所有的变量名
    private static void collectVariables(JexlNode node, Set<String> variables) {
        if (node instanceof ASTIdentifier) {
            ASTIdentifier identifier = (ASTIdentifier) node;
            variables.add(identifier.getName());
        }
        // 遍历子节点,处理递归嵌套的情况
        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
            collectVariables(node.jjtGetChild(i), variables);
        }
    }

    // 递归解析并注入所有变量到上下文
    private static void resolveVariables(Map<String, String> variableExpressions, JexlContext context) {
        Set<String> unresolvedVars = new HashSet<>();
        // 先收集所有当前未解析的变量
        for (Map.Entry<String, String> entry : variableExpressions.entrySet()) {
            if (!context.has(entry.getKey())) {
                unresolvedVars.add(entry.getKey());
            }
        }
        if (unresolvedVars.isEmpty()) {
            return;
        }
        // 遍历未解析的变量,解析其表达式并收集依赖
        for (String varName : unresolvedVars) {
            String exprStr = variableExpressions.get(varName);
            Expression expr = JEXL_ENGINE.createExpression(exprStr);
            Set<String> subVars = new HashSet<>();
            collectVariables(expr.getAST(), subVars);
            // 先解析子变量的依赖
            subVars.removeIf(context::has); // 移除已解析的变量
            if (!subVars.isEmpty()) {
                // 递归处理子变量(假设子变量的表达式也在variableExpressions中)
                resolveVariables(variableExpressions, context);
            }
            // 计算当前变量的值并注入上下文
            Object value = expr.evaluate(context);
            context.set(varName, value);
        }
    }

    public static void main(String[] args) {
        // 模拟变量表达式映射:VAR1、VAR2本身是其他表达式,VAR3是直接值
        Map<String, String> variableExpressions = Map.of(
                "VAR1", "VAR3 * 2",
                "VAR2", "VAR1 + 5",
                "VAR3", "10"
        );
        // 初始表达式
        String initialExpr = "VAR2 - 3";

        // 创建JEXL上下文
        JexlContext context = new MapContext();
        // 先注入已知的直接值变量(比如VAR3如果是固定值,可以直接注入)
        context.set("VAR3", 10);
        // 递归解析所有变量并注入上下文
        resolveVariables(variableExpressions, context);

        // 执行初始表达式
        Expression initialExpression = JEXL_ENGINE.createExpression(initialExpr);
        Object result = initialExpression.evaluate(context);
        System.out.println("初始表达式计算结果:" + result); // 输出:22
    }
}

方案二:通过捕获JEXL异常被动补全上下文

这个方案更简单粗暴:先尝试执行初始表达式,当JEXL抛出VariableUndefinedException时,捕获异常并获取未定义的变量名,然后计算该变量的值注入上下文,再重新执行表达式,直到没有异常抛出。这种方法适合变量层级不深的场景,缺点是可能会多次执行表达式,效率稍低。

Java代码示例

import org.apache.commons.jexl3.*;
import java.util.Map;

public class JexlExceptionCatch {
    private static final JexlEngine JEXL_ENGINE = new JexlBuilder().create();

    public static void main(String[] args) {
        Map<String, String> variableExpressions = Map.of(
                "VAR1", "VAR3 * 2",
                "VAR2", "VAR1 + 5",
                "VAR3", "10"
        );
        String initialExpr = "VAR2 - 3";

        JexlContext context = new MapContext();
        Expression initialExpression = JEXL_ENGINE.createExpression(initialExpr);
        Object result = null;

        while (true) {
            try {
                result = initialExpression.evaluate(context);
                break; // 执行成功,退出循环
            } catch (JexlException.VariableUndefined e) {
                String varName = e.getVariable();
                String exprStr = variableExpressions.get(varName);
                if (exprStr == null) {
                    throw new RuntimeException("找不到变量" + varName + "对应的表达式");
                }
                // 计算变量值并注入上下文
                Expression varExpr = JEXL_ENGINE.createExpression(exprStr);
                Object varValue = varExpr.evaluate(context);
                context.set(varName, varValue);
            }
        }

        System.out.println("初始表达式计算结果:" + result); // 输出:22
    }
}

注意事项

  • 两种方案都需要你维护一个变量名→对应表达式的映射表,这样才能知道每个VAR对应的计算逻辑;
  • 如果变量的表达式依赖外部数据源(比如数据库、配置中心),你需要在解析前先获取到对应的表达式内容;
  • 解析器方案的效率更高,适合复杂嵌套的变量场景,异常捕获方案适合快速实现简单场景。

内容的提问来源于stack exchange,提问作者Osy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:58:48