聊聊我们那些年用过的表达式引擎组件

前言

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

常用表达式引擎

1、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));

    }
}

2、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));
    }
}

3、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));
    }

}

4、Mvel2

官方文档
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关注执行效率,通过优化的编译器和执行引擎来减少运行时开销。

5、Hutool表达式引擎门面

官方文档
https://doc.hutool.cn/pages/ExpressionUtil/#介绍

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整合的表达引擎聚合实现,大家感兴趣也可以看看。

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-el

posted @ 2024-11-12 10:39  Linyb极客之路  阅读(50)  评论(0编辑  收藏  举报