1. QLExpress入门:从零开始认识脚本引擎
第一次接触QLExpress时,我被它的简洁语法和强大功能惊艳到了。这个由阿里开源的脚本引擎,最初是为了解决电商业务中复杂的规则计算问题而设计的。想象一下,当你在电商平台看到"满300减40"的促销规则时,背后可能就是QLExpress在实时计算是否符合优惠条件。
安装QLExpress只需要一个简单的Maven依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>QLExpress</artifactId> <version>3.2.0</version> </dependency>基础使用只需要几行代码:
ExpressRunner runner = new ExpressRunner(); DefaultContext<String, Object> context = new DefaultContext<>(); context.put("a", 10); context.put("b", 20); String express = "a + b * 2"; Object result = runner.execute(express, context, null, true, false); System.out.println(result); // 输出50QLExpress有几个显著特点让它从众多脚本引擎中脱颖而出:
- 轻量高效:整个jar包只有250KB左右,即使在Android低端设备上也能流畅运行
- 弱类型设计:不需要声明变量类型,写起来像JavaScript一样灵活
- 线程安全:所有临时变量都使用ThreadLocal存储,避免并发问题
- 安全控制:可以预防死循环、限制危险API调用等
2. 基础语法全掌握:像写Java一样写脚本
2.1 基本运算符与表达式
QLExpress支持大多数Java常用的运算符,包括:
- 算术运算:
+ - * / % ++ -- - 比较运算:
> >= < <= == != - 逻辑运算:
&& || ! - 三元运算:
条件 ? 值1 : 值2
一个典型的表达式示例:
// 计算BMI指数 height = 1.75; weight = 68; bmi = weight / (height * height); healthStatus = bmi > 24 ? "超重" : (bmi < 18.5 ? "偏瘦" : "正常");2.2 流程控制语句
QLExpress支持常见的流程控制结构,但语法上有一些差异:
// if-else语句 if(score >= 90) { grade = "A"; } else if(score >= 80) { grade = "B"; } else { grade = "C"; } // for循环 sum = 0; for(i=1; i<=100; i++) { sum += i; } // while循环 n = 10; factorial = 1; while(n > 0) { factorial *= n; n--; }需要注意的是,QLExpress不支持Java 8的lambda表达式,也不支持增强for循环。遍历集合时需要使用传统下标方式:
// 遍历List list = [1, 2, 3, 4, 5]; for(i=0; i<list.size(); i++) { item = list.get(i); println(item); } // 遍历Map map = {"key1":"value1", "key2":"value2"}; keys = map.keySet().toArray(); for(i=0; i<keys.length; i++) { key = keys[i]; println(map.get(key)); }3. 高级特性:让脚本更强大的技巧
3.1 自定义函数与操作符
QLExpress允许你扩展自己的函数和操作符,这是它最强大的特性之一。
定义函数:
function calculateTax(income) { if(income <= 5000) { return 0; } else if(income <= 8000) { return (income - 5000) * 0.03; } else { return 3000 * 0.03 + (income - 8000) * 0.1; } } // 使用函数 monthlyIncome = 15000; tax = calculateTax(monthlyIncome);自定义操作符:
// 定义一个连接操作符 public class JoinOperator extends Operator { public Object executeInner(Object[] list) throws Exception { List<Object> result = new ArrayList<>(); for(Object item : list) { result.add(item); } return result; } } // 注册并使用操作符 runner.addOperator("join", new JoinOperator()); result = runner.execute("1 join 2 join 3", context, null, false, false); // 结果将是[1, 2, 3]3.2 绑定Java方法与类
QLExpress可以无缝调用Java方法和类:
// 绑定静态方法 runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs", new String[]{"double"}, null); // 绑定实例方法 List<String> sampleList = new ArrayList<>(); runner.addFunctionOfServiceMethod("添加元素", sampleList, "add", new Class[]{Object.class}, null); // 绑定系统方法 runner.addFunctionOfServiceMethod("打印", System.out, "println", new String[]{"String"}, null); // 使用绑定的方法 express = "取绝对值(-100); 添加元素('test'); 打印('Hello QLExpress')"; runner.execute(express, context, null, false, false);3.3 宏定义:简化复杂表达式
宏定义相当于给复杂表达式起别名,特别适合业务规则中重复使用的逻辑:
// 定义宏 runner.addMacro("是否VIP用户", "userLevel >= 3 && totalSpend > 10000"); runner.addMacro("计算折扣", "是否VIP用户 ? 0.85 : (totalSpend > 5000 ? 0.9 : 1.0)"); // 使用宏 context.put("userLevel", 4); context.put("totalSpend", 12000); Object discount = runner.execute("计算折扣", context, null, false, false); // 结果将是0.854. 实战技巧与性能优化
4.1 安全与风险控制
在实际使用中,我们需要特别注意脚本执行的安全性:
// 防止死循环 runner.execute("while(true){}", context, null, true, false, 1000); // 设置1秒超时 // 禁止危险方法调用 QLExpressRunStrategy.setForbiddenInvokeSecurityRiskMethods(true); try { runner.execute("System.exit(0)", context, null, true, false); } catch (QLException e) { System.out.println("阻止了危险操作: " + e.getMessage()); }4.2 性能优化建议
启用缓存:脚本编译比较耗时,应该开启缓存
runner.execute(express, context, null, true, false); // 最后一个true表示使用缓存合理使用高精度计算:对于财务等需要精确计算的场景
runner.setIsPrecise(true); // 启用高精度模式控制日志输出:生产环境应该关闭trace日志
runner.setIsTrace(false); // 关闭详细执行日志
4.3 与Spring集成
QLExpress可以很好地与Spring框架集成:
public class SpringContext extends HashMap<String, Object> implements IExpressContext<String, Object> { private ApplicationContext applicationContext; public SpringContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override public Object get(Object name) { Object result = super.get(name); if(result == null && applicationContext.containsBean((String)name)) { result = applicationContext.getBean((String)name); } return result; } } // 使用示例 SpringContext context = new SpringContext(applicationContext); context.put("param1", "value1"); runner.execute("service.doSomething(param1)", context, null, true, false);5. 实际业务场景应用案例
5.1 电商促销规则引擎
// 定义促销规则宏 runner.addMacro("满减规则", "(totalAmount >= 300 ? 40 : 0) + " + "(contains(categoryList, '电子产品') ? totalAmount * 0.05 : 0)"); // 使用规则 context.put("totalAmount", 500); context.put("categoryList", Arrays.asList("服装", "电子产品")); discount = runner.execute("满减规则", context, null, false, false); // 结果将是40 + 25 = 655.2 金融风控规则判断
// 定义风控规则函数 function riskCheck(user) { if(user.blacklist) return "拒绝"; if(user.creditScore < 60) return "高风险"; if(user.income < 5000 && user.loanAmount > 10000) return "中等风险"; return "低风险"; } // 使用规则 context.put("user", new User(/*...*/)); riskLevel = runner.execute("riskCheck(user)", context, null, false, false);5.3 动态表单校验规则
// 定义校验规则 runner.addMacro("校验手机号", "phone != null && phone.matches('^1[3-9]\\d{9}$')"); runner.addMacro("校验邮箱", "email != null && email.matches('^\\w+@\\w+\\.[a-z]{2,3}$')"); // 执行校验 context.put("phone", "13800138000"); context.put("email", "test@example.com"); isValid = runner.execute("校验手机号 && 校验邮箱", context, null, false, false);在项目中使用QLExpress后,我发现它特别适合处理频繁变化的业务规则。有一次,我们的促销策略需要从"满300减40"临时调整为"满200减30",只需要修改脚本内容而无需重新发布应用,整个过程只花了不到5分钟就完成了线上更新。这种灵活性在传统硬编码方式中是难以实现的。