QLExpress基本语法

一、背景介绍

由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。
在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。

QLExpress脚本引擎被广泛应用在阿里的电商业务场景,具有以下的一些特性:

  • 1、线程安全,引擎运算过程中的产生的临时变量都是threadlocal类型。
  • 2、高效执行,比较耗时的脚本编译过程可以缓存在本地机器,运行时的临时变量创建采用了缓冲池的技术,和groovy性能相当。
  • 3、弱类型脚本语言,和groovy,javascript语法类似,虽然比强类型脚本语言要慢一些,但是使业务的灵活度大大增强。
  • 4、安全控制,可以通过设置相关运行参数,预防死循环、高危系统api调用等情况。
  • 5、代码精简,依赖最小,250k的jar包适合所有java的运行环境,在android系统的低端pos机也得到广泛运用。

二、依赖和调用说明

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.2.0</version>Valine
</dependency>
1
2
3
4
5
6
7
8
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
context.put("a", 1);
context.put("b", 2);
context.put("c", 3);
String express = "a + b * c";
Object r = runner.execute(express, context, null, true, false);
System.out.println(r);

三、项目使用

ExpressService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public interface ExpressService {

/**
* 执行表达式计算
* @param expressString 表达式
* @param context 计算上下文
* @return 计算结果
*/
Object execute(String expressString, Map<String, Object> context);

/**
* 执行表达式计算
* @param expressString 表达式
* @param valueParam 用以填充的值对象,只支持对象,不能是集合
* @return 计算结果
*/
Object execute(String expressString, Object valueParam);

}

@Component
public class ExpressServiceImpl implements ExpressService {

private final static Logger LOGGER = LoggerFactory.getLogger(ExpressServiceImpl.class);

private final static ExpressRunner expressRunner;

static {
DynamicParamsUtil.supportDynamicParams = true;
ExpressRunner runner = new ExpressRunner(true, false);
// Class[] cls = new Class[]{String.class,Integer.class,Integer.class};
// runner.addFunctionOfClassMethod(ExpressOperationConstants.SUB_STRING,
// ExpressStringUtils.class.getName(),
// ExpressOperationConstants.CUSTOM_SUB_STRING,
// cls,"substring method error");
expressRunner = runner;
}


/**
* 执行表达式计算
* @param expressString 表达式
* @param context 计算上下文
* @return 计算结果
*/
@Override
public Object execute(String expressString, Map<String, Object> context) {
Assert.notNull(context,"method execute param context is null");
Object value = null;
if(StringUtils.isNotBlank(expressString)){
DefaultContext<String, Object> contextMap = new DefaultContext<String, Object>();
for(Map.Entry<String,Object> entry : context.entrySet()){
contextMap.put(entry.getKey(), entry.getValue());
}
try{
value = expressRunner.execute(expressString, contextMap, null, true, false);
}catch (Exception e){
LOGGER.error("call expressRunner error expressString is : {}, context :{}", expressString, context, e);
throw new RuntimeException(String.format("表达式{%s}计算错误", expressString));
}
}
return value;
}

/**
* 执行表达式计算
* @param expressString 表达式
* @param valueParam 用以填充的值对象,只支持对象,不能是集合
* @return 计算结果
*/
@Override
public Object execute(String expressString, Object valueParam) {
Assert.notNull(valueParam,"method execute context valueParam is null");

Map<String, Object> map = new ObjectMapper().convertValue(valueParam, new TypeReference<Map<String, Object>>() {});
return this.execute(expressString,map);
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class ExpressServiceTest {

private final static Logger LOGGER = LoggerFactory.getLogger(ExpressServiceTest.class);

@Autowired
private ExpressService expressService;

@Test
public void testExecute0(){
String express = "3452";

Map<String, Object> map = new HashMap<>(4);
map.put("amount1",0.01);
map.put("amount2",1.10);

Object value = this.expressService.execute(express, map);

LOGGER.info("输出类型:{} 结果:{}", value.getClass(), value);

value = this.expressService.execute(express, map);

LOGGER.info("输出类型:{} 结果:{}", value.getClass(), value);


}


@Test
public void testExecute1(){
String express = "(amount1+amount2)*100";

Map<String, Object> map = new HashMap<>(4);
map.put("amount1",0.01);
map.put("amount2",1.10);

Object value = this.expressService.execute(express, map);

LOGGER.info("输出类型:{} 结果:{}", value.getClass(), value);

Assert.assertEquals(new BigDecimal("111.00"), value);
}

}