写点什么

告别 if else,推荐 5 款 Java 表达式引擎

作者:采菊东篱下
  • 2024-12-27
    湖南
  • 本文字数:5561 字

    阅读完需:约 18 分钟

前言

我们在设计一些表单或者流程引擎时,可能我们会设计各种各样的表达式或者规则,我们通过各种表达式或者规则来实现我们的业务流转。今天就来盘点一下我们经常会使用到的表达式引擎。

这些表达式引擎都是比较常用的,很多人估计都很熟悉。我们再一起回顾一下。

spring el

官方文档

https://docs.spring.io/spring-framework/reference/core/expressions.html

官方示例

https://github.com/spring-projects/spring-framework/tree/master/spring-expression

Spring Expression Language (SpEL) 是 Spring 框架中的一个强大的表达式语言,用于在运行时查询和操作对象图。以下是关于 Spring EL 的几个关键点:

  • 动态查询和操作: SpEL 允许你在运行时执行复杂的查询和操作数据,比如读取 bean 的属性值、调用方法、进行算术运算、逻辑判断等。

  • 集成于 Spring 框架: SpEL 广泛应用于 Spring 的各种模块中,如 Spring Security 的访问控制表达式、Spring Data 的查询条件定义、Spring Integration 的消息路由等。

  • 基本语法: SpEL 表达式通常被包含在 #{...}中,例如 #{property}用来获取一个 bean 的属性值。它支持字符串、布尔、算术、关系、逻辑运算符,以及方法调用、数组和列表索引访问等。

  • 上下文感知: SpEL 能够访问 Spring 应用上下文中的 Bean,这意味着你可以直接在表达式中引用配置的 bean,实现高度灵活的配置和运行时行为调整。

  • 类型转换: SpEL 提供了内置的类型转换服务,可以自动或显式地将一种类型的值转换为另一种类型。

  • 安全考量: 使用 SpEL 时需要注意安全性,避免注入攻击。Spring 提供了 ExpressionParser 的配置来限制表达式的执行能力,如禁用方法调用或属性访问等。

例子:

  • 访问 Bean 属性: #{myBean.propertyName}

  • 方法调用: #{myBean.myMethod(args)}

  • 三元运算符: #{condition ? trueValue : falseValue}

  • 列表和数组访问: #{myList[0]}

  • 算术运算: #{2+3}

spel 工具类

public class SpringExpressionUtil {    private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    private SpringExpressionUtil(){}
    /**     * Evaluates the given Spring EL expression against the provided root object.     *      * @param rootObject The object to use as the root of the expression evaluation.     * @param expressionString The Spring EL expression to evaluate.     * @param returnType The expected return type.     * @return The result of the expression evaluation.     */    public static <T> T evaluateExpression(Map<String, Object> rootObject, String expressionString, Class<T> returnType) {        StandardEvaluationContext context = new StandardEvaluationContext(rootObject);        rootObject.forEach(context::setVariable);        return EXPRESSION_PARSER.parseExpression(expressionString).getValue(context,returnType);    }
    public static void main(String[] args) {        Map<String,Object> map = new HashMap<>();        map.put("name","lybgeek");        map.put("hello","world");        System.out.println(evaluateExpression(map,"#root.get('name')",String.class));
    }}
复制代码

ognl

官方文档

https://ognl.orphan.software/language-guide

官方示例

https://github.com/orphan-oss/ognl

OGNL (Object-Graph Navigation Language) 是一个强大的表达式语言,用于获取和设置 Java 对象的属性。它在许多 Java 框架中被用作数据绑定和操作对象图的工具,最著名的应用是在 Apache Struts2 框架中。



以下是关于 OGNL 的一些关键特性:

  • 简单表达式: OGNL 允许你以简单的字符串形式编写表达式来访问对象属性,如 person.name 就可以获取 person 对象的 name 属性。

  • 链式导航: 支持链式调用来深入对象图,例如 customer.address.street 会依次导航到 customer 的 address 属性,再从 address 获取 street。

  • 集合操作: OGNL 可以直接在表达式中处理集合和数组,包括遍历、筛选、投影等操作,如 customers.{name}可以获取所有 customers 集合中每个元素的 name 属性。

  • 上下文敏感: OGNL 表达式解析时会考虑一个上下文环境,这个环境包含了变量、对象和其他表达式可能需要的信息。

  • 方法调用与构造器: 除了属性访问,OGNL 还支持调用对象的方法和构造新对象,如 @myUtil.trim(name)调用工具类方法,或 new java.util.Date()创建新对象。

  • 条件与逻辑运算: 支持 if、else 逻辑,以及 &&、||等逻辑运算符,使得表达式可以处理更复杂的逻辑判断。

  • 变量赋值: OGNL 不仅能够读取数据,还能设置对象属性的值,如 person.name = "Alice"。

  • 安全问题: 和 SpEL 一样,使用 OGNL 时也需注意表达式注入的安全风险,确保用户输入不会被直接用于构造表达式,以防止恶意操作。

OGNL 以其简洁的语法和强大的功能,在处理对象关系和数据绑定方面非常实用,尤其是在需要动态操作对象和集合的场景下。

ognl 工具类

public class OgnlExpressionUtil {    private OgnlExpressionUtil(){}
    /**     * Evaluates the given Ognl EL expression against the provided root object.     *      * @param rootObject The object to use as the root of the expression evaluation.     * @param expressionString The OGNL EL expression to evaluate.     * @param returnType The expected return type.     * @return The result of the expression evaluation.     */    public static <T> T evaluateExpression(Map<String, Object> rootObject, String expressionString, Class<T> returnType) {        Object value = OgnlCache.getValue(expressionString, rootObject);        if(value != null && value.getClass().isAssignableFrom(returnType)){            return (T)value;        }
        return null;    }
    public static void main(String[] args) {        Map<String,Object> map = new HashMap<>();        map.put("name","lybgeek");        map.put("hello","world");        System.out.println(OgnlExpressionUtil.evaluateExpression(map,"#root.name",String.class));
        System.out.println(SpringExpressionUtil.evaluateExpression(map,"#root.get('hello')",String.class));    }}
复制代码

Aviator

官方文档

http://fnil.net/aviator/

官方示例

https://github.com/killme2008/aviatorscript

Aviator 是一个轻量级的 Java 表达式执行引擎,它设计用于高性能的动态计算场景,特别是那些需要在运行时解析和执行复杂表达式的应用场景。

以下是 Aviator 的一些核心特点和功能:

  • 高性能: Aviator 优化了表达式的编译和执行过程,特别适合于对性能有严格要求的系统,如金融风控、实时计算等领域。

  • 易于集成: 提供简单的 API 接口,使得在 Java 项目中嵌入 Aviator 变得非常容易,只需引入依赖,即可开始编写和执行表达式。

  • 丰富的表达式支持: 支持数学运算、逻辑运算、比较运算、位运算、字符串操作、三元运算、变量定义与引用、函数调用等,几乎覆盖了所有常见的运算需求。

  • 安全沙箱模式: Aviator 提供了沙箱机制,可以限制表达式的执行权限,比如禁止访问某些方法或字段,从而提高应用的安全性。动态脚本执行: 允许在运行时动态加载和执行脚本,非常适合用于规则引擎、配置驱动的系统逻辑等场景。

  • JIT 编译: Aviator 采用即时编译技术,将表达式编译成 Java 字节码执行,进一步提升执行效率。数据绑定: 可以方便地将 Java 对象、Map、List 等数据结构绑定到表达式上下文中,实现表达式与 Java 数据的无缝对接。

  • 扩展性: 支持自定义函数,用户可以根据需要扩展 Aviator 的功能,增加特定业务逻辑的处理能力。

Aviator 因其高性能和灵活性,在需要动态脚本处理的场景中,特别是在那些对性能敏感且需要频繁执行复杂计算逻辑的应用中,是一个非常有吸引力的选择。

Aviator 工具类

public final class AviatorExpressionUtil {    private AviatorExpressionUtil() {    }
    /**     * 执行Aviator表达式并返回结果     *     * @param expression Aviator表达式字符串     * @param env        上下文环境,可以包含变量和函数     * @return 表达式计算后的结果     */    public static <T> T evaluateExpression(Map<String, Object> env,String expression, Class<T> returnType) {        Object value = AviatorEvaluator.execute(expression, env);        if(value != null && value.getClass().isAssignableFrom(returnType)){            return (T)value;        }
        return null;    }
    public static void main(String[] args) {        Map<String,Object> map = new HashMap<>();        map.put("name","lybgeek");        map.put("hello","world");
        Map<String,Object> env = new HashMap<>();        env.put("root",map);        System.out.println(evaluateExpression(env,"#root.name",String.class));    }}
复制代码

Mvel2

官方文档

https://juejin.cn/post/mvel.documentnode.com/

官方示例

https://github.com/mvel/mvel

MVEL2(MVFLEX Expression Language 2)是一种强大且灵活的 Java 库,用于解析和执行表达式语言。它是 MVEL 项目的第二代版本,旨在提供高效、简洁的方式来操作对象和执行逻辑。

下面是关于 MVEL2 的一些关键特性和使用指南:

  • 动态类型与静态类型混合: MVEL 支持动态类型,同时也允许静态类型检查,这意味着你可以选择是否在编译时检查类型错误,增加了灵活性和安全性。

  • 简洁的语法: MVEL 语法基于 Java 但更加简洁,便于编写和阅读,适用于快速构建表达式和小型脚本。

  • 属性访问与方法调用: 类似于其他表达式语言,MVEL 允许直接访问对象属性和调用其方法,如 person.name 或 list.size()。

  • 控制流语句: 支持 if、else、switch、循环(for、while)等控制流结构,使得在表达式中实现复杂逻辑成为可能。

  • 模板引擎: MVEL2 提供了一个强大的模板引擎,可以用来生成文本输出,类似于 Velocity 或 Freemarker,但与 MVEL 表达式无缝集成。

  • 变量赋值与函数定义: 直接在表达式中定义变量和函数,支持局部变量和闭包(匿名函数)。数据绑定与转换: 自动或手动进行类型转换,简化了不同数据类型间的操作。

  • 集成与扩展: MVEL 设计为易于集成到现有 Java 项目中,同时提供了扩展点,允许用户定义自定义函数和操作符。

  • 性能优化: MVEL 关注执行效率,通过优化的编译器和执行引擎来减少运行时开销。

Hutool 表达式引擎门面

官方文档

https://doc.hutool.cn/pages/ExpressionUtil/#%E4%BB%8B%E7%BB%8D

hutool 工具包在 5.5.0 版本之后,提供了表达式计算引擎封装为门面模式,提供统一的 API,去除差异。目前支持如下表达式引擎

  • Aviator

  • Apache Jexl3

  • MVEL

  • JfireEL

  • Rhino

  • Spring Expression Language (SpEL)

如上所述的表达式引擎不能满足要求,hutool 还支持通过 SPI 进行自定义扩展

基于 hutool 封装的工具类

public class HutoolExpressionUtil {    private HutoolExpressionUtil(){}
    /**     * 执行表达式并返回结果。     *     * @param expression 表达式字符串     * @param variables  变量映射,键为变量名,值为变量值     * @return 表达式计算后的结果     */    public static <T> T evaluateExpression(Map<String, Object> variables,String expression, Class<T> returnType) {        try {            Object value = ExpressionUtil.eval(expression, variables);            if(value != null && value.getClass().isAssignableFrom(returnType)){                return (T)value;            }        } catch (Exception e) {            throw new RuntimeException("Error executing  expression: " + expression, e);        }
        return null;    }
    public static void main(String[] args) {        Map<String,Object> map = new HashMap<>();        map.put("name","lybgeek");        map.put("hello","world");
        Map<String,Object> variables = new HashMap<>();        variables.put("root",map);        System.out.println(evaluateExpression(variables,"root.name",String.class));    }}
复制代码

总结

本文介绍了市面比较常用的表达式引擎组件,而这些引擎基本上都可以用 hutool 提供的表达式门面实现,hutool 确实在工具类这方面做得很好,基本上我们日常会用到的工具,它大部分都涵盖到。最后文末 demo 链接,也提供了跟 spring 整合的表达引擎聚合实现,大家感兴趣也可以看看。

用户头像

还未添加个人签名 2023-02-14 加入

还未添加个人简介

评论

发布
暂无评论
告别if else,推荐5款Java表达式引擎_Java_采菊东篱下_InfoQ写作社区