本文目录导读:

- 案例一:基础入门(Maven 依赖 & Hello World)
- 案例二:业务逻辑场景(电商价格计算)
- 案例三:规则引擎实战(风控系统)
- 案例四:高级特性(函数定义 & 宏 & 依赖注入)
- 案例五:性能优化与安全(编译缓存 & 沙箱)
- 总结:QLExpress 适用场景与最佳实践
QLExpress 是阿里巴巴开源的一款轻量级、高性能的规则引擎,主要用于解决业务规则复杂多变、灵活配置的问题,它采用 Java 语法(近似)编写,支持脚本化动态编译执行。
下面我将通过几个从简单到复杂的典型案例,展示 QLExpress 的核心用法、语法特性和实际业务场景。
基础入门(Maven 依赖 & Hello World)
这是最基础的用法,演示如何引入依赖并执行一个简单表达式。
引入 Maven 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.1</version> <!-- 请使用最新稳定版本 -->
</dependency>
基础执行代码
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
public class HelloWorld {
public static void main(String[] args) throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
// 执行简单的数学运算
Object r1 = runner.execute("1 + 2 * 3", context, null, true, false);
System.out.println("Result: " + r1); // 输出 7
// 带变量的表达式
context.put("a", 10);
context.put("b", 5);
Object r2 = runner.execute("a > b ? 'a更大' : 'b更大'", context, null, true, false);
System.out.println("Result: " + r2); // 输出 a更大
}
}
业务逻辑场景(电商价格计算)
这是最常见的业务场景——根据规则计算商品价格,包含条件分支、循环和方法调用。
业务规则:
- 普通会员:原价 9 折
- VIP 会员:原价 7 折,且满 500 减 50
- 如果商品库存不足,提示错误
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
public class PriceCalculator {
// 定义一个业务方法,供表达式调用
public static double calculateDiscount(int level, double price) {
if (level == 1) return price * 0.9;
if (level == 2) return price * 0.7;
return price;
}
public static void main(String[] args) throws Exception {
ExpressRunner runner = new ExpressRunner();
// 将 Java 方法注入到表达式中
runner.addFunction("calcDiscount", new com.ql.util.express.Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
int level = (Integer) list[0];
double price = (Double) list[1];
return calculateDiscount(level, price);
}
});
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
context.put("memberLevel", 2); // VIP
context.put("goodsPrice", 800.0);
context.put("stock", 10);
// 复杂表达式:包含条件、方法调用
String expression =
"if (stock < 1) { return '库存不足'; }" +
"else { " +
" double finalPrice = calcDiscount(memberLevel, goodsPrice);" +
" if (memberLevel == 2 && finalPrice >= 500) {" +
" finalPrice = finalPrice - 50;" +
" }" +
" return '应付金额: ' + finalPrice;" +
"}";
Object result = runner.execute(expression, context, null, true, false);
System.out.println(result); // 输出: 应付金额: 510.0 (800*0.7 - 50)
}
}
规则引擎实战(风控系统)
这是 QLExpress 最核心的使用场景——动态下发风控规则,无需重启应用即可快速调整规则。
场景:根据用户行为(登录地域、金额、频次)判断是否存在风险。
核心代码(规则加载与执行):
package com.example.rule;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class RiskControlEngine {
// 缓存规则,key为规则ID,value为规则表达式
private static Map<String, String> ruleCache = new ConcurrentHashMap<>();
private ExpressRunner runner = new ExpressRunner();
// 动态更新规则(通过后台管理系统调用)
public void updateRule(String ruleId, String expression) {
ruleCache.put(ruleId, expression);
System.out.println("规则已更新: " + ruleId);
}
// 执行风控检查
public boolean checkRisk(String ruleId, Map<String, Object> params) throws Exception {
String expression = ruleCache.get(ruleId);
if (expression == null) {
throw new IllegalArgumentException("规则不存在: " + ruleId);
}
DefaultContext<String, Object> context = new DefaultContext<>();
// 将请求参数注入上下文
params.forEach(context::put);
Object result = runner.execute(expression, context, null, true, false);
// 结果转为 boolean
return result instanceof Boolean && (Boolean) result;
}
public static void main(String[] args) throws Exception {
RiskControlEngine engine = new RiskControlEngine();
// 初始规则:如果交易金额 > 5000 且 新用户 且 IP 不在白名单,则风控
String rule1 =
"amount > 5000 && isNewUser && !whiteList.contains(ip) " +
"&& (loginCount < 3 || lastLoginDays > 30)";
engine.updateRule("RULE_HIGH_TRADE", rule1);
// 模拟请求参数
DefaultContext<String, Object> params1 = new DefaultContext<>();
params1.put("amount", 12000);
params1.put("isNewUser", true);
params1.put("ip", "192.168.1.100");
params1.put("loginCount", 2);
params1.put("lastLoginDays", 45);
params1.put("whiteList", java.util.Arrays.asList("127.0.0.1", "10.0.0.1"));
boolean risk1 = engine.checkRisk("RULE_HIGH_TRADE", params1);
System.out.println("是否触发风控: " + risk1); // 输出 true (金额大、新用户、登录少且间隔久)
// 动态修改规则(不重启应用)
String rule1New =
"amount > 10000 && isNewUser && !whiteList.contains(ip) " +
"&& (loginCount < 5 || lastLoginDays > 15)";
engine.updateRule("RULE_HIGH_TRADE", rule1New);
// 再次检测,规则已更新
boolean risk2 = engine.checkRisk("RULE_HIGH_TRADE", params1);
System.out.println("更新规则后是否触发风控: " + risk2);
// 因为 loginCount(2) < 5 依然为true,所以仍为 true
// 模拟一个业务人员调低阈值
DefaultContext<String, Object> params2 = new DefaultContext<>();
params2.put("amount", 8000);
params2.put("isNewUser", false);
params2.put("ip", "10.0.0.5");
params2.put("loginCount", 10);
params2.put("lastLoginDays", 2);
params2.put("whiteList", java.util.Arrays.asList("127.0.0.1"));
boolean risk3 = engine.checkRisk("RULE_HIGH_TRADE", params2);
System.out.println("低风险用户是否触发: " + risk3); // 输出 false (金额未超10000 且 非新用户)
}
}
高级特性(函数定义 & 宏 & 依赖注入)
QLExpress 支持在脚本中定义函数(function)、使用宏(macro)以及通过 Spring 注入 Bean,以下展示其强大扩展能力。
场景:物流运费计算 + 优惠券逻辑。
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
public class AdvancedUsage {
public static void main(String[] args) throws Exception {
ExpressRunner runner = new ExpressRunner();
// 1. 定义一个函数:计算运费(按重量阶梯计价)
runner.addFunction("calcFreight", new com.ql.util.express.Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
double weight = (Double) list[0];
double region = (Double) list[1]; // 地区系数
double base = (Double) list[2];
// 模拟:首重 1kg 内 base,超出后每kg 加 1.5
if (weight <= 1.0) {
return base * region;
} else {
return (base + (weight - 1.0) * 1.5) * region;
}
}
});
// 2. 定义宏(常量替换)
runner.addMacro("FREE_SHIPPING_THRESHOLD", 199.0);
runner.addMacro("VIP_DISCOUNT", 0.85);
// 3. 模拟 Spring Bean 注入(假设有个优惠券服务)
@SuppressWarnings("unused")
class CouponService {
public double getCouponAmount(double price, int couponId) {
return couponId == 1001 ? 20.0 : (couponId == 1002 ? 50.0 : 0.0);
}
}
Object couponBean = new CouponService() {
public Object executeInner(Object[] list) {
// 简化:直接暴露一个方法
return (Double) getCouponAmount((Double) list[0], (Integer) list[1]);
}
};
runner.addFunction("getCoupon", new com.ql.util.express.Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
return ((CouponService) list[0]).getCouponAmount((Double) list[1], (Integer) list[2]);
}
});
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("weight", 3.5);
context.put("region", 1.2); // 偏远地区
context.put("prodPrice", 250.0);
context.put("couponId", 1001);
context.put("couponService", couponBean);
// 复杂表达式:使用宏、函数、条件
String expression =
// 如果商品价格达到免邮门槛(199),运费为0,否则计算运费
"freight = prodPrice >= FREE_SHIPPING_THRESHOLD ? 0.0 : calcFreight(weight, region, 10);" +
// 应用 VIP 折扣和优惠券(注意:这里演示宏和函数)
"finalPrice = (prodPrice * VIP_DISCOUNT) - getCoupon(couponService, prodPrice, couponId);" +
// 自动取到变量结果
"total = finalPrice + freight;" +
"return '原价: ' + prodPrice + ', 折后: ' + finalPrice + ', 运费: ' + freight + ', 总价: ' + total;";
Object result = runner.execute(expression, context, null, true, false);
System.out.println(result);
// 输出: 原价: 250.0, 折后: 192.5, 运费: 0.0, 总价: 192.5
}
}
性能优化与安全(编译缓存 & 沙箱)
在生产环境中,我们通常希望提前编译表达式并限制脚本能力以保证性能和安全。
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
import java.util.ArrayList;
import java.util.List;
public class ProductionReady {
public static void main(String[] args) throws Exception {
// 开启沙箱模式,禁止某些危险操作(如 System.exit, Runtime.exec 等)
ExpressRunner runner = new ExpressRunner(true, true);
// 预编译表达式(提高性能)
String expression = "name == 'Alice' ? '欢迎' : '拒绝'";
com.ql.util.express.InstructionSet instructionSet = runner.parseInstructionSet(expression);
// 多线程池化使用
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int num = i;
Thread t = new Thread(() -> {
try {
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("name", num % 2 == 0 ? "Alice" : "Bob");
// 使用编译好的指令集,避免重复解析
Object result = runner.executeInstructionSet(instructionSet, context, null);
System.out.println(Thread.currentThread().getName() + ": " + result);
} catch (Exception e) {
e.printStackTrace();
}
});
threads.add(t);
t.start();
}
for (Thread t : threads) t.join();
}
}
QLExpress 适用场景与最佳实践
| 场景 | 是否推荐 | 说明 |
|---|---|---|
| 风控规则 | ✅ 强烈推荐 | 规则变更频繁,动态下发 |
| 营销活动(改折扣/满减) | ✅ 推荐 | 业务人员可配置 |
| 复杂计算引擎 | ✅ 推荐 | 如运费、积分、佣金计算 |
| 数据校验 | ✅ 可选 | 支持复杂条件 |
| 简单 if-else 映射 | ❌ 不推荐 | 直接用 Java 或枚举更简单 |
| 超高性能要求(QPS万级以上) | ⚠️ 谨慎 | 虽然支持预编译,但脚本执行仍有开销 |
最佳实践贴士:
- 预编译:在启动时将常用规则
parseInstructionSet,不要每次执行都解析。 - 白名单:在脚本中限制可调用方法,禁止
Runtime、System、Class等危险类。 - 上下文隔离:每次请求传入独立的
DefaultContext,避免并发数据污染。 - 监控:添加执行耗时日志和异常统计。
通过以上案例,你已经掌握了 QLExpress 从基础语法到生产级应用的核心技巧,希望你项目开发顺利!如果还有细节想了解(比如特定版本差异、SPEL 对比),可以继续交流。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播