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

前言

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

常用表达式引擎

1、spring el

官方文档

docs.spring.io/spring-fram...

官方示例 github.com/spring-proj...

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工具类

java 复制代码
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

官方文档 ognl.orphan.software/language-gu...

官方示例 github.com/orphan-oss/...

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工具类

java 复制代码
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

官方文档 fnil.net/aviator/

官方示例 github.com/killme2008/...

Aviator是一个轻量级的Java表达式执行引擎,它设计用于高性能的动态计算场景,特别是那些需要在运行时解析和执行复杂表达式的应用场景。以下是Aviator的一些核心特点和功能: 高性能: Aviator优化了表达式的编译和执行过程,特别适合于对性能有严格要求的系统,如金融风控、实时计算等领域。 易于集成: 提供简单的API接口,使得在Java项目中嵌入Aviator变得非常容易,只需引入依赖,即可开始编写和执行表达式。 丰富的表达式支持: 支持数学运算、逻辑运算、比较运算、位运算、字符串操作、三元运算、变量定义与引用、函数调用等,几乎覆盖了所有常见的运算需求。 安全沙箱模式: Aviator提供了沙箱机制,可以限制表达式的执行权限,比如禁止访问某些方法或字段,从而提高应用的安全性。 动态脚本执行: 允许在运行时动态加载和执行脚本,非常适合用于规则引擎、配置驱动的系统逻辑等场景。 JIT编译: Aviator采用即时编译技术,将表达式编译成Java字节码执行,进一步提升执行效率。 数据绑定: 可以方便地将Java对象、Map、List等数据结构绑定到表达式上下文中,实现表达式与Java数据的无缝对接。 扩展性: 支持自定义函数,用户可以根据需要扩展Aviator的功能,增加特定业务逻辑的处理能力。 Aviator因其高性能和灵活性,在需要动态脚本处理的场景中,特别是在那些对性能敏感且需要频繁执行复杂计算逻辑的应用中,是一个非常有吸引力的选择。

Aviator工具类

java 复制代码
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/

官方示例 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表达式引擎门面

官方文档 doc.hutool.cn/pages/Expre...

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

  • Aviator
  • Apache Jexl3
  • MVEL
  • JfireEL
  • Rhino
  • Spring Expression Language (SpEL)

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

基于hutool封装的工具类

java 复制代码
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链接

github.com/lyb-geek/sp...

相关推荐
掘金-我是哪吒14 分钟前
分布式微服务系统架构第156集:JavaPlus技术文档平台日更-Java线程池使用指南
java·分布式·微服务·云原生·架构
亲爱的非洲野猪40 分钟前
Kafka消息积压的多维度解决方案:超越简单扩容的完整策略
java·分布式·中间件·kafka
wfsm42 分钟前
spring事件使用
java·后端·spring
微风粼粼1 小时前
程序员在线接单
java·jvm·后端·python·eclipse·tomcat·dubbo
缘来是庄1 小时前
设计模式之中介者模式
java·设计模式·中介者模式
rebel2 小时前
若依框架整合 CXF 实现 WebService 改造流程(后端)
java·后端
代码的余温3 小时前
5种高效解决Maven依赖冲突的方法
java·maven
慕y2743 小时前
Java学习第十六部分——JUnit框架
java·开发语言·学习
paishishaba3 小时前
Maven
java·maven
张人玉3 小时前
C# 常量与变量
java·算法·c#