@@ -44,19 +44,51 @@ assertEquals(7, result);
4444
4545## 添加自定义函数与操作符
4646
47+ 最简单的方式是通过 Java Lambda 表达式快速定义函数/操作符的逻辑:
48+
4749``` java
4850Express4Runner express4Runner = new Express4Runner (InitOptions . DEFAULT_OPTIONS );
49- // custom function
51+ // 自定义函数
5052express4Runner. addVarArgsFunction(" join" , params - >
5153 Arrays . stream(params). map(Object :: toString). collect(Collectors . joining(" ," )));
5254Object resultFunction = express4Runner. execute(" join(1,2,3)" , Collections . emptyMap(), QLOptions . DEFAULT_OPTIONS );
5355assertEquals(" 1,2,3" , resultFunction);
5456
55- // custom operator
57+ // 自定义操作符
5658express4Runner. addOperatorBiFunction(" join" , (left, right) - > left + " ," + right);
5759Object resultOperator = express4Runner. execute(" 1 join 2 join 3" , Collections . emptyMap(), QLOptions . DEFAULT_OPTIONS );
5860assertEquals(" 1,2,3" , resultOperator);
5961```
62+
63+ 如果自定义函数的逻辑比较复杂,或者需要获得脚本的上下文信息,也可以通过继承 ` CustomFunction ` 的方式实现。
64+
65+ 比如下面的 ` hello ` 自定义函数,根据租户不同,返回不同的欢迎信息:
66+
67+ ``` java
68+ import com.alibaba.qlexpress4.runtime.Parameters ;
69+ import com.alibaba.qlexpress4.runtime.QContext ;
70+ import com.alibaba.qlexpress4.runtime.function.CustomFunction ;
71+
72+ public class HelloFunction implements CustomFunction {
73+ @Override
74+ public Object call (QContext qContext , Parameters parameters ) throws Throwable {
75+ String tenant = (String ) qContext. attachment(). get(" tenant" );
76+ return " hello," + tenant;
77+ }
78+ }
79+ ```
80+
81+ ``` java
82+ Express4Runner express4Runner = new Express4Runner (InitOptions . DEFAULT_OPTIONS );
83+ express4Runner. addFunction(" hello" , new HelloFunction ());
84+ String resultJack = (String ) express4Runner. execute(" hello()" , Collections . emptyMap(), QLOptions . builder()
85+ .attachments(Collections . singletonMap(" tenant" , " jack" )). build());
86+ assertEquals(" hello,jack" , resultJack);
87+ String resultLucy = (String ) express4Runner. execute(" hello()" , Collections . emptyMap(), QLOptions . builder()
88+ .attachments(Collections . singletonMap(" tenant" , " lucy" )). build());
89+ assertEquals(" hello,lucy" , resultLucy);
90+ ```
91+
6092## 高精度计算
6193
6294QLExpress 内部会用 BigDecimal 表示所有无法用 double 精确表示数字,来尽可能地表示计算精度:
@@ -151,6 +183,133 @@ express4Runner.execute("1+2", new HashMap<>(), QLOptions.builder()
151183 .cache(true ). build());
152184```
153185
186+ ## 扩展函数
187+
188+ 利用 QLExpress 提供的扩展函数能力,可以给Java类中添加额外的成员方法。
189+
190+ 扩展函数是基于 QLExpress 运行时实现的,因此仅仅在 QLExpress 脚本中有效。
191+
192+ 下面的示例代码给 String 类添加了一个 ` hello() ` 扩展函数:
193+
194+ ``` java
195+ ExtensionFunction helloFunction = new ExtensionFunction () {
196+ @Override
197+ public Class<?> [] getParameterTypes () {
198+ return new Class [0 ];
199+ }
200+
201+ @Override
202+ public String getName () {
203+ return " hello" ;
204+ }
205+
206+ @Override
207+ public Class<?> getDeclaringClass () {
208+ return String . class;
209+ }
210+
211+ @Override
212+ public Object invoke (Object obj , Object [] args ) throws InvocationTargetException , IllegalAccessException {
213+ String originStr = (String ) obj;
214+ return " Hello," + originStr;
215+ }
216+ };
217+ Express4Runner express4Runner = new Express4Runner (InitOptions . builder()
218+ .addExtensionFunctions(Collections . singletonList(helloFunction))
219+ .build());
220+ Object result = express4Runner. execute(" 'jack'.hello()" , Collections . emptyMap(), QLOptions . DEFAULT_OPTIONS );
221+ assertEquals(" Hello,jack" , result);
222+ ```
223+
224+ ## 对象,字段和函数别名
225+
226+ QLExpress 支持通过 ` QLAlias ` 注解给对象,字段或者函数定义一个或多个别名,方便非技术人员使用表达式定义规则。
227+
228+ 下面的例子中,根据用户是否 vip 计算订单最终金额。
229+
230+ 用户类定义:
231+
232+ ``` java
233+ import com.alibaba.qlexpress4.annotation.QLAlias ;
234+
235+ @QLAlias (" 用户" )
236+ public class User {
237+
238+ @QLAlias (" 是vip" )
239+ private boolean vip;
240+
241+ @QLAlias (" 用户名" )
242+ private String name;
243+
244+ public boolean isVip () {
245+ return vip;
246+ }
247+
248+ public void setVip (boolean vip ) {
249+ this . vip = vip;
250+ }
251+
252+ public String getName () {
253+ return name;
254+ }
255+
256+ public void setName (String name ) {
257+ this . name = name;
258+ }
259+ }
260+ ```
261+
262+ 订单类定义:
263+
264+ ``` java
265+ import com.alibaba.qlexpress4.annotation.QLAlias ;
266+
267+ @QLAlias (" 订单" )
268+ public class Order {
269+
270+ @QLAlias (" 订单号" )
271+ private String orderNum;
272+
273+ @QLAlias (" 金额" )
274+ private int amount;
275+
276+ public String getOrderNum () {
277+ return orderNum;
278+ }
279+
280+ public void setOrderNum (String orderNum ) {
281+ this . orderNum = orderNum;
282+ }
283+
284+ public int getAmount () {
285+ return amount;
286+ }
287+
288+ public void setAmount (int amount ) {
289+ this . amount = amount;
290+ }
291+ }
292+ ```
293+
294+ 通过 QLExpress 脚本规则计算最终订单金额:
295+
296+ ``` java
297+ Order order = new Order ();
298+ order. setOrderNum(" OR123455" );
299+ order. setAmount(100 );
300+
301+ User user = new User ();
302+ user. setName(" jack" );
303+ user. setVip(true );
304+
305+ // Calculate the Final Order Amount
306+ Express4Runner express4Runner = new Express4Runner (InitOptions . builder()
307+ .securityStrategy(QLSecurityStrategy . open()). build());
308+ Number result = (Number ) express4Runner. executeWithAliasObjects(" 用户.是vip? 订单.金额 * 0.8 : 订单.金额" ,
309+ QLOptions . DEFAULT_OPTIONS , order, user);
310+ assertEquals(80 , result. intValue());
311+ ```
312+
154313# 语法入门
155314
156315QLExpress4 兼容 Java8 语法的同时,也提供了很多更加灵活宽松的语法模式,帮助用户更快捷地编写表达式。
@@ -206,6 +365,26 @@ QLExpress会根据其所属范围自动从 int, long, BigInteger, BigDecimal 等
206365
207366因此在自定义函数或者操作符时,建议使用 Number 类型进行接收,因为数字类型是无法事先确定的。
208367
368+ ## 动态字符串
369+
370+ QLExpress 支持 ` ${expression$} ` 的格式在字符串中插入表达式,更方便地进行动态字符串组装:
371+
372+ ``` qlexpress
373+ a = 123;
374+ // Output: hello,122
375+ "hello,${a-1$}"
376+ ```
377+
378+ ``` qlexpress
379+ b = "test"
380+ // Output: m xx YYY
381+ "m xx ${
382+ if (b like 't%') {
383+ "YYY"
384+ }
385+ $}"
386+ ```
387+
209388## 分号
210389
211390表达式语句可以省略结尾的分号,整个脚本的返回值就是最后一个表达式的计算结果。
@@ -324,15 +503,84 @@ try {
324503}
325504```
326505
506+ ### 函数定义
507+
508+ 以下脚本的输出为 2:
509+
510+ ``` qlexpress
511+ function sub(a, b) {
512+ return a-b;
513+ }
514+ sub(3,1)
515+ ```
516+
517+ ### Lambda表达式
518+
519+ QLExpress4 中,Lambda 表达式作为一等公民,可以作为变量进行传递或者返回。
520+
521+ 以下脚本的输出为 3:
522+
523+ ``` qlexpress
524+ add = (a, b) -> {
525+ return a + b;
526+ };
527+ add(1,2)
528+ ```
529+
530+ ### 列表过滤和映射
531+
532+ 支持通过 filter, map 方法直接对列表类型进行函数式过滤和映射。
533+ 底层通过在列表类型添加 [ 扩展函数] ( #扩展函数 ) 实现,注意和 [ Stream Api] ( #stream-api ) 中同名方法区分。
534+ 相比 Stream Api,它可以直接对列表进行操作,返回值也直接就是列表,更加方便。
535+
536+ ``` qlexpress
537+ l = ["a-111", "a-222", "b-333", "c-888"]
538+ // Output: ["111", "222"]
539+ l.filter(i -> i.startsWith("a-")).map(i -> i.split("-")[1]);
540+ ```
541+
542+ ### 兼容 Java8 语法
543+
544+ QLExpress 可以兼容 Java8 的常见语法。
545+
546+ 比如 [ for-each循环] ( #for-each-循环 ) , Stream 语法, 函数式接口等等。
547+
548+ ### Stream Api
549+
550+ 可以直接使用 Java 集合中的 stream api 对集合进行操作。
551+
552+ 因为此时的 stream api 都是来自 Java 中的方法,参考 [ 调用应用中的 Java 类] ( #调用应用中的-java-类 ) 打开安全选项,以下脚本才能正常执行。
553+
554+ ``` qlexpress
555+ l = ["a-111", "a-222", "b-333", "c-888"]
556+
557+ // Output: ["111", "222"]
558+ l.stream()
559+ .filter(i -> i.startsWith("a-"))
560+ .map(i -> i.split("-")[1])
561+ .collect(Collectors.toList())
562+ ```
563+
564+ ### 函数式接口
565+
566+ Java8 中引入了 Function, Consumer, Predicate 等函数式接口,QLExpress 中的 [ Lambda表达式] ( #lambda表达式 ) 可以赋值给这些接口,
567+ 或者作为接收这些接口的方法参数:
568+
569+ ``` qlexpress
570+ Supplier s = () -> "test";
571+ // Output: test
572+ s.get()
573+ ```
574+
327575# 附录一 QLExpress4性能提升
328576
329577[ QLExpress4与3性能对比] ( https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B )
330578
331- 总结:常见场景下,无缓存时 ,QLExpress4能比3有接近10倍性能提升;有缓存 ,也有一倍性能提升。
579+ 总结:常见场景下,无编译缓存时 ,QLExpress4能比3有接近10倍性能提升;有编译缓存 ,也有一倍性能提升。
332580
333581# 附录二 开发者联系方式
334582
335- - email: tianqiao @alibaba-inc.com,baoxingjie@126 .com
583+ - email: qinyuan . dqy @alibaba-inc.com ,yumin.pym@taobao.com , 704643716@qq .com
336584 - wechat:
337585 - xuanheng: dqy932087612
338586 - binggou: pymbupt
0 commit comments