告別硬編碼:阿里開(kāi)源的動(dòng)態(tài)腳本引擎 QLExpress ,真香!
現(xiàn)代業(yè)務(wù)系統(tǒng)中,如何實(shí)現(xiàn)快速、靈活的規(guī)則配置和動(dòng)態(tài)決策,成為了企業(yè)提升響應(yīng)速度和智能化水平的關(guān)鍵。阿里巴巴開(kāi)源的 QLExpress 引擎,以其輕量、高效、簡(jiǎn)潔的優(yōu)勢(shì),為復(fù)雜業(yè)務(wù)邏輯的動(dòng)態(tài)處理提供了一種創(chuàng)新的解決方案。
無(wú)論是需要實(shí)時(shí)調(diào)整規(guī)則的電商促銷(xiāo),還是依賴(lài)規(guī)則動(dòng)態(tài)性的金融風(fēng)控,QLExpress 都能以其靈活的表達(dá)式和易用的規(guī)則配置,實(shí)現(xiàn)高效而精準(zhǔn)的業(yè)務(wù)決策。
如果你正在尋找一款強(qiáng)大、靈活的動(dòng)態(tài)腳本引擎,那么 QLExpress 可能正是你需要的工具!
一、QLExpress快速了解
QLExpress(Quick Language Express)是阿里巴巴開(kāi)源的一門(mén)動(dòng)態(tài)腳本引擎解析工具,起源于阿里巴巴的電商業(yè)務(wù),旨在解決業(yè)務(wù)規(guī)則、表達(dá)式、數(shù)學(xué)計(jì)算等動(dòng)態(tài)腳本的解析問(wèn)題。其具有以下基本特點(diǎn):
- 線(xiàn)程安全: QLExpress被設(shè)計(jì)為線(xiàn)程安全的動(dòng)態(tài)腳本引擎,它使用threadlocal類(lèi)型的臨時(shí)變量,確保在引擎運(yùn)算過(guò)程中的并發(fā)場(chǎng)景下的線(xiàn)程安全性。
- 高效執(zhí)行: 為了提高執(zhí)行效率,QLExpress在編譯過(guò)程中可以將比較耗時(shí)的腳本編譯結(jié)果緩存到本地機(jī)器。此外,運(yùn)行時(shí)的臨時(shí)變量創(chuàng)建采用了緩沖池技術(shù),以確保高效的運(yùn)行時(shí)性能,使其與一些性能優(yōu)秀的腳本引擎(如Groovy)相當(dāng)。
- 弱類(lèi)型腳本語(yǔ)言: QLExpress采用弱類(lèi)型腳本語(yǔ)言,語(yǔ)法類(lèi)似于Groovy和JavaScript。這使得業(yè)務(wù)規(guī)則的表達(dá)更加靈活,雖然相對(duì)于強(qiáng)類(lèi)型腳本語(yǔ)言可能略慢,但在業(yè)務(wù)的靈活性方面提供了很大的優(yōu)勢(shì)。
- 安全控制: QLExpress提供了一些運(yùn)行時(shí)參數(shù)的設(shè)置,以進(jìn)行安全控制。通過(guò)這些參數(shù),可以預(yù)防一些潛在的安全問(wèn)題,如死循環(huán)或?qū)Ω呶O到y(tǒng)API的調(diào)用。
- 代碼精簡(jiǎn)、依賴(lài)最?。?nbsp;QLExpress的設(shè)計(jì)追求代碼的精簡(jiǎn)和最小依賴(lài),其jar包大小為250k,適用于所有Java的運(yùn)行環(huán)境。這使得它在各種環(huán)境中都能輕松部署和運(yùn)行,包括在A(yíng)ndroid系統(tǒng)的低端POS機(jī)上廣泛應(yīng)用。
總體而言,這些特性使QLExpress成為一個(gè)在阿里電商業(yè)務(wù)場(chǎng)景中得到廣泛應(yīng)用的強(qiáng)大工具,具有高效、靈活和安全的特點(diǎn)。
二、QLExpress與常用規(guī)則引擎對(duì)比
圖片
Drools適用于復(fù)雜的業(yè)務(wù)規(guī)則,而Aviator和QLExpress適用于相對(duì)簡(jiǎn)單的表達(dá)式計(jì)算和規(guī)則。EasyRule更適合簡(jiǎn)單規(guī)則場(chǎng)景,特別是面向非專(zhuān)業(yè)開(kāi)發(fā)人員的情況。最終選擇取決于具體需求,包括業(yè)務(wù)規(guī)則的復(fù)雜性、性能要求、開(kāi)發(fā)人員技能水平以及項(xiàng)目的特定場(chǎng)景。
三、快速引用和一般工作原理簡(jiǎn)訴
1.引用與基本演示
在 Maven 項(xiàng)目中引入 QLExpress,需要在項(xiàng)目的 pom.xml 文件中添加相關(guān)的依賴(lài):
<dependencies>
<dependency>
<groupId>com.ql</groupId>
<artifactId>qlExpress</artifactId>
<version>3.2.2</version> <!-- 使用實(shí)際版本號(hào) -->
</dependency>
</dependencies>
以下展示簡(jiǎn)單演示如何使用 QLExpress 計(jì)算折扣后的金額。在實(shí)際項(xiàng)目中,可能需要更復(fù)雜的腳本和上下文,以適應(yīng)業(yè)務(wù)需求。
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: 演示如何使用 QLExpress 計(jì)算折扣后的金額
* @author: zhangyanfeng
**/
publicclass QLExpressExample {
public static void main(String[] args) {
try {
// 創(chuàng)建 QLExpress 引擎
ExpressRunner runner = new ExpressRunner();
// 創(chuàng)建上下文并設(shè)置變量
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("amount", 1000);
context.put("discount", 0.1);
// 執(zhí)行腳本
String expression = "amount * (1 - discount)";
Object result = runner.execute(expression, context, null, true, false);
// 輸出結(jié)果
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.一般工作原理說(shuō)明
QLExpress的一般工作原理,包括語(yǔ)法樹(shù)分析、上下文和執(zhí)行過(guò)程。
圖片
- 語(yǔ)法樹(shù)分析: QLExpress會(huì)先將輸入的腳本進(jìn)行詞法分析和語(yǔ)法分析,生成一棵語(yǔ)法樹(shù)。這個(gè)語(yǔ)法樹(shù)表示了腳本的結(jié)構(gòu),將其組織成可以被執(zhí)行的形式。
- 上下文: 在QLExpress中,上下文是一個(gè)關(guān)鍵概念。它是腳本執(zhí)行時(shí)的環(huán)境,包含變量、函數(shù)等信息。在執(zhí)行腳本之前,你可以向上下文中添加變量,定義函數(shù),設(shè)置一些執(zhí)行參數(shù)等。這樣,腳本執(zhí)行時(shí)可以引用上下文中的內(nèi)容。
- 執(zhí)行過(guò)程: QLExpress的執(zhí)行過(guò)程包括編譯和運(yùn)行兩個(gè)主要階段。在編譯階段,腳本被解析并生成可以執(zhí)行的指令序列。在運(yùn)行階段,這些指令被執(zhí)行,從而實(shí)現(xiàn)腳本的功能。
在這個(gè)框架下,二次定制的功能擴(kuò)展可以包括:
- 自定義操作符和函數(shù): 可以通過(guò)實(shí)現(xiàn)自定義的操作符和函數(shù),使得腳本能夠執(zhí)行特定的業(yè)務(wù)邏輯。
- 修改執(zhí)行流程: 可以在執(zhí)行階段插入自定義的邏輯,改變腳本執(zhí)行的流程。
- 定制編譯過(guò)程: 在編譯階段定制特定的優(yōu)化或變換,以滿(mǎn)足特殊需求。
- 擴(kuò)展上下文功能: 可以添加一些上下文的攔截器,使得在腳本執(zhí)行前后可以執(zhí)行額外的邏輯。
這樣的擴(kuò)展點(diǎn)允許更好地適應(yīng)特定的業(yè)務(wù)場(chǎng)景和需求。
四、基本語(yǔ)法學(xué)習(xí)
1.操作符
QLExpress 支持一系列操作符,包括算術(shù)運(yùn)算符、比較運(yùn)算符、邏輯運(yùn)算符等。
圖片
定義一個(gè) Calculator 類(lèi),其中包含一些數(shù)字和一個(gè)用戶(hù)對(duì)象,然后使用 QLExpress 進(jìn)行一些簡(jiǎn)單的運(yùn)算和條件判斷。
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: 一些簡(jiǎn)單的運(yùn)算和條件判斷
* @author: zhangyanfeng
**/
publicclass Calculator {
public static void main(String[] args) {
try {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
// 設(shè)置變量
context.put("a", 10);
context.put("b", 5);
// 算術(shù)運(yùn)算示例
executeAndPrint(runner, context, "a + b", "Addition");
// 比較運(yùn)算示例
executeAndPrint(runner, context, "a > b", "Greater than");
// 邏輯運(yùn)算示例
executeAndPrint(runner, context, "a > 0 && b > 0", "Logical AND");
// 三元運(yùn)算示例
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 {
// 執(zhí)行腳本
Object result = runner.execute(expression, context, null, true, false);
// 輸出結(jié)果
System.out.println(operation + ": " + result);
}
}
2.Java對(duì)象操作
基本的 Java 語(yǔ)法和對(duì)象操作在 QLExpress 中同樣適用,演示在 QLExpress 中使用 Java 語(yǔ)法和進(jìn)行對(duì)象操作:
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: 基本的 Java 語(yǔ)法和對(duì)象操作
* @author: zhangyanfeng
**/
publicclass QLExpressJavaSyntaxExample {
public static void main(String[] args) {
try {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
// 創(chuàng)建一個(gè)用戶(hù)對(duì)象
User user = new User("John", 25);
context.put("user", user);
// 使用 Java 語(yǔ)法訪(fǎng)問(wèn)對(duì)象屬性和調(diào)用方法
executeAndPrint(runner, context, "user.getName()", "Accessing Object Property");
executeAndPrint(runner, context, "user.getAge() + 5", "Performing Arithmetic with Object Property");
// 使用 Java 語(yǔ)法進(jìn)行對(duì)象操作
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 {
// 執(zhí)行腳本
Object result = runner.execute(expression, context, null, true, false);
// 輸出結(jié)果
System.out.println(operation + ": " + result);
}
// 用戶(hù)對(duì)象類(lèi)
staticclass User {
private String name;
privateint age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
3.腳本中定義function
在QLExpress中,可以通過(guò) function 關(guān)鍵字來(lái)定義函數(shù)。以下是一個(gè)簡(jiǎn)單的示例:
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: 通過(guò) function 關(guān)鍵字來(lái)定義函數(shù)
* @author: zhangyanfeng
**/
publicclass 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<>();
// 執(zhí)行腳本
Object result = runner.execute(express, context, null, true, false);
// 輸出腳本執(zhí)行結(jié)果
System.out.println("Result: " + result);
// 輸出函數(shù)調(diào)用過(guò)程中的參數(shù)和返回值
System.out.println("add function call: a + 4 = " + context.get("a") + " + 4 = " + context.get("result"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.擴(kuò)展操作符
在 QLExpress 中,可以通過(guò)自定義操作符(Operator)來(lái)擴(kuò)展語(yǔ)言的功能。
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import java.util.ArrayList;
import java.util.List;
/**
* @program: zyfboot-javabasic
* @description: 自定義操作符(Operator)來(lái)擴(kuò)展語(yǔ)言的功能
* @author: zhangyanfeng
**/
publicclass QLExpressOperatorExample {
public static void main(String[] args) {
try {
// 示例 1:替換 if then else 關(guān)鍵字
ExpressRunner runner1 = new ExpressRunner();
DefaultContext<String, Object> context1 = new DefaultContext<>();
context1.put("語(yǔ)文",120);
context1.put("數(shù)學(xué)",23);
context1.put("英語(yǔ)",23);
runner1.addOperatorWithAlias("如果", "if", null);
runner1.addOperatorWithAlias("則", "then", null);
runner1.addOperatorWithAlias("否則", "else", null);
String express1 = "如果 (語(yǔ)文 + 數(shù)學(xué) + 英語(yǔ) > 270) 則 {return 1;} 否則 {return 0;}";
Object result1 = runner1.execute(express1, context1, null, false, false, 100L);
System.out.println("Result 1: " + result1); // 輸出結(jié)果 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); // 輸出結(jié)果 [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); // 輸出結(jié)果 [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); // 輸出結(jié)果 [1, 2, 3]
} catch (Exception e) {
e.printStackTrace();
}
}
// JoinOperator 類(lèi)的定義
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;
}
}
}
}
5.綁定Java類(lèi)或?qū)ο蟮膍ethon
在 QLExpress 中,可使用 addFunctionOfClassMethod 和 addFunctionOfServiceMethod 方法來(lái)綁定 Java 類(lèi)或?qū)ο蟮姆椒ā?/p>
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: 可以使用 addFunctionOfClassMethod 和 addFunctionOfServiceMethod 方法來(lái)綁定 Java 類(lèi)或?qū)ο蟮姆椒? * @author: zhangyanfeng
* @create: 2023-11-19 16:13
**/
publicclass QLExpressFunctionBindingExample {
public static void main(String[] args) {
try {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
// 綁定 Math 類(lèi)的 abs 方法
runner.addFunctionOfClassMethod("取絕對(duì)值", Math.class.getName(), "abs", new String[]{"double"}, null);
// 綁定 BeanExample 類(lèi)的 upper 方法
runner.addFunctionOfClassMethod("轉(zhuǎn)換為大寫(xiě)", BeanExample.class.getName(), "upper", new String[]{"String"}, null);
// 綁定 System.out 的 println 方法
runner.addFunctionOfServiceMethod("打印", System.out, "println", new String[]{"String"}, null);
// 綁定 BeanExample 對(duì)象的 anyContains 方法
runner.addFunctionOfServiceMethod("contains", new BeanExample(), "anyContains", new Class[]{String.class, String.class}, null);
String express = "取絕對(duì)值(-100); 轉(zhuǎn)換為大寫(xiě)(\"hello world\"); 打印(\"你好嗎?\"); contains(\"helloworld\", \"aeiou\")";
Object result = runner.execute(express, context, null, false, false);
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
publicstaticclass BeanExample {
public static double abs(double value) {
System.out.println("取絕對(duì)值結(jié)果: " + value);
return Math.abs(value);
}
public static String upper(String abc) {
System.out.println("轉(zhuǎn)換為大寫(xiě)結(jié)果: " + 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;
}
}
}
6.宏定義(macro)
在QLExpress中,宏定義(macro)允許將一個(gè)表達(dá)式片段命名為宏,并在其他地方引用這個(gè)宏。當(dāng)需要在多個(gè)地方使用相同的復(fù)雜表達(dá)式時(shí)非常有用,可以提高代碼的可讀性和維護(hù)性。
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: macro 宏定義
* @author: zhangyanfeng
**/
publicclass QLExpressMacroExample {
public static void main(String[] args) {
try {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
// 定義宏
runner.addMacro("計(jì)算平均成績(jī)", "(語(yǔ)文+數(shù)學(xué)+英語(yǔ))/3.0");
runner.addMacro("是否優(yōu)秀", "計(jì)算平均成績(jī)>90");
// 設(shè)置變量值
context.put("語(yǔ)文", 88);
context.put("數(shù)學(xué)", 99);
context.put("英語(yǔ)", 95);
// 執(zhí)行表達(dá)式并打印結(jié)果
Object result = runner.execute("是否優(yōu)秀", context, null, false, false);
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
7.編譯腳本查詢(xún)外部需要定義的變量和函數(shù)
在QLExpress中,編譯腳本并查詢(xún)外部需要定義的變量和函數(shù)可以通過(guò) ExpressRunner 提供的一些方法來(lái)實(shí)現(xiàn)。
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: 編譯腳本,查詢(xún)外部需要定義的變量和函數(shù)。
* @author: zhangyanfeng
**/
publicclass QLExpressCompileExample {
public static void main(String[] args) {
try {
ExpressRunner runner = new ExpressRunner(true, true);
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("語(yǔ)文",120);
context.put("數(shù)學(xué)",23);
context.put("英語(yǔ)",23);
context.put("綜合考試",235);
// 定義腳本
String express = "double 平均分 = (語(yǔ)文 + 數(shù)學(xué) + 英語(yǔ) + 綜合考試) / 4.0; return 平均分";
// 查詢(xún)外部需要定義的變量
String[] variableNames = runner.getOutVarNames(express);
System.out.println("外部需要定義的變量:");
for (String variableName : variableNames) {
System.out.println("var : " + variableName);
}
// 查詢(xún)外部需要定義的函數(shù)
String[] functionNames = runner.getOutFunctionNames(express);
System.out.println("\n外部需要定義的函數(shù):");
for (String functionName : functionNames) {
System.out.println("function : " + functionName);
}
// 編譯腳本并執(zhí)行
Object result = runner.execute(express, context, null, false, false);
System.out.println("\n腳本執(zhí)行結(jié)果: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
看下對(duì)應(yīng)打印結(jié)果:
執(zhí)行的表達(dá)式:double 平均分 = (語(yǔ)文 + 數(shù)學(xué) + 英語(yǔ) + 綜合考試) / 4.0; return 平均分
單詞分解結(jié)果:{double},{平均分},{=},{(},{語(yǔ)文},{+},{數(shù)學(xué)},{+},{英語(yǔ)},{+},{綜合考試},{)},{/},{4.0},{;},{return},{平均分}
預(yù)處理后結(jié)果:{double},{平均分},{=},{(},{語(yǔ)文},{+},{數(shù)學(xué)},{+},{英語(yǔ)},{+},{綜合考試},{)},{/},{4.0},{;},{return},{平均分}
單詞分析結(jié)果:double:CONST_CLASS,平均分:ID,=:=,(:(,語(yǔ)文:ID,+:+,數(shù)學(xué):ID,+:+,英語(yǔ):ID,+:+,綜合考試:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
最后的語(yǔ)法樹(shù):
1: STAT_BLOCK:STAT_BLOCK STAT_BLOCK
2: STAT_SEMICOLON:STAT_SEMICOLON STAT_SEMICOLON
3: =:= =
4: def:def def
5: double:CONST_CLASS CONST_CLASS
5: 平均分:CONST_STRING CONST
4: /:/ /
5: (:CHILD_EXPRESS CHILD_EXPRESS
6: +:+ +
7: +:+ +
8: +:+ +
9: 語(yǔ)文:ID ID
9: 數(shù)學(xué):ID ID
8: 英語(yǔ):ID ID
7: 綜合考試:ID ID
5: 4.0:CONST_DOUBLE CONST
2: STAT_SEMICOLON:STAT_SEMICOLON STAT_SEMICOLON
3: return:returnreturn
4: 平均分:ID ID
1:LoadData Class:double
2:LoadData 平均分
3:OP : def OPNUMBER[2]
4:LoadAttr:語(yǔ)文
5:LoadAttr:數(shù)學(xué)
6:OP : + OPNUMBER[2]
7:LoadAttr:英語(yǔ)
8:OP : + OPNUMBER[2]
9:LoadAttr:綜合考試
10:OP : + OPNUMBER[2]
11:LoadData 4.0
12:OP : / OPNUMBER[2]
13:OP : = OPNUMBER[2]
14:clearDataStack
15:LoadAttr:平均分
16:return [value]
外部需要定義的變量:
var : 數(shù)學(xué)
var : 綜合考試
var : 英語(yǔ)
var : 語(yǔ)文
執(zhí)行的表達(dá)式:double 平均分 = (語(yǔ)文 + 數(shù)學(xué) + 英語(yǔ) + 綜合考試) / 4.0; return 平均分
單詞分解結(jié)果:{double},{平均分},{=},{(},{語(yǔ)文},{+},{數(shù)學(xué)},{+},{英語(yǔ)},{+},{綜合考試},{)},{/},{4.0},{;},{return},{平均分}
預(yù)處理后結(jié)果:{double},{平均分},{=},{(},{語(yǔ)文},{+},{數(shù)學(xué)},{+},{英語(yǔ)},{+},{綜合考試},{)},{/},{4.0},{;},{return},{平均分}
單詞分析結(jié)果:double:CONST_CLASS,平均分:ID,=:=,(:(,語(yǔ)文:ID,+:+,數(shù)學(xué):ID,+:+,英語(yǔ):ID,+:+,綜合考試:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
最后的語(yǔ)法樹(shù):
1: STAT_BLOCK:STAT_BLOCK STAT_BLOCK
2: STAT_SEMICOLON:STAT_SEMICOLON STAT_SEMICOLON
3: =:= =
4: def:def def
5: double:CONST_CLASS CONST_CLASS
5: 平均分:CONST_STRING CONST
4: /:/ /
5: (:CHILD_EXPRESS CHILD_EXPRESS
6: +:+ +
7: +:+ +
8: +:+ +
9: 語(yǔ)文:ID ID
9: 數(shù)學(xué):ID ID
8: 英語(yǔ):ID ID
7: 綜合考試:ID ID
5: 4.0:CONST_DOUBLE CONST
2: STAT_SEMICOLON:STAT_SEMICOLON STAT_SEMICOLON
3: return:returnreturn
4: 平均分:ID ID
1:LoadData Class:double
2:LoadData 平均分
3:OP : def OPNUMBER[2]
4:LoadAttr:語(yǔ)文
5:LoadAttr:數(shù)學(xué)
6:OP : + OPNUMBER[2]
7:LoadAttr:英語(yǔ)
8:OP : + OPNUMBER[2]
9:LoadAttr:綜合考試
10:OP : + OPNUMBER[2]
11:LoadData 4.0
12:OP : / OPNUMBER[2]
13:OP : = OPNUMBER[2]
14:clearDataStack
15:LoadAttr:平均分
16:return [value]
外部需要定義的函數(shù):
執(zhí)行的表達(dá)式:double 平均分 = (語(yǔ)文 + 數(shù)學(xué) + 英語(yǔ) + 綜合考試) / 4.0; return 平均分
單詞分解結(jié)果:{double},{平均分},{=},{(},{語(yǔ)文},{+},{數(shù)學(xué)},{+},{英語(yǔ)},{+},{綜合考試},{)},{/},{4.0},{;},{return},{平均分}
預(yù)處理后結(jié)果:{double},{平均分},{=},{(},{語(yǔ)文},{+},{數(shù)學(xué)},{+},{英語(yǔ)},{+},{綜合考試},{)},{/},{4.0},{;},{return},{平均分}
單詞分析結(jié)果:double:CONST_CLASS,平均分:ID,=:=,(:(,語(yǔ)文:ID,+:+,數(shù)學(xué):ID,+:+,英語(yǔ):ID,+:+,綜合考試:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
最后的語(yǔ)法樹(shù):
1: STAT_BLOCK:STAT_BLOCK STAT_BLOCK
2: STAT_SEMICOLON:STAT_SEMICOLON STAT_SEMICOLON
3: =:= =
4: def:def def
5: double:CONST_CLASS CONST_CLASS
5: 平均分:CONST_STRING CONST
4: /:/ /
5: (:CHILD_EXPRESS CHILD_EXPRESS
6: +:+ +
7: +:+ +
8: +:+ +
9: 語(yǔ)文:ID ID
9: 數(shù)學(xué):ID ID
8: 英語(yǔ):ID ID
7: 綜合考試:ID ID
5: 4.0:CONST_DOUBLE CONST
2: STAT_SEMICOLON:STAT_SEMICOLON STAT_SEMICOLON
3: return:returnreturn
4: 平均分:ID ID
1:LoadData Class:double
2:LoadData 平均分
3:OP : def OPNUMBER[2]
4:LoadAttr:語(yǔ)文
5:LoadAttr:數(shù)學(xué)
6:OP : + OPNUMBER[2]
7:LoadAttr:英語(yǔ)
8:OP : + OPNUMBER[2]
9:LoadAttr:綜合考試
10:OP : + OPNUMBER[2]
11:LoadData 4.0
12:OP : / OPNUMBER[2]
13:OP : = OPNUMBER[2]
14:clearDataStack
15:LoadAttr:平均分
16:return [value]
腳本執(zhí)行結(jié)果: 100.25
8.不定參數(shù)的使用
在QLExpress中,可以通過(guò)使用不定參數(shù)(動(dòng)態(tài)參數(shù))來(lái)處理方法的參數(shù)。
package org.zyf.javabasic.qlexpress;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.DynamicParamsUtil;
import com.ql.util.express.ExpressRunner;
/**
* @program: zyfboot-javabasic
* @description: 不定參數(shù)的使用
* @author: zhangyanfeng
**/
publicclass QLExpressDynamicParamsExample {
public static void main(String[] args) {
try {
// 創(chuàng)建 ExpressRunner 實(shí)例
ExpressRunner runner = new ExpressRunner();
// 創(chuàng)建 DefaultContext 實(shí)例
DefaultContext<String, Object> expressContext = new DefaultContext<>();
// 在 runner 中添加一個(gè)函數(shù),使用不定參數(shù)
runner.addFunctionOfServiceMethod("getTemplate", new QLExpressDynamicParamsExample(), "getTemplate",
new Class[]{Object[].class}, null);
// 調(diào)用 getTemplate 方法,傳遞數(shù)組作為參數(shù)
Object resultWithArray = runner.execute("getTemplate([11, '22', 33L, true])",
expressContext, null, false, false);
System.out.println("Result with Array: " + resultWithArray);
// 打開(kāi)全局開(kāi)關(guān),啟用動(dòng)態(tài)參數(shù)調(diào)用
DynamicParamsUtil.supportDynamicParams = true;
// 調(diào)用 getTemplate 方法,傳遞多個(gè)參數(shù)
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();
}
}
// 等價(jià)于 getTemplate(Object[] params)
public Object getTemplate(Object... params) {
StringBuilder result = new StringBuilder();
for (Object obj : params) {
result.append(obj).append(",");
}
return result.toString();
}
}
9.集合的快捷用法
在QLExpress中,你可以使用一些快捷的語(yǔ)法來(lái)操作集合。
package org.zyf.javabasic.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;
/**
* @program: zyfboot-javabasic
* @description: 集合操作
* @author: zhangyanfeng
**/
publicclass QLExpressCollectionOperationsExample {
public static void main(String[] args) {
try {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
// 使用NewMap創(chuàng)建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創(chuàng)建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);
// 使用方括號(hào)[]創(chuàng)建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();
}
}
}
10.集合的遍歷
類(lèi)似java的語(yǔ)法,只是ql不支持for(obj:list){}的語(yǔ)法,只能通過(guò)下標(biāo)訪(fǎng)問(wèn)。
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;
/**
* @program: zyfboot-javabasic
* @description: 使用foreach關(guān)鍵字結(jié)合索引遍歷集合
* @author: zhangyanfeng
**/
publicclass QLExpressCollectionTraversalExample {
public static void main(String[] args) {
try {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
// 創(chuàng)建一個(gè)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();
}
}
}
四、總結(jié)
本文全面介紹了 QLExpress 作為阿里巴巴開(kāi)源的一款輕量級(jí)動(dòng)態(tài)腳本引擎的主要功能和應(yīng)用場(chǎng)景,并深入講解了其在規(guī)則引擎和業(yè)務(wù)場(chǎng)景中的使用優(yōu)勢(shì)。通過(guò)對(duì)比其他規(guī)則引擎的性能與靈活性,QLExpress 展現(xiàn)了其在處理動(dòng)態(tài)規(guī)則、復(fù)雜計(jì)算邏輯、實(shí)時(shí)決策等方面的獨(dú)特優(yōu)勢(shì),尤其適用于需要?jiǎng)討B(tài)配置業(yè)務(wù)規(guī)則的場(chǎng)景。在實(shí)際應(yīng)用中,QLExpress 的簡(jiǎn)潔語(yǔ)法、快速上手和高性能表現(xiàn),使其成為了企業(yè)動(dòng)態(tài)業(yè)務(wù)處理的有效工具。
未來(lái),QLExpress 還可以進(jìn)一步優(yōu)化和拓展,以支持更多場(chǎng)景和定制化功能。對(duì)于希望在系統(tǒng)中靈活應(yīng)用規(guī)則邏輯、提升業(yè)務(wù)決策效率的開(kāi)發(fā)者來(lái)說(shuō),QLExpress 是一個(gè)值得關(guān)注和嘗試的工具。
希望通過(guò)本文的介紹,大家能更好地理解和應(yīng)用 QLExpress,從而在項(xiàng)目中實(shí)現(xiàn)更高效、更靈活的規(guī)則管理。