codecamp

表达式引擎简介

为什么需要 EL 表达式引擎

你,对,就是你,正在看这篇文章的人,我虽然不认识你,但是 我可以负责任的说, 如果你看到这个标题就心里在悄悄的呼喊: “靠,他们连这个都有!我省事了,哇哈哈哈和”。 那么,你绝对属于百分之一的特例。 就是说,绝大多数人的绝大多数项目,是不需要 一个嵌入式的表达式引擎的。因此,提供这个功能的目的就是:

满足一小撮人的一小撮要求

但是,"一小撮人"的"一小撮要求"有很多,作为一个小众类库,为什么单单打算支持这个特性呢? 下面是我的理由:

  • 这个功能是其它模块功能的基础,我们需要它
  • 可能因此吸引其他的开发者对 Nutz 的兴趣
    • 需要嵌入式表达式引擎的人是 Java 开发者的少数人,但是这些人也应该比 Nutz 的用户要多
    • 这些人基本上编程水平要强一些
  • 其他的提交者对增加这个特性没有特别强烈的反对

近一步介绍表达式引擎

那么它怎么使用呢?

是的,我想这可能会是你脑海里闪出的第一个问题。并且,我想你真正想问的是:“它好用吗?”

如果你脑海里第一个问题不是这个,而是:“表达式引擎是神马东东?” 那么建议你不用阅读本文了, 反正你也用不着。等你需要的时候,再读也不迟,反正这篇文章又不长。

而关于 "好用",还有下面这三层含义:

它容易使用吗?

System.out.println(El.eval("3+4*5").equals(23));  // 将打印 true,够简单吧

表达式接受的是字符串输入,输出则是一个Object对象,而Object对象本身是根据计算结果进行进行了自动封装的。

它功能强大吗?

虽然在 #一些表达式的例子 这一节我有更详细的例子,但是这里我必须要先概括几点:

它支持变量,比如

Context context = Lang.context();
context.set("a", 10);
System.out.println(El.eval(context, "a*10"));  // 将打印 100 

通过 Context 接口,你可以为你的表达式随意设置变量的值。它支持如下类型的 Java 数据

  • 整型 - int 或 Integer
  • 浮点 - float 或 Float
  • 长整 - long 或 Long
  • 布尔 - boolean 或 Boolean
  • 字符串 - String
  • 数组 - T[]
  • 列表 - Lst<Ti>
  • 集合 - Collection<T>
  • Map - Map<String,?>
  • 普通 Java 对象

基本上,有了这些,你可以为所欲为了吧。

它速度怎么样?

我觉得它速度不怎么样。它的工作的原理是这样的,每次解析都经过如果下三步

  1. 解析成后缀表达式形式的一个队列
  2. 将后缀表达式解析成一棵运算树.
  3. 对运算树的根结点进行运算.

当然我也提供了一个提升效率的手段,因为如果每次计算都经过这三个步骤当然慢, 所以我们可以对它先预编译:

El exp = new El("a*10");  // 预编译结果为一个 El 对象

Context context = Lang.context();
context.set("a", 10);

System.out.println(exp.eval(context));  // 将打印 100 

El在实例化时就会对表达式进行预编译,会直接编译成运算树,当调用eval方法时, 就不用再耗时的编译动作了.

它的 eval 函数是线程安全的,只要在多个线程内给它不同的 context 就是了。 当然,你也可以在多个线程间共享同一个 Context,那运行起来一定很有趣,不是吗?

支持什么样的操作符

我想但凡有机会和兴趣读到这篇文字的同学,一定是编程老手,即使是自称小白的的同学们, 你们对一个编程语言应该支持的操作符基本都差不多熟的不行, 所以,我就不在这里唠叨操作符的具体细节了,我只给一个列表,告诉你我现在支持什么操作符。

另外,再加上一句:

只要支持的操作符,我会让它的优先级以及行为会和 Java 的表达式一致。如果你发现不一致 别犹豫,给我报 Issue 吧。

符号权重解释
()100括号,优先计算
,0逗号,主要是方法参数
.1访问对象的属性,或者Map的值,或者方法调用,或者自定义函数调用(需要结合后面是否有括号)
['abc']1Java 对象 Map按键值获得值
[3]1数字,列表,或者集合的下标访问符号
*3
/3整除
%3取模
+4
-4
-2
>=6大于等于
<=5小于等于
==7等于
!=6不等于
!7
>6大于
<6小于
&&11逻辑与
| |12逻辑或
?:13三元运算
&8位运算,与
~2位运算,非
|10位运算,或
^9位运算,异或
<<5位运算,左移
>>5位运算,右移
>>>5位运算,无符号右移
&8位运算,与

当然,同任何编程语言一样,表达式也支持 左括号 `(` 以及 右括号`)`, 来控制表达式的的计算优先级别

自定义函数

好吧, 你肯定要说上面的功能简直弱爆了, 就一点简单的加加减减有什么好稀奇的, 再强点的需求就没办法满足了. 确实是这样, 所以, 我们在 EL 里面添加了自定义函数, 嘿嘿, 这回强了吧. 言归正传,下面详细的说说它的使用, 以及怎么自定义.

现有的自定义函数:

名称参数解释例子
max任意个Number型取出参数中最大值max(1, 2, 3, 4)=>4
min任意个Number型取出参数中最小值min(1, 2, 3, 4)=>1
trim一个String去掉字符串两边的空格trim(" 1 ")=> "1"

使用很简单吧.

当EL里面的功能无法满足你的需求时, 你可以自定义一些功能来实现, 怎么自定义呢?

# 创建一个类, 使它实现 org.nutz.el.opt.RunMethod, org.nutz.plugin.Plugin 这两个接口.
# 在配置文件中添加一个配置项:
{
"EL": {"custom":[...函数列表...]}
}
# 如果你只使用了EL这一个模块, 没有其它模块, 并且在其它地方都没有加载过配置, 请使用下面的语句加载配置:
NutConf.load("配置文件");
//加载自定义函数配置, 必须!因为默认情况下, CustomMake只在static{}块中加载一次配置, 新添加的配置在static之后的话, 那么新添加的函数配置将不会生效!
CustomMake.init();

完成这两步后, 你就定义了你自己的函数. 看个trim的例子:

public class Trim implements RunMethod, Plugin{
    //处理方法, fetchParam为函数的参数. 它会将EL中函数括号后的所有内容传递过来
    public Object run(List<Object> fetchParam) {
        if(fetchParam.size() <= 0){
        throw new ElException("trim方法参数错误");
        }
        String obj = (String) fetchParam.get(0);
        return obj.trim();
    }
    //是否可以执行
    public boolean canWork() {
        return true;
    }
    //在EL表达式中的函数名
    public String fetchSelf() {
        return "trim";
    }
}

还有些什么功能?

其实我们的 EL 很强悍的, 有好多功能, 好多使用技巧等待着你的发现. 现在简单的罗列一下, EL中的一些特性吧.

忠于 Java

Nutz中的 EL 完全忠实于 JAVA 基本运算规则, 并没有做一些扩展, 比如最常见的, 数据类型转换, 在 JAVA 中进行数值运算的过程中, 是根据运算符两边的类型而最终决定运算结果的类型的, 比如:

7/3  // 将返回int型
而 
(1.0 * 7)/3    // 返回double
(1.0f * 7)/3   // 则返回float

为什么后面两个返回类型不一样呢? 因为在 JAVA 中默认浮点类型是 double 哦.

基于这个原因, 在 EL 中同样保留了这些特点. 所以, 亲, 要是没返回 double 别骂我们哦~~~

支持对象方法调用

亲, EL 支持对象, 支持对象方法调用哦~~~

这有什么好大不了的? 给你看个例子, 嘿嘿:

Context context = Lang.context();
context.set("a", new BigDecimal("7"));
context.set("b", new BigDecimal("3"));
assertEquals(10, El.eval(context, "a.add(b).intValue()"));
  • 看到什么没? 对了, BigDecimal 你完全可以丢各种各样的对象到 context 里面去, 然后在 EL 中调用它们的方法.
  • 然后你就进行各种各样的虐待, 皮鞭, 蜡烛, 想来啥来啥...额...太邪恶了...
  • 同样, EL 是使用反射的, 所以会存在一些底层异常的问题.

支持静态方法调用

没看错, EL支持静态方法调用, 不过, 在调用之前你需要设置一个变量才行...

Context context = Lang.context();
context.set("strings", Strings.class);
assertEquals("nutz", El.eval(context, "strings.trim(\"    nutz   \")"));

一些表达式的例子

普通运算

System.out.println(El.eval("3+2*5"));
// 输出为  13

字符串操作

System.out.println(El.eval("trim(\"  abc  \")"));
// 输出为  abc

Java 对象属性访问调用

Context context = Lang.context();
Pet pet = new Pet();
pet.setName("GFW");
context.set("pet",pet);
System.out.println(El.eval(context,"pet.name"));
// 输出为  GFW

函数调用

Context context = Lang.context();
Pet pet = new Pet();
context.set("pet",pet);
El.eval(context, "pet.setName('XiaoBai')");

System.out.println(El.eval(context,"pet.getName()"));
// 输出为  XiaoBai

数组访问

Context context = Lang.context();
context.set("x",Lang.array("A", "B", "C"));

System.out.println(El.eval(context,"x[0].toLowerCase()"));
// 输出为  a

列表访问

Context context = Lang.context();
context.set("x",Lang.list("A", "B", "C"));

System.out.println(El.eval(context, "x.get(0).toLowerCase()"));
// 输出为  a

Map 访问

Context context = Lang.context();
context.set("map",Lang.map("{x:10, y:5}"));

System.out.println(El.eval(context,"map['x'] * map['y']"));
// 输出为  50

判断

Context context = Lang.context();
context.set("a",5);

System.out.println(El.eval(context,"a>10"));
// 输出为  false

context.set("a",20);
System.out.println(El.eval(context,"a>10"));
// 输出为  true

通过代码注册自定义Function

局部注册

@Test
public void test_el2() throws Exception {
    El el = new El("sayhi(name)");
    Context ctx = Lang.context();
    ctx.set("name", "wendal");
    ctx.set("sayhi", getClass().getMethod("sayhi", String.class));
    assertEquals("hi,wendal", el.eval(ctx));
}

public static String sayhi(String name) {
    return "hi,"+name;
}

全局注册

// register任意一个实现了RunMethod接口的对象
CustomMake.me().register("ig", ioc.get(RedisIdGenerator.class));
Nutz.Json 注解一览表
Mapl 结构
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

表达式引擎

maplist结构

图像处理小军刀

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }