动态脚本引擎之QLExpress讲解

1 QLExpress

1.1 简介

1.1.1 介绍

QLExpress(Quick Language Express) 是阿里巴巴开源的一门动态脚本引擎解析工具,是一个轻量级、高性能的 Java 规则引擎 和 动态脚本执行框架,起源于阿里巴巴的电商业务,旨在解决业务规则、表达式、数学计算等动态脚本的解析问题。它类似于 GroovyJavaScript 引擎,但更专注于规则解析和执行效率

其具有以下基本特点:

  • 线程安全QLExpress 被设计为线程安全的动态脚本引擎,它使用 threadlocal 类型的临时变量,确保在引擎运算过程中的并发场景下的线程安全性。
  • 高效执行: 为了提高执行效率,QLExpress 在编译过程中可以将比较耗时的脚本编译结果缓存到本地机器。此外,运行时的临时变量创建采用了缓冲池技术,以确保高效的运行时性能,使其与一些性能优秀的脚本引擎(如Groovy)相当。
  • 弱类型脚本语言QLExpress 采用弱类型脚本语言,语法类似于GroovyJavaScript。这使得业务规则的表达更加灵活,虽然相对于强类型脚本语言可能略慢,但在业务的灵活性方面提供了很大的优势。
  • 安全控制QLExpress 提供了一些运行时参数的设置,以进行安全控制。通过这些参数,可以预防一些潜在的安全问题,如死循环或对高危系统API的调用。
  • 代码精简、依赖最小QLExpress的设计追求代码的精简和最小依赖,其jar包大小为250k,适用于所有Java的运行环境。这使得它在各种环境中都能轻松部署和运行,包括在Android系统的低端POS机上广泛应用。

1.1.2 QLExpress与常用规则引擎对比

特性 / 规则引擎 Drools Aviator EasyRule QLExpress
语言 Drools规则语言 (DRL) Aviator表达式语言 Java 弱类型脚本语言
性能 适用于复杂规则,可能较慢 高性能表达式求值引擎 相对较高性能,适用于简单规则 高效执行,适用于业务规则和表达式计算
灵活性 非常灵活,支持动态修改规则 灵活,支持丰富的运算符和函数 简单易用,适合非专业开发人员 灵活,支持业务规则、表达式和数学计算
语法 专门的规则语言 表达式语言 Java编写规则 弱类型脚本语言,类似于Groovy和JavaScript
应用场景 复杂的业务规则 简单的表达式计算和规则 简单规则场景,面向非专业开发人员 业务规则、表达式、数学计算,适用于电商业务
开发者社区 大型开发者社区 相对较小的社区规模 相对较小的社区规模 相对较小的社区规模,阿里巴巴内部有影响力
文档 详尽的文档 文档相对较少 文档相对较少 文档相对较少,可能需要深入源代码理解
开源

Drools 适用于复杂的业务规则,而 AviatorQLExpress适用于相对简单的表达式计算和规则。EasyRule更适合简单规则场景,特别是面向非专业开发人员的情况。最终选择取决于具体需求,包括业务规则的复杂性、性能要求、开发人员技能水平以及项目的特定场景。

1.2 简单操作和原理介绍

1.2.1 简单操作

在 Maven 项目中引入 QLExpress,需要在项目的 pom.xml 文件中添加相关的依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>QLExpress</artifactId>
    <version>3.3.0</version> <!-- 推荐最新稳定版 -->
</dependency>

以下展示简单演示如何使用 QLExpress 计算折扣后的金额。在实际项目中,可能需要更复杂的脚本和上下文,以适应业务需求。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

publicclass QLExpressExample {
    public static void main(String[] args) {
        try {
            // 创建 QLExpress 引擎
            ExpressRunner runner = new ExpressRunner();

            // 创建上下文并设置变量
            DefaultContext<String, Object> context = new DefaultContext<>();
            context.put("amount", 1000);
            context.put("discount", 0.1);

            // 执行脚本
            String expression = "amount * (1 - discount)";
            Object result = runner.execute(expression, context, null, true, false);

            // 输出结果
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

参数说明

参数 类型 是否必填 默认值 说明
express String - 要执行的动态脚本(如 "a + b * 2" 或 "if (x > 0) { return 'OK'; }")
context DefaultContext<String, Object> - 变量上下文,用于向脚本传递参数(如 context.put("a", 10))
如果无变量可传 null,但通常建议显式创建 new DefaultContext<>()
errorList List null 用于收集执行过程中的错误信息(如语法错误)
传 null 表示不收集,错误直接抛异常。
isCache boolean true 是否缓存脚本的编译结果(提升重复执行性能)。建议开启(true),除非脚本每次变化。
isTrace boolean false 是否输出详细的执行日志(调试时使用)。生产环境建议关闭(false),避免日志膨胀。

1.2.2 原理介绍

QLExpress 的一般工作原理,包括语法树分析、上下文和执行过程。
在这里插入图片描述

  • 语法树分析QLExpress 会先将输入的脚本进行词法分析语法分析,生成一棵语法树。这个语法树表示了脚本的结构,将其组织成可以被执行的形式。
  • 上下文: 在QLExpress中,上下文是一个关键概念。它是脚本执行时的环境,包含变量、函数等信息。在执行脚本之前,可以向上下文中添加变量,定义函数,设置一些执行参数等。这样,脚本执行时可以引用上下文中的内容。
  • 执行过程QLExpress的执行过程包括编译运行两个主要阶段。在编译阶段,脚本被解析并生成可以执行的指令序列。在运行阶段,这些指令被执行,从而实现脚本的功能。

在这个框架下,二次定制的功能扩展可以包括:

  • 自定义操作符和函数: 可以通过实现自定义的操作符和函数,使得脚本能够执行特定的业务逻辑。
  • 修改执行流程: 可以在执行阶段插入自定义的逻辑,改变脚本执行的流程。
  • 定制编译过程: 在编译阶段定制特定的优化或变换,以满足特殊需求。
  • 扩展上下文功能: 可以添加一些上下文的拦截器,使得在脚本执行前后可以执行额外的逻辑。

1.3 基本语法学习

1.3.1 操作符

QLExpress 支持一系列操作符,包括算术运算符、比较运算符、逻辑运算符等。

类别 操作符 示例 描述
算术运算符 + a + b 加法,将两个数字相加
- a - b 减法,从第一个数字中减去第二个数字
* a * b 乘法,将两个数字相乘
/ a / b 除法,将第一个数字除以第二个数字
% a % b 取余,返回第一个数字除以第二个数字的余数
比较运算符 == a == b 等于,判断两个值是否相等
!= a != b 不等于,判断两个值是否不相等
> a > b 大于,判断第一个值是否大于第二个值
< a < b 小于,判断第一个值是否小于第二个值
>= a >= b 大于等于,判断第一个值是否大于或等于第二个值
<= a <= b 小于等于,判断第一个值是否小于或等于第二个值
逻辑运算符 && condition1 && condition2 逻辑与,两个条件都为真时结果为真
|| condition1 || condition2 逻辑或,两个条件中有一个为真时结果为真
! !condition 逻辑非,将真变为假,假变为真
三元运算符 ? : condition ? valueIfTrue : valueIfFalse 用于根据条件选择两个值中的一个

定义一个 Calculator 类,其中包含一些数字和一个用户对象,然后使用 QLExpress 进行一些简单的运算和条件判断。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

public class Calculator {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 设置变量
            context.put("a", 10);
            context.put("b", 5);

            // 算术运算示例
            executeAndPrint(runner, context, "a + b", "Addition");

            // 比较运算示例
            executeAndPrint(runner, context, "a > b", "Greater than");

            // 逻辑运算示例
            executeAndPrint(runner, context, "a > 0 && b > 0", "Logical AND");

            // 三元运算示例
            executeAndPrint(runner, context, "a > b ? 'a is greater' : 'b is greater'", "Ternary Operator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void executeAndPrint(ExpressRunner runner, DefaultContext<String, Object> context, String expression, String operation) throws Exception {
        // 执行脚本
        Object result = runner.execute(expression, context, null, true, false);

        // 输出结果
        System.out.println(operation + ": " + result);
    }
}

1.3.2 Java对象操作

基本的 Java 语法和对象操作在 QLExpress 中同样适用,演示在 QLExpress 中使用 Java 语法和进行对象操作:

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

public class QLExpressJavaSyntaxExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 创建一个用户对象
            User user = new User("John", 25);
            context.put("user", user);

            // 使用 Java 语法访问对象属性和调用方法
            executeAndPrint(runner, context, "user.getName()", "Accessing Object Property");
            executeAndPrint(runner, context, "user.getAge() + 5", "Performing Arithmetic with Object Property");

            // 使用 Java 语法进行对象操作
            executeAndPrint(runner, context, "user.age = 30", "Modifying Object Property");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void executeAndPrint(ExpressRunner runner, DefaultContext<String, Object> context, String expression, String operation) throws Exception {
        // 执行脚本
        Object result = runner.execute(expression, context, null, true, false);

        // 输出结果
        System.out.println(operation + ": " + result);
    }
    
    @Data
	@AllArgsConstructor
	@NoArgsConstructor
    // 用户对象类
    static class User {
        private String name;
        private int age;
	}
}

1.3.3 脚本中定义function

QLExpress中,可以通过 function 关键字来定义函数。以下是一个简单的示例:

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

public class QLExpressFunctionExample {
    public static void main(String[] args) {
        try {
            final String express = "function add(int a, int b){\n" +
                    "  return a + b;\n" +
                    "};\n" +
                    "\n" +
                    "function sub(int a, int b){\n" +
                    "  return a - b;\n" +
                    "};\n" +
                    "\n" +
                    "a = 10;\n" +
                    "result = add(a, 4) + sub(a, 9);\n" +
                    "return result;";
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 执行脚本
            Object result = runner.execute(express, context, null, true, false);

            // 输出脚本执行结果
            System.out.println("Result: " + result);

            // 输出函数调用过程中的参数和返回值
            System.out.println("add function call: a + 4 = " + context.get("a") + " + 4 = " + context.get("result"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.3.4 扩展操作符

QLExpress 中,可以通过自定义操作符(Operator)来扩展语言的功能。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

import java.util.ArrayList;
import java.util.List;

public class QLExpressOperatorExample {
    public static void main(String[] args) {
        try {
            // 示例 1:替换 ifthenelse 关键字
            ExpressRunner runner1 = new ExpressRunner();
            DefaultContext<String, Object> context1 = new DefaultContext<>();
            context1.put("语文",120);
            context1.put("数学",23);
            context1.put("英语",23);

            runner1.addOperatorWithAlias("如果", "if", null);
            runner1.addOperatorWithAlias("则", "then", null);
            runner1.addOperatorWithAlias("否则", "else", null);

            String express1 = "如果 (语文 + 数学 + 英语 > 270) 则 {return 1;} 否则 {return 0;}";
            Object result1 = runner1.execute(express1, context1, null, false, false, 100L);
            System.out.println("Result 1: " + result1); // 输出结果 1

            // 示例 2:自定义 Operator
            ExpressRunner runner2 = new ExpressRunner();
            DefaultContext<String, Object> context2 = new DefaultContext<>();

            // 自定义 Operator
            runner2.addOperator("join", new JoinOperator());

            // 示例 2.1:addOperator
            Object result2_1 = runner2.execute("1 join 2 join 3", context2, null, false, false);
            System.out.println("Result 2.1: " + result2_1); // 输出结果 [1, 2, 3]

            // 示例 2.2:replaceOperator
            ExpressRunner runner2_2 = new ExpressRunner();
            runner2_2.replaceOperator("+", new JoinOperator());
            Object result2_2 = runner2_2.execute("1 + 2 + 3", context2, null, false, false);
            System.out.println("Result 2.2: " + result2_2); // 输出结果 [1, 2, 3]

            // 示例 2.3:addFunction
            ExpressRunner runner2_3 = new ExpressRunner();
            runner2_3.addFunction("join", new JoinOperator());
            Object result2_3 = runner2_3.execute("join(1, 2, 3)", context2, null, false, false);
            System.out.println("Result 2.3: " + result2_3); // 输出结果 [1, 2, 3]

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // JoinOperator 类的定义
    publicstaticclass JoinOperator extends com.ql.util.express.Operator {
        public Object executeInner(Object[] list) throws Exception {
            Object opdata1 = list[0];
            Object opdata2 = list[1];
            if (opdata1 instanceof List) {
                ((List) opdata1).add(opdata2);
                return opdata1;
            } else {
                List result = new ArrayList();
                for (Object opdata : list) {
                    result.add(opdata);
                }
                return result;
            }
        }
    }
}

1.3.5 绑定Java类或对象的methon

QLExpress 中,可使用 addFunctionOfClassMethodaddFunctionOfServiceMethod 方法来绑定 Java 类或对象的方法。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

public class QLExpressFunctionBindingExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 绑定 Math 类的 abs 方法
            runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs", new String[]{"double"}, null);

            // 绑定 BeanExample 类的 upper 方法
            runner.addFunctionOfClassMethod("转换为大写", BeanExample.class.getName(), "upper", new String[]{"String"}, null);

            // 绑定 System.out 的 println 方法
            runner.addFunctionOfServiceMethod("打印", System.out, "println", new String[]{"String"}, null);

            // 绑定 BeanExample 对象的 anyContains 方法
            runner.addFunctionOfServiceMethod("contains", new BeanExample(), "anyContains", new Class[]{String.class, String.class}, null);

            String express = "取绝对值(-100); 转换为大写(\"hello world\"); 打印(\"你好吗?\"); contains(\"helloworld\", \"aeiou\")";
            Object result = runner.execute(express, context, null, false, false);

            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class BeanExample {
        public static double abs(double value) {
            System.out.println("取绝对值结果: " + value);
            return Math.abs(value);
        }

        public static String upper(String abc) {
            System.out.println("转换为大写结果: " + abc.toUpperCase());
            return abc.toUpperCase();
        }

        public boolean anyContains(String str, String searchStr) {
            char[] s = str.toCharArray();
            for (char c : s) {
                if (searchStr.contains(c + "")) {
                    returntrue;
                }
            }
            returnfalse;
        }
    }
}

1.3.6 宏定义(macro)

QLExpress中,宏定义(macro)允许将一个表达式片段命名为宏,并在其他地方引用这个宏。当需要在多个地方使用相同的复杂表达式时非常有用,可以提高代码的可读性和维护性。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
 
public class QLExpressMacroExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 定义宏
            runner.addMacro("计算平均成绩", "(语文+数学+英语)/3.0");
            runner.addMacro("是否优秀", "计算平均成绩>90");

            // 设置变量值
            context.put("语文", 88);
            context.put("数学", 99);
            context.put("英语", 95);

            // 执行表达式并打印结果
            Object result = runner.execute("是否优秀", context, null, false, false);
            System.out.println("Result: " + result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.3.7 编译脚本查询外部需要定义的变量和函数

QLExpress 中,编译脚本并查询外部需要定义的变量和函数可以通过 ExpressRunner 提供的一些方法来实现。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

public class QLExpressCompileExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner(true, true);
            DefaultContext<String, Object> context = new DefaultContext<>();
            context.put("语文",120);
            context.put("数学",23);
            context.put("英语",23);
            context.put("综合考试",235);

            // 定义脚本
            String express = "double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分";

            // 查询外部需要定义的变量
            String[] variableNames = runner.getOutVarNames(express);
            System.out.println("外部需要定义的变量:");
            for (String variableName : variableNames) {
                System.out.println("var : " + variableName);
            }

            // 查询外部需要定义的函数
            String[] functionNames = runner.getOutFunctionNames(express);
            System.out.println("\n外部需要定义的函数:");
            for (String functionName : functionNames) {
                System.out.println("function : " + functionName);
            }

            // 编译脚本并执行
            Object result = runner.execute(express, context, null, false, false);
            System.out.println("\n脚本执行结果: " + result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.3.8 不定参数的使用

在QLExpress中,可以通过使用不定参数(动态参数)来处理方法的参数。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.DynamicParamsUtil;
import com.ql.util.express.ExpressRunner;

public class QLExpressDynamicParamsExample {
    public static void main(String[] args) {
        try {
            // 创建 ExpressRunner 实例
            ExpressRunner runner = new ExpressRunner();

            // 创建 DefaultContext 实例
            DefaultContext<String, Object> expressContext = new DefaultContext<>();

            // 在 runner 中添加一个函数,使用不定参数
            runner.addFunctionOfServiceMethod("getTemplate", new QLExpressDynamicParamsExample(), "getTemplate",
                    new Class[]{Object[].class}, null);

            // 调用 getTemplate 方法,传递数组作为参数
            Object resultWithArray = runner.execute("getTemplate([11, '22', 33L, true])",
                    expressContext, null, false, false);
            System.out.println("Result with Array: " + resultWithArray);

            // 打开全局开关,启用动态参数调用
            DynamicParamsUtil.supportDynamicParams = true;

            // 调用 getTemplate 方法,传递多个参数
            Object resultWithDynamicParams = runner.execute("getTemplate(11, '22', 33L, true)", expressContext,
                    null, false, false);
            System.out.println("Result with Dynamic Params: " + resultWithDynamicParams);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 等价于 getTemplate(Object[] params)
    public Object getTemplate(Object... params) {
        StringBuilder result = new StringBuilder();
        for (Object obj : params) {
            result.append(obj).append(",");
        }
        return result.toString();
    }
}

1.3.9 集合的快捷用法

在QLExpress中,你可以使用一些快捷的语法来操作集合。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class QLExpressCollectionOperationsExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 使用NewMap创建Map
            String expressMap = "abc = NewMap(1:1, 2:2); return abc.get(1) + abc.get(2);";
            Object resultMap = runner.execute(expressMap, context, null, false, false);
            System.out.println("NewMap Result: " + resultMap);

            // 使用NewList创建List
            String expressList = "abc = NewList(1, 2, 3); return abc.get(1) + abc.get(2);";
            Object resultList = runner.execute(expressList, context, null, false, false);
            System.out.println("NewList Result: " + resultList);

            // 使用方括号[]创建List
            String expressSquareBrackets = "abc = [1, 2, 3]; return abc[1] + abc[2];";
            Object resultSquareBrackets = runner.execute(expressSquareBrackets, context, null, false, false);
            System.out.println("Square Brackets Result: " + resultSquareBrackets);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.3.10 集合的遍历

类似java的语法,只是ql不支持for(obj:list){}的语法,只能通过下标访问。

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;

import java.util.HashMap;
import java.util.Map;

public class QLExpressCollectionTraversalExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 创建一个Map
            Map<String, String> map = new HashMap<>();
            map.put("a", "a_value");
            map.put("b", "b_value");

            // 将Map放入上下文中
            context.put("map", map);

            // 遍历Map
            String express = "keySet = map.keySet();\n" +
                    "objArr = keySet.toArray();\n" +
                    "for (i = 0; i < objArr.length; i++) {\n" +
                    "    key = objArr[i];\n" +
                    "    System.out.println(map.get(key));\n" +
                    "}";
            runner.execute(express, context, null, false, false);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

参考链接:https://zyfcodes.blog.csdn.net/article/details/134363363

1.4 与SpringBoot对接

在 Spring Boot 中,通常将 ExpressRunner 声明为 Bean,方便全局调用。

1.4.1 配置 QLExpress Bean

1.4.1.1 直接初始化(简单场景)

import com.ql.util.express.ExpressRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QLExpressConfig {
    
    @Bean
    public ExpressRunner expressRunner() {
        ExpressRunner runner = new ExpressRunner();
        // 可选:设置缓存模式(默认开启)
        runner.setShortCircuit(true);
        return runner;
    }
}

1.4.1.2 自定义配置(高级场景)

如果需要 预加载函数安全控制,可以扩展配置:

@Bean
public ExpressRunner expressRunner() {
    ExpressRunner runner = new ExpressRunner(true, true); // 开启缓存和短计算
    // 注册自定义函数
    runner.addFunction("double", params -> (Integer) params[0] * 2);
    // 限制可访问的类(安全)
    runner.addOperatorWithAlias("危险操作", "dangerOp", null);
    return runner;
}

1.4.2 在 Service 中使用 QLExpress

1.4.2.1 执行简单表达式

import com.ql.util.express.ExpressRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RuleService {
    
    @Autowired
    private ExpressRunner expressRunner;

    public Object executeRule(String expression) throws Exception {
        // 绑定变量
        DefaultContext<String, Object> context = new DefaultContext<>();
        context.put("a", 10);
        context.put("b", 20);
        
        // 执行脚本
        return expressRunner.execute(expression, context, null, false, false);
    }
}

测试调用

@RestController
@RequestMapping("/rule")
public class RuleController {
    
    @Autowired
    private RuleService ruleService;

    @GetMapping("/calculate")
    public String calculate(@RequestParam String expr) throws Exception {
        Object result = ruleService.executeRule(expr);
        return "Result: " + result;
    }
}

1.4.2.2 动态规则引擎(如风控规则)

假设需要根据用户积分判断等级:

public String evaluateUserLevel(int score) throws Exception {
    String rule = 
        "if (score >= 90) { " +
        "    return 'VIP'; " +
        "} else if (score >= 60) { " +
        "    return 'Normal'; " +
        "} else { " +
        "    return 'Restricted'; " +
        "}";
    
    DefaultContext<String, Object> context = new DefaultContext<>();
    context.put("score", score);
    
    return (String) expressRunner.execute(rule, context, null, false, false);
}

1.4.3 高级用法

1.4.3.1 注册 Spring Bean 为 QLExpress 函数

如果想让 QLExpress 调用 Spring 管理的 Bean:

@Service
public class MathService {
    public int add(int a, int b) {
        return a + b;
    }
}

@Bean
public ExpressRunner expressRunner(MathService mathService) {
    ExpressRunner runner = new ExpressRunner();
    // 将 Spring Bean 注册为 QLExpress 函数
    runner.addFunction("add", mathService::add);
    return runner;
}

1.4.3.2 安全控制(限制可访问的类)

防止恶意代码调用危险方法:

runner.addPreciseMethod(
    "System", // 类名
    "exit",   // 方法名
    null,     // 方法对象(null表示禁止调用)
    null
);

1.4.3.3 结合数据库动态加载规则

@Service
public class DynamicRuleService {
    
    @Autowired
    private ExpressRunner expressRunner;
    @Autowired
    private RuleRepository ruleRepository; // 假设存储规则脚本

    public Object executeRule(Long ruleId, Map<String, Object> params) throws Exception {
        String ruleScript = ruleRepository.findById(ruleId).getScript();
        DefaultContext<String, Object> context = new DefaultContext<>();
        context.putAll(params);
        return expressRunner.execute(ruleScript, context, null, false, false);
    }
}
posted @ 2025-04-11 09:50  上善若泪  阅读(157)  评论(0)    收藏  举报