Backend Development 30 min read

QLExpress: A Lightweight Dynamic Script Engine for Fast and Flexible Business Rule Configuration

This article introduces QLExpress, Alibaba's open‑source lightweight dynamic script engine, explains its thread‑safe and high‑performance features, compares it with other rule engines, and provides comprehensive Java examples covering integration, syntax, custom operators, functions, macros, dynamic parameters, collection handling, and traversal for enterprise rule management.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
QLExpress: A Lightweight Dynamic Script Engine for Fast and Flexible Business Rule Configuration

In modern business systems, fast and flexible rule configuration and dynamic decision‑making are key to improving response speed and intelligence; Alibaba's open‑source QLExpress engine offers a lightweight, efficient, and concise solution for dynamic processing of complex business logic.

1. Quick Overview of QLExpress

QLExpress (Quick Language Express) originated from Alibaba's e‑commerce business to solve dynamic script parsing for rules, expressions, and mathematical calculations.

Thread‑safe: Uses ThreadLocal temporary variables to ensure safety in concurrent scenarios.

High‑performance execution: Caches compiled script results locally and employs a buffer pool for temporary variables, comparable to Groovy.

Weakly‑typed script language: Syntax similar to Groovy and JavaScript, offering flexibility at a slight performance cost.

Security control: Provides runtime parameters to prevent infinite loops or dangerous API calls.

Minimal dependencies: The JAR is only 250 KB, suitable for any Java environment, including low‑end Android POS devices.

These characteristics make QLExpress a powerful tool widely used in Alibaba's e‑commerce scenarios.

2. Comparison with Common Rule Engines

Drools is suited for complex business rules, while Aviator and QLExpress excel at simpler expression calculations and rule handling. EasyRule targets very simple rules for non‑technical users. The final choice depends on rule complexity, performance needs, developer skill, and specific project context.

3. Quick Reference and General Working Principle

1. Dependency and Basic Demonstration

To use QLExpress in a Maven project, add the following dependency to pom.xml :

<dependencies>
  <dependency>
    <groupId>com.ql</groupId>
    <artifactId>qlExpress</artifactId>
    <version>3.2.2<!-- use actual version --></version>
  </dependency>
</dependencies>

The following example calculates a discounted amount using QLExpress :

package org.zyf.javabasic.qlexpress;

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

/**
 * Demonstrates how to use QLExpress to calculate a discounted amount
 */
public class QLExpressExample {
    public static void main(String[] args) {
        try {
            // Create QLExpress engine
            ExpressRunner runner = new ExpressRunner();
            // Create context and set variables
            DefaultContext
context = new DefaultContext<>();
            context.put("amount", 1000);
            context.put("discount", 0.1);
            // Execute script
            String expression = "amount * (1 - discount)";
            Object result = runner.execute(expression, context, null, true, false);
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. General Working Principle Explanation

QLExpress first performs lexical and syntactic analysis to build an abstract syntax tree (AST). The AST is then compiled into executable instructions. During execution, a DefaultContext supplies variables and functions, allowing scripts to access and manipulate data dynamically.

Syntax‑tree analysis: Parses the script into a tree structure.

Context: Holds variables, functions, and execution parameters.

Execution process: Consists of compilation (generating instruction sequence) and runtime execution.

Extension points include custom operators/functions, modifying execution flow, customizing compilation, and extending context functionality.

4. Basic Syntax Learning

1. Operators

QLExpress supports arithmetic, comparison, logical, and other operators. The following image lists the supported operators:

2. Java Object Operations

Standard Java syntax and object manipulation can be used inside QLExpress . The example below creates a User object and accesses its properties:

package org.zyf.javabasic.qlexpress;

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

/**
 * Demonstrates basic Java syntax and object operations in QLExpress
 */
public class QLExpressJavaSyntaxExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext
context = new DefaultContext<>();
            // Create a user object
            User user = new User("John", 25);
            context.put("user", user);
            // Access object properties and methods
            executeAndPrint(runner, context, "user.getName()", "Accessing Object Property");
            executeAndPrint(runner, context, "user.getAge() + 5", "Performing Arithmetic with Object Property");
            // Modify object property
            executeAndPrint(runner, context, "user.age = 30", "Modifying Object Property");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static void executeAndPrint(ExpressRunner runner, DefaultContext
context, String expression, String operation) throws Exception {
        Object result = runner.execute(expression, context, null, true, false);
        System.out.println(operation + ": " + result);
    }
    // User class definition
    static class User {
        private String name;
        private int age;
        public User(String name, int age) { this.name = name; this.age = age; }
        public String getName() { return name; }
        public int getAge() { return age; }
    }
}

3. Defining Functions in Scripts

Functions can be defined using the function keyword. The example defines add and sub functions and uses them:

package org.zyf.javabasic.qlexpress;

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

public class QLExpressFunctionExample {
    public static void main(String[] args) {
        try {
            String express = "function add(int a, int b){\n  return a + b;\n};\n\nfunction 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
context = new DefaultContext<>();
            Object result = runner.execute(express, context, null, true, false);
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. Extending Operators

Custom operators can be added by implementing Operator . The following example replaces the if‑then‑else keywords with Chinese aliases and defines a join operator:

package org.zyf.javabasic.qlexpress;

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.Operator;
import java.util.ArrayList;
import java.util.List;

public class QLExpressOperatorExample {
    public static void main(String[] args) {
        try {
            // Alias example
            ExpressRunner runner1 = new ExpressRunner();
            DefaultContext
ctx1 = new DefaultContext<>();
            ctx1.put("语文", 120);
            ctx1.put("数学", 23);
            ctx1.put("英语", 23);
            runner1.addOperatorWithAlias("如果", "if", null);
            runner1.addOperatorWithAlias("则", "then", null);
            runner1.addOperatorWithAlias("否则", "else", null);
            String expr1 = "如果 (语文 + 数学 + 英语 > 270) 则 {return 1;} 否则 {return 0;}";
            Object r1 = runner1.execute(expr1, ctx1, null, false, false, 100L);
            System.out.println("Result 1: " + r1);
            // Custom join operator example
            ExpressRunner runner2 = new ExpressRunner();
            DefaultContext
ctx2 = new DefaultContext<>();
            runner2.addOperator("join", new JoinOperator());
            Object r2_1 = runner2.execute("1 join 2 join 3", ctx2, null, false, false);
            System.out.println("Result 2.1: " + r2_1);
            // Replace '+' with join
            ExpressRunner runner2_2 = new ExpressRunner();
            runner2_2.replaceOperator("+", new JoinOperator());
            Object r2_2 = runner2_2.execute("1 + 2 + 3", ctx2, null, false, false);
            System.out.println("Result 2.2: " + r2_2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // Custom join operator implementation
    public static class JoinOperator extends Operator {
        @Override
        public Object executeInner(Object[] list) throws Exception {
            Object op1 = list[0];
            Object op2 = list[1];
            if (op1 instanceof List) {
                ((List) op1).add(op2);
                return op1;
            } else {
                List result = new ArrayList();
                for (Object o : list) result.add(o);
                return result;
            }
        }
    }
}

5. Binding Java Class or Object Methods

Methods can be bound using addFunctionOfClassMethod or addFunctionOfServiceMethod . The example binds Math.abs , a custom BeanExample.upper , and System.out.println :

package org.zyf.javabasic.qlexpress;

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
ctx = new DefaultContext<>();
            // Bind Math.abs as "取绝对值"
            runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs", new String[]{"double"}, null);
            // Bind BeanExample.upper as "转换为大写"
            runner.addFunctionOfClassMethod("转换为大写", BeanExample.class.getName(), "upper", new String[]{"String"}, null);
            // Bind System.out.println as "打印"
            runner.addFunctionOfServiceMethod("打印", System.out, "println", new String[]{"String"}, null);
            // Bind BeanExample.anyContains as "contains"
            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, ctx, null, false, false);
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    static class BeanExample {
        public static double abs(double v) { System.out.println("取绝对值结果: " + v); return Math.abs(v); }
        public static String upper(String s) { System.out.println("转换为大写结果: " + s); return s.toUpperCase(); }
        public boolean anyContains(String str, String search) {
            for (char c : str.toCharArray()) {
                if (search.contains(String.valueOf(c))) return true;
            }
            return false;
        }
    }
}

6. Macro Definition

Macros allow naming a reusable expression fragment. The example defines a macro for average score calculation and checks if the score is excellent:

package org.zyf.javabasic.qlexpress;

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
ctx = new DefaultContext<>();
            runner.addMacro("计算平均成绩", "(语文+数学+英语)/3.0");
            runner.addMacro("是否优秀", "计算平均成绩>90");
            ctx.put("语文", 88);
            ctx.put("数学", 99);
            ctx.put("英语", 95);
            Object result = runner.execute("是否优秀", ctx, null, false, false);
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

7. Compiling Scripts and Querying External Variables/Functions

The engine can analyze a script to list variables and functions that must be provided externally before execution:

package org.zyf.javabasic.qlexpress;

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
ctx = new DefaultContext<>();
            ctx.put("语文", 120);
            ctx.put("数学", 23);
            ctx.put("英语", 23);
            ctx.put("综合考试", 235);
            String expr = "double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分";
            String[] vars = runner.getOutVarNames(expr);
            System.out.println("External variables:");
            for (String v : vars) System.out.println("var : " + v);
            String[] funcs = runner.getOutFunctionNames(expr);
            System.out.println("External functions:");
            for (String f : funcs) System.out.println("function : " + f);
            Object result = runner.execute(expr, ctx, null, false, false);
            System.out.println("Script result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

8. Using Variable‑Length (Dynamic) Parameters

Dynamic parameters enable a method to accept an arbitrary number of arguments. The example shows both array‑style and var‑args calls:

package org.zyf.javabasic.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 runner = new ExpressRunner();
            DefaultContext
ctx = new DefaultContext<>();
            // Bind method with var‑args
            runner.addFunctionOfServiceMethod("getTemplate", new QLExpressDynamicParamsExample(), "getTemplate", new Class[]{Object[].class}, null);
            Object r1 = runner.execute("getTemplate([11, '22', 33L, true])", ctx, null, false, false);
            System.out.println("Result with Array: " + r1);
            DynamicParamsUtil.supportDynamicParams = true;
            Object r2 = runner.execute("getTemplate(11, '22', 33L, true)", ctx, null, false, false);
            System.out.println("Result with Dynamic Params: " + r2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public Object getTemplate(Object... params) {
        StringBuilder sb = new StringBuilder();
        for (Object o : params) sb.append(o).append(",");
        return sb.toString();
    }
}

9. Shortcut Collection Creation

QLExpress provides NewMap , NewList , and array‑style literals for quick collection creation:

package org.zyf.javabasic.qlexpress;

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

public class QLExpressCollectionOperationsExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext
ctx = new DefaultContext<>();
            // NewMap example
            String mapExpr = "abc = NewMap(1:1, 2:2); return abc.get(1) + abc.get(2);";
            System.out.println("NewMap Result: " + runner.execute(mapExpr, ctx, null, false, false));
            // NewList example
            String listExpr = "abc = NewList(1, 2, 3); return abc.get(1) + abc.get(2);";
            System.out.println("NewList Result: " + runner.execute(listExpr, ctx, null, false, false));
            // Square‑bracket list literal
            String sqExpr = "abc = [1, 2, 3]; return abc[1] + abc[2];";
            System.out.println("Square Brackets Result: " + runner.execute(sqExpr, ctx, null, false, false));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

10. Collection Traversal

QLExpress does not support Java‑style for(obj : list){} . Traversal must be done via index access, as shown below:

package org.zyf.javabasic.qlexpress;

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
ctx = new DefaultContext<>();
            Map
map = new HashMap<>();
            map.put("a", "a_value");
            map.put("b", "b_value");
            ctx.put("map", map);
            String expr = "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(expr, ctx, null, false, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Conclusion

The article provides a comprehensive introduction to QLExpress , an open‑source lightweight dynamic script engine from Alibaba, highlighting its thread‑safety, performance, weak‑type flexibility, and minimal dependencies. By comparing it with other rule engines and presenting extensive code samples, readers can quickly adopt QLExpress for dynamic rule configuration, complex calculations, and real‑time decision making in enterprise applications.

Future work may focus on further optimizations, richer extensions, and tighter integration with other platforms, making QLExpress an even more attractive tool for developers seeking flexible and efficient rule management.

Enterprise‑Level Practical Summary – 40 Lectures

Recommended: a new booklet by Chen summarizing 40 core backend pain points (JVM, databases, performance tuning, etc.) with practical solutions. Original price 99 CNY, now discounted to 11.9 CNY for a permanent license. Scan the QR code below to subscribe.

Scan the QR code to view the article directory
Javarule enginetutorialQLExpressDynamic Scripting
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.