Dorado 9 : 05. EL表达式

简介

Dorado提供了两种EL表达式,静态EL表达式和动态EL表达式,在实做HelloWorld的范例中我们已经初步接触了静态EL表达式.

 

动态EL表达式是在普通EL表达式基础上的进一步扩展。普通EL表达式以 ${expression} 的方式定义,而动态EL表达式以 $${expression} 的方式定义。

 

动态EL表达式与普通EL表达式之间的主要差别是求值更晚。普通EL表达式通产都是在从配置信息转化为Java对象时求值,而动态EL表达式则是在属性被读取时才进行求值。
普通EL表达式只能进行单次的求值,一旦其被执行其结果就会被固化到目标对象或属性中,其后该表达式就会失效。而动态EL表达式则可以多次求值,即每次读取包含动态EL表达式的属性时,你可能都会得到不同的结果。

 

表达式类型

表达式格式

动态EL表达式

$${expression}

静态EL表达式

${expression}

 

差别

静态EL表达式:在创建目标对象的过程中一次性的求值.

动态EL表达式:以动态代理的方式创建目标对象,并在外界每一次读取相应属性时都进行求值。

举例说明:

如我们设置button的caption为${util.getDate()}.

则我们用java访问这个属性的时候:

System.out.println(button.getCaption());
System.out.println(button.getCaption());
System.out.println(button.getCaption());

上面的输出是一样的。

而如果改为动态表达式$${util.getDate()}:

System.out.println(button.getCaption());
System.out.println(button.getCaption());
System.out.println(button.getCaption());

这个时候上面的每一个输出都不一样,在每一次读取的时候都会运算求值。

动态EL表达式的这一特性并不是依赖于Map对象而实现,即使返回的是一个Bean的实例,你同样可以使用动态EL表达式。Dorado能够实现这种奇妙特性是依靠了cglib、javassist提供的字节码动态代理特性。

提示

动态EL表达式通过动态代理实现,使用的时候需要注意隐式对象的选择,例如我们不能在JS端使用Java端的对象做动态EL表达式

动态EL表达式的求值甚至可以不仅仅是局限在服务端进行,对于某些特殊的属性中某些EL表达式而言你甚至可以在客户端的代码中对他们进行求值。
这一功能最常见的使用场景就是在Reference的parameter属性中的$${this.xxx}表达式。例如前一个例子主从表视图(懒装载)总使用的$${this.id}表达式,就是在客户端对this.id进行求值,得到当前产品分类的ID,然后利用Ajax将其作为参数传递给服务端的DataProvider。

隐式对象

Dorado共提供了14个隐式对象,我们不需要预先定义就可以直接使用这些隐式对象。

与线程无关

 

标识符

描述

null

表示null

env

表示获取系统环境变量。${env.HOME}或${env["HOME"]}

system

Java的系统属性,即System.getProperties()对象。${system.property1}或${system["property1"]}

configure

用于获取Dorado的配置信息。${configure["core.runMode"]}

 

与线程有关

 

标识符

描述

argument

用于获取当前配置文件中的参数值,仅可用于View.xml

context

当前线程中的DoradoContext。

ctx

用于简化对DoradoContext的getAttribute方法的访问。${ctx["foo"]}相当于${context.getAttribute("foo")}

util

基本工具类,见com.bstek.dorado.core.el.ExpressionUtilsObject的javadoc,如:

${util.getToday()}//获取当前日期

${util.getDate('yyyy年MM月dd日 HH时mm分ss秒')}//指定日期格式

request

当前的HttpServletRequest对象,如:${request.getAttribute("foo")}

req

用于简化对request的getAttribute方法的访问。 ${req["foo"]}相当于${request.getAttribute("foo")}

param

用于简化对request的getParameter方法的访问。 ${param["foo"]}相当于${request.getParameter("foo")}

session

当前的HttpSession对象,与会话作用域属性的名称和值相关联的 Map 类。范例:${session.getAttribute("foo")}

servletContext

当前的SerlvetContext对象。范例:${servletContext.getAttribute("foo")}

web

基本工具类,见com.bstek.dorado.web.WebExpressionUtilsObject的javadoc

 

可扩展

如有需要,开发人员可以自行扩展出更多的隐式变量.

扩展方法:自定义EL表达式的隐式变量(SEUG)

说明

因为 EL 标识符是作为隐式对象或限制了作用域的变量(通过属性来实现)解析的,因此有必要将它们转换成 Java 对象。EL 可以自动包装和解包其相应的 Java 类中的基本类型

(例如,可以在后台将 int 强制转换成 Integer 类,反之亦可),但大多数的标识符将成为指向完整的 Java 对象的指针。结果是,对这些对象的特性或(在对象是数组和集合的情况下)对其元素的访问通常是令人满意的。就为了实现这种用途,EL 提供了两种不同的存取器(点运算符(.)和方括号运算符([])),也支持通过 EL 操作特性和元素。

 

  • 点运算符通常用于访问对象的特性。例如,在表达式 ${user.firstName} 中,使用点运算符来访问 user 标识符所引用对象的名为 firstName 的特性。EL 使用 Java bean 约定访问对象特性,因此必须定义这个特性的 getter 方法(通常是名为 getFirstName() 的方法),以便表达式正确求值。当被访问的特性本身是对象时,可以递归地应用点运算符。例如,如果我们虚构的 user 对象有一个实现为 Java 对象的 address 特性,那么也可以用点运算符来访问这个对象的特性。例如,表达式 ${user.address.city} 将会返回这个地址对象嵌套的 city 特性。

 

  • 方括号运算符用来检索数组和集合的元素。在数组和有序集合(也即,实现了 java.util.List 接口的集合)的情况下,把要检索的元素的下标放在方括号中。例如,表达式${urls[3] } 返回 urls 标识符所引用的数组或集合的第四个元素(和 Java 语言以及 JavaScript 中一样,EL 中的下标是从零开始的)。对于实现 java.util.Map 接口的集合,方括号运算符使用关联的键查找存储在映射中的值。在方括号中指定键,并将相应的值作为表达式的值返回。例如,表达式 ${commands["dir"]} 返回与 commands 标识符所引用的 Map 中的 "dir" 键相关联的值。

对于上述两种情况,都可允许表达式出现在方括号中。对嵌套表达式求值的结果将被作为下标或键,用来检索集合或数组的适当元素。和点运算符一样,方括号运算符也可以递归应用。这使得 EL 能够从多维数组、嵌套集合或两者的任意组合中检索元素。此外,点运算符和方括号运算符还可以互操作。例如,如果数组的元素本身是对象,则可以使用方括号运算符来检索该数组的元素,并结合点运算符来检索该元素的一个特性(例如 ${urls[3].protocol})。

假定 EL 充当指定动态属性值的简化语言,EL 存取器有一个有趣的功能(与 Java 语言的存取器不同),那就是它们在应用于 null 时不抛出异常。如果应用 EL 存取器的对象(例如,${foo.bar} 和 ${foo["bar"]} 中的 foo 标识符)是 null,那么应用存取器的结果也是 null。事实证明,在大多数情况下,这是一个相当有用的行为,不久您就会了解这一点。

最后,点运算符和方括号运算符可能实现某种程度的互换。例如,也可以使用 ${user["firstName"]} 来检索 user 对象的 firstName 特性,正如可以用 $\commands.dir} 获取与 commands 映射中的 "dir" 键相关联的值一样。

基本语法

EL 还可以通过使用标识符和存取器,遍历包含应用程序数据(通过限制了作用域的变量公开)或关于环境的信息(通过 EL 隐式对象)的对象层次结构。但是,只是访问这些数据

,通常不足以实现许多 JSP 应用程序所需的表示逻辑。

最终,EL 还包括了几个用来操作和比较 EL 表达式所访问数据的运算符。

 

类别

运算符

算术运算符

+、-、*、/(或 div)和 %(或 mod)

关系运算符

==(或 eq)、!=(或 ne)、<(或 lt)、>(或 gt)、<=(或 le)和 >=(或 ge)

逻辑运算符

&&(或 and)、||(或 or)和 !(或 not)

验证运算符

empty

 

最后一种 EL 运算符是 empty,它对于验证数据特别有用。empty 运算符采用单个表达式作为其变量(也即,${empty input}),并返回一个布尔值,该布尔值表示对表达式求值

的结果是不是"空"值。求值结果为 null 的表达式被认为是空,即无元素的集合或数组。如果参数是对长度为零的 String 求值所得的结果,则 empty 运算符也将返回 true。

EL 运算符优先级(自顶到底,从左到右)

 

优先级

[], .

()

unary -、not、!、empty

*、/、div、%、mod

+、binary -

() <</code>、>、<=、>=、lt、gt、le、ge

==、!=、eq、ne

&&、and

||、or

 

范例
  • ${4+2} = 6
  • ${4-2} = 2
  • ${4*2} = 8
  • ${4/2} = 2
  • ${3>2} = true
  • ${3<2} = false
  • ${2!=3} = false
  • ${true&&false} = false
  • ${!(2==3)} = true
  • ${(x >= min) && (x <= max)}
  • ${empty(foo)}${foo>0? "enabled":"disabled"}
  • ${foo["bar"]}或${foo.bar} 读取map中的bar键值。
文字

在 EL 表达式中,数字、字符串、布尔值和 null 都可以被指定为文字值。字符串可以用单引号或双引号定界。布尔值被指定为 true 和 false。

特殊EL隐式变量

在使用Dorado EL表达式的时候,我们还需要掌握几个特殊的EL隐式变量,如:$${this}或$${this.foo}.虽然写法上像动态EL表达式,但其实际运行的机制与一般EL表达式并不一样,注意要点:

  • 此种隐式变量通常只用在DataType的Reference属性中。this表示当前正在处理的数据实体对象,即系统正在尝试获取该数据实体对象的Reference属性的值。
  • 此种EL表达式的实际求值范围是在Client端的JavaScript环境中。
    Reference使用图例:

关于这几个EL表达式的实际用法我们将会在后面Reference的使用中用实例阐述

 

自定义EL表达式的隐式变量(SEUG)


Dorado中EL表达式的隐式变变量是由隐式变量初始化器实现初始化的。

一个典型的初始化器的格式如下:

public class XXXVarsInitializer implements ContextVarsInitializer {
    private static final Log logger = LogFactory.getLog(XXXInitializer.class);
 
    public void
    (Map<String, Object> vars) {
        vars.put("test", new MyExpressionBean());
    }
}

其中vars中包含的key即EL隐式变量的名字。MyExpressionBean是你自定义的JavaBean。

之后我们只要将这个初始化器配置到Spring中就可以在View设计中使用这个隐式变量了。Spring声明参考格式:

<bean parent="dorado.expressionVarsInitializerRegister">
    <property name="contextInitializer">
        <bean class="xxx.xxx.xxx.XXXVarsInitializer" />
    </property>
</bean>

范例1:

vars.put("mysystem", System.getProperties());

测试用例:

${mysystem['java.home']}
${mysystem.get('java.home')}

范例2:

 

//ExpressionUtilsObject为dorado中的com.bstek.dorado.core.el.ExpressionUtilsObject
vars.put("myutil", SingletonBeanFactory.getInstance(ExpressionUtilsObject.class));

测试用例:

 

${myutil.getToday()}
${myutil.trim(${param['userId']})}


Attachments:

el.png (image/png)