Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
Q

QLExpress

> 编程语言
Open source

QLExpress is a powerful, lightweight, dynamic language for the Java platform aimed at improving developers’ productivity in different business scenes.

5.6K stars0 likes0 views
WebsiteGitHub

About

QLExpress is a powerful, lightweight, dynamic language for the Java platform aimed at improving developers’ productivity in different business scenes.

:toc:

= QLExpress

image::images/logo.png[]

【中文版】| link:README-EN.adoc[[English]]

image::https://api.star-history.com/svg?repos=alibaba/QLExpress&type=Date[Star History Chart]

== 背景介绍

由阿里的电商业务规则演化而来的嵌入式Java动态脚本工具,在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。

在基本的表达式计算的基础上,还有以下特色:

  • 灵活的自定义能力,通过 Java API 自定义函数和操作符,可以快速实现业务规则的 DSL
  • 兼容最新的 Java 语法,方便 Java 程序员快速熟悉。熟悉类 C 语言的业务人员使用起来也会非常顺手
  • 原生支持 JSON,快捷定义复杂数据结构
  • 友好的报错提示,无论是编译还是运行时错误,都能精确友好地提示错误位置
  • 独一无二的表达式追踪功能,可以追踪表达式在中间节点计算的值,方便业务人员或者 AI 对线上规则的计算结果进行归因分析
  • 默认安全,脚本默认不允许和应用代码进行交互,如果需要交互,也可以自行定义安全的交互方式
  • 解释执行,不占用 JVM 元空间,可以开启缓存提升解释性能
  • 代码精简,依赖最小,适合所有java的运行环境

QLExpress4 作为 QLExpress 的最新演进版本,基于 Antlr4 重写了解析引擎,将原先的优点进一步发扬光大,新增了大量特色功能,彻底拥抱函数式编程,在性能和表达能力上都进行了进一步增强。

如果项目还是使用旧版本的 QLExpress,可以跳转 link:https://github.com/alibaba/QLExpress/tree/branch_version_3.x.x[branch_version_3.x.x] 维护分支查看旧版文档。如需升级可以参考 link:#附录一-升级指南[附录一-升级指南]

场景举例:

  • 电商优惠券规则配置:通过 QLExpress 自定义函数和操作符快速实现优惠规则 DSL,供运营人员根据需求自行动态配置
  • 表单搭建控件关联规则配置:表单搭建平台允许用户拖拽控件搭建自定义的表单,利用 QLExpress 脚本配置不同控件间的关联关系
  • 流程引擎条件规则配置
  • 广告系统计费规则配置

......

== 新版特色

新版本并不是对旧版本的简单功能重构,而是我们基于对用户需求的洞察,对下一代规则表达式引擎的探索。拥有许多非常实用,但是在其他引擎中缺失的重要功能。

=== 表达式计算追踪

在业务人员完成规则脚本的配置后,很难对其线上执行情况进行感知。比如电商的促销规则,要求用户满足规则 isVip && 未登录10天以上。到底有多少线上用户是被 vip 条件拦截,又有多少用户是因为登录条件被拦截?这还是只是仅仅两个条件的简单规则,实际线上情况则更加复杂。

线上规则执行情况的追踪,不仅仅可以帮助业务人员了解线上的实际情况,排查和修复问题。其沉淀的数据也非常有价值,可以用于后续的规则优化和业务决策。以下是某个规则平台,基于 QLExpress4 的表达式追踪能力,对规则进行归因分析与附注的决策的产品简化图:

image::images/order_rules_cn.png[]

归因分析的原理在于利用 QLExpress4 的表达式追踪能力,获得表达式在计算过程中每个中间结果的值, 据此判断表达式最终运行结果产生的原因。

具体使用方法参考:link:#表达式计算追踪-1[表达式计算追踪]

=== 原生支持 JSON 语法

QLExpress4 原生支持 JSON 语法,可以快捷定义复杂的数据结构。

JSON 数组代表列表(List),而 JSON 对象代表映射(Map),也可以直接定义复杂对象。

产品上可以基于该特性实现 JSON 映射规则。让用户可以便捷地定义从一个模型向另一个模型的映射关系。以下是某个规则平台,基于该能力实现的模型映射产品简化图:

image::images/json_map.png[]

具体使用方法参考:link:#方便语法元素[方便语法元素]

=== 便捷字符串处理

QLExpress4 对字符串处理能力进行针对性的增强,在字符串中可以直接通过 $\{expression} 嵌入表达式计算结果。

具体使用方法参考:link:#动态字符串[动态字符串]

=== 附件透传

正常情况下,脚本执行需要的全部信息都在 context 中。context 中的 key 可以在脚本中作为变量引用,最终传递给自定义函数或者操作符。

但是出于安全,或者方便使用等因素考虑。有些信息并不希望用户通过变量引用到,比如租户名,密码等等。

此时可以通过附件(attachments)将这部分信息传递给自定义函数或者操作符使用。

具体使用方法参考:link:#添加自定义函数与操作符[添加自定义函数与操作符] 其中 hello 自定义函数根据附件中租户不同,返回不同的欢迎信息的示例。

=== 函数式编程

函数被提升为 QLExpress4 中的第一等公民,可以作为变量使用,也可以作为函数的返回值。并且可以很容易地和 Java 中常见的函数式 API(比如 Stream) 结合使用。

以下是一个简单的 QLExpress 示例脚本:

[source,java]

add = (a, b) -> { return a + b; }; i = add(1,2); assert(i == 3);

更多使用方法参考:

  • link:#lambda-表达式[Lambda表达式]
  • link:#列表过滤和映射[列表过滤和映射]
  • link:#stream-api[Stream API]
  • link:#函数式接口[函数式接口]

=== 分号简化

QLExpress4 支持省略分号,让表达式更加简洁。具体参考 link:#分号[分号]

== API 快速入门

=== 引入依赖

[source,xml,subs="attributes+"]

com.alibaba qlexpress4 4.1.3 ----

环境要求:

  • JDK 8 或更高版本

=== 第一个 QLExpress 程序

[source,java,indent=0]

    Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    Map<String, Object> context = new HashMap<>();
    context.put("a", 1);
    context.put("b", 2);
    context.put("c", 3);
    Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult();
    assertEquals(7, result);

更多的表达式执行方式见文档 link:docs/execute.adoc[表达式执行]

=== 添加自定义函数与操作符

最简单的方式是通过 Java Lambda 表达式快速定义函数/操作符的逻辑:

[source,java,indent=0]

    Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    // custom function
    express4Runner.addVarArgsFunction("join",
        params -> Arrays.stream(params).map(Object::toString).collect(Collectors.joining(",")));
    Object resultFunction =
        express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
    assertEquals("1,2,3", resultFunction);
    
    // custom operator
    express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
    Object resultOperator =
        express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
    assertEquals("1,2,3", resultOperator);

如果自定义函数的逻辑比较复杂,或者需要获得脚本的上下文信息,也可以通过继承 CustomFunction 的方式实现。

比如下面的 hello 自定义函数,根据租户不同,返回不同的欢迎信息:

[source,java,indent=0]

package com.alibaba.qlexpress4.test.function;

import com.alibaba.qlexpress4.runtime.Parameters; import com.alibaba.qlexpress4.runtime.QContext; import com.alibaba.qlexpress4.runtime.function.CustomFunction;

public class HelloFunction implements CustomFunction { @Override public Object call(QContext qContext, Parameters parameters) throws Throwable { String tenant = (String)qContext.attachment().get("tenant"); return "hello," + tenant; }

}

[source,java,indent=0]

    Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    express4Runner.addFunction("hello", new HelloFunction());
    String resultJack = (String)express4Runner.execute("hello()",
        Collections.emptyMap(),
        // Additional information(tenant for example) can be brought into the custom function from outside via attachments
        QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult();
    assertEquals("hello,jack", resultJack);
    String resultLucy =
        (String)express4Runner
            .execute("hello()",
                Collections.emptyMap(),
                QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build())
            .getResult();
    assertEquals("hello,lucy", resultLucy);

QLExpress4还支持通过QLExpress脚本添加自定义函数。需要注意的是,在函数外定义的变量(如示例中的defineTime)在函数定义时就已初始化完成,后续调用函数时不会重新计算该变量的值。

[source,java,indent=0]

    Express4Runner express4Runner =
        new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build());
    BatchAddFunctionResult addResult = express4Runner.addFunctionsDefinedInScript(
        "function myAdd(a,b) {\n" + "    return a+b;" + "}\n" + "\n" + "function getCurrentTime() {\n"
            + "    return System.currentTimeMillis();\n" + "}" + "\n" + "defineTime=System.currentTimeMillis();\n"
            + "function defineTime() {\n" + "    return defineTime;" + "}\n",
        ExpressContext.EMPTY_CONTEXT,
        QLOptions.DEFAULT_OPTIONS);
    assertEquals(3, addResult.getSucc().size());
    QLResult result = express4Runner.execute("myAdd(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
    assertEquals(3, result.getResult());
    
    QLResult resultCurTime1 =
        express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
    Thread.sleep(1000);
    QLResult resultCurTime2 =
        express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
    assertNotSame(resultCurTime1.getResult(), resultCurTime2.getResult());
    
    /*
     * The defineTime variable is defined outside the function and is initialized when the function is defined;
     * it is not recalculated afterward, so the value returned is always the time at which the function was defined.
     */
    QLResult resultDefineTime1 =
        express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
    Thread.sleep(1000);
    QLResult resultDefineTime2 =
        express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
    assertSame(resultDefineTime1.getResult(), resultDefineTime2.getResult());

建议尽可能使用Java方式定义自定义函数,这样可以获得更好的性能和稳定性。

==== 延迟参数求值函数(LazyArgCustomFunction)

默认情况下,QLExpress 在调用函数前会先对所有参数完成求值。如果某个参数的计算会产生副作用(如除零异常),即使函数逻辑上不需要该参数,也会在调用前触发错误。

实现 LazyArgCustomFunction 接口的函数可以控制参数的求值时机。编译器默认会将每个参数包装为 QLambda,也可以通过 isLazyArg(int argIndex) 指定部分参数,函数内部通过调用 QLambda.get() 手动触发求值,从而实现短路求值语义。

[source,java,indent=0]

    Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    express4Runner.addFunction("IF", new LazyArgCustomFunction() {
        private static final int PARAM_LENGTH = 3;
        
        @Override
        public boolean isLazyArg(int argIndex) {
            return 1 == argIndex || 2 == argIndex;
        }
        
        @Override
        public Object call(QContext qContext, Parameters parameters) {
            if (parameters == null || parameters.size() != PARAM_LENGTH) {
                throw new IllegalArgumentException("Invalid number of arguments");
            }
            Object v1 = call(parameters.getValue(0));
            if (!(v1 instanceof Boolean)) {
                throw new IllegalArgumentException("Argument 1 must be a boolean");
            }
            if ((Boolean)v1) {
                return call(parameters.getValue(1));
            }
            
            return call(parameters.getValue(2));
        }
        
        private Object call(Object obj) {
            if (obj instanceof QLambda) {
                return ((QLambda)obj).get();
            }
            return obj;
        }
    });

上例中,当 b == 0 条件成立时,第三个参数 a / b 不会被求值,因此不会触发除零异常。

更多自定义语法元素的方式见文档 link:docs/custom-item.adoc[自定义语法元素]

=== 校验语法正确性

在不执行脚本的情况下,单纯校验语法的正确性,其中包含了操作符的限制校验,调用 check 并且捕获异常,如果捕获到 QLSyntaxException,则说明存在语法错误

[source,java,indent=0]

    Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    try {
        express4Runner.check("a+b;\n(a+b");
        fail();
    }
    catch (QLSyntaxException e) {
        assertEquals(2, e.getLineNo());
        assertEquals(5, e.getColNo());
        assertEquals("SYNTAX_ERROR", e.getErrorCode());
        // <EOF> represents the end of script
        assertEquals(
            "[Error SYNTAX_ERROR: mismatched input '<EOF>' expecting ')']\n" + "[Near: a+b; (a+b<EOF>]\n"
                + "                ^^^^^\n" + "[Line: 2, Column: 5]",
            e.getMessage());
    }

你可以使用 CheckOptions 配置更精细的语法校验规则,主要支持以下两个选项:

  1. operatorCheckStrategy: 操作符校验策略,用于限制脚本中可以使用的操作符
  2. disableFunctionCalls: 是否禁用函数调用,默认为 false

示例1:使用操作符校验策略(白名单)

[source,java,indent=0]

    // Create a whitelist of allowed operators
    Set<String> allowedOps = new HashSet<>(Arrays.asList("+", "*"));
    
    // Configure check options with operator whitelist
    CheckOptions checkOptions =
        CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build();
    
    // Create runner and check script with custom options
    Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    runner.check("a + b * c", checkOptions); // This will pass as + and * are allowed

示例2:禁用函数调用

[source,java,indent=0]

    // Create a runner
    Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    
    // Create options with function calls disabled
    CheckOptions options = CheckOptions.builder().disableFunctionCalls(true).build();
    
    // Script with function call
    String scriptWithFunctionCall = "Math.max(1, 2)";
    
    // Use custom options to check script
    try {
        runner.check(scriptWithFunctionCall, options);
    }
    catch (QLSyntaxException e) {
        // Will throw exception as function calls are disabled
    }

=== 解析脚本所需外部变量

脚本中使用的变量有的是脚本内生,有的是需要从外部通过 context 传入的。

QLExpress4 提供了一个方法,可以解析出脚本中所有需要从外部传入的变量:

[source,java,indent=0]

    Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
    Set<String> outVarNames =
        express4Runner.g

GitHub Issues· 0 open

View all on GitHub

No open issues yet, or sync has not completed.

Highlights

  • •灵活的自定义能力,通过 Java API 自定义函数和操作符,可以快速实现业务规则的 DSL
  • •兼容最新的 Java 语法,方便 Java 程序员快速熟悉。熟悉类 C 语言的业务人员使用起来也会非常顺手
  • •原生支持 JSON,快捷定义复杂数据结构
  • •友好的报错提示,无论是编译还是运行时错误,都能精确友好地提示错误位置
  • •独一无二的表达式追踪功能,可以追踪表达式在中间节点计算的值,方便业务人员或者 AI 对线上规则的计算结果进行归因分析
  • •默认安全,脚本默认不允许和应用代码进行交互,如果需要交互,也可以自行定义安全的交互方式
  • •解释执行,不占用 JVM 元空间,可以开启缓存提升解释性能
  • •代码精简,依赖最小,适合所有java的运行环境
  • •电商优惠券规则配置:通过 QLExpress 自定义函数和操作符快速实现优惠规则 DSL,供运营人员根据需求自行动态配置
  • •表单搭建控件关联规则配置:表单搭建平台允许用户拖拽控件搭建自定义的表单,利用 QLExpress 脚本配置不同控件间的关联关系

> Tags

Java

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言