1. MVEL 是什么?

简单来说,MVEL 是一种强大的表达式解析器(Expression Language)。我们可以编写特定的表达式字符串,交给 MVEL 进行解析计算,从而得到结果。

概念可能比较抽象,我们通过示例来理解。

基本示例

比如我们要进行一个加法运算。在 Java 中通常这样写:

int res = 1 + 1;  // 2

若使用 MVEL,则可以这样写:

Object res = MVEL.eval("1 + 1");  // 2

对比两者,第一种方式是硬编码实现的计算结果;而第二种方案直接向 MVEL.eval 函数传递一个表达式字符串,即可动态计算出结果。如果需要计算 1 - 1,只需传入不同的表达式字符串即可。

试想如果要计算 (2 + 2) * 3 + 5 / 22 > 1 ? 1 + 1 : 2 + 2 这样的复杂逻辑,硬编码需要编写多行代码且不便扩展,而 MVEL 仅需改变表达式字符串。

MVEL 的能力不仅限于此。它支持大量的语法结构,包括条件判断、循环等,还可以支持自定义函数。那么在工作中我们该如何使用它呢?

2. 在自定义数据流转中的使用

什么是数据流转

数据流转指的是不同对象间数据的转换。例如,将对象 A 的数据通过某些规则转化为对象 B 的数据。这听起来很像数据清洗,确实,数据清洗是其中的一个具体场景。

如下图所示:

由图可以看出,源对象和目标对象中 nameage 字段是一对一映射。但是目标对象不需要 sex 字段,却多了一个 birthYear(出生年)字段,且该字段是通过年龄计算而来的。

下面我们以代码模拟这个转换过程。为了简化演示,这里对象均使用 Map 来定义:

HashMap<Object, Object> srcMap = Maps.newHashMap();
srcMap.put("name", "zs");
srcMap.put("age", 10);
srcMap.put("sex", "女");

// 字段映射关系
HashMap<String, String> mapping = Maps.newHashMap();
mapping.put("name", "name");
mapping.put("age", "age");
// 这里先把当前年份写死为 2019
mapping.put("birthYear", "2019 - age");

// 目标对象
HashMap<Object, Object> targetMap = Maps.newHashMap();
// k 为目标表字段,v 为转换规则
mapping.forEach((k, v) -> {
    Object reValue = MVEL.eval(v, srcMap);
    targetMap.put(k, reValue);
});

System.out.println("源对象" + srcMap);    // 源对象{sex=女,name=zs, age=10}
System.out.println("目标对象" + targetMap);   // 目标对象{birthYear=2009, name=zs, age=10}

实现过程非常简单。但上述代码中,计算出生年份时的当前年份被写死为 2019,这明显不是我们想要的通用方案。我们可以通过自定义函数来解决这个问题。

自定义函数扩展

1. 定义获取当前年份函数

首先定义一个 Java 方法来获取当前年份:

/**
 * 获取当前年份方法
 * @return 当前年份字符串
 */
public static Object getCurrentYear() {
    Calendar date = Calendar.getInstance();
    String year = String.valueOf(date.get(Calendar.YEAR));
    return year;
}

2. 注册自定义函数

将定义好的函数注册到 MVEL 的解析上下文中:

static ParserContext context = new ParserContext();
static {
    // MvelTest 是 getCurrentYear 函数所在的类
    Method[] declaredMethods = MvelTest.class.getDeclaredMethods();
    for (Method method : declaredMethods) {
        context.addImport(method.getName(), method);
    }
}

3. 使用编译后的表达式

在使用时,将原来的 MVEL.eval(v, srcMap) 替换为以下代码:

Object reValue = MVEL.executeExpression(MVEL.compileExpression(v, context), srcMap);

compileExpression 的作用是将我们的规则编译成 MVEL 可以识别的执行过程。此时,将 birthYear 的规则替换为 mapping.put("birthYear", "getCurrentYear() - age"),执行后即可得到动态计算的正确结果。

有了这些基础,我们可以自定义更多的转换规则,还可以借此开发一套用户配置工具,根据用户自己的配置进行相应的资源映射,得到想要的目标数据。

3. 小结

本文只是展示了在工作中用到 MVEL 的一个小小尝试。关于 MVEL 更多的特性与研究,后续可进一步深入。

说明:文中示例代码基于 MVEL 2.x 版本及 Java 8 语法环境(使用了 Maps.newHashMap 需引入 Guava 依赖)。部分示例中的年份(如 2019)为写作时的硬编码演示,实际使用时请结合动态逻辑。