JSF表达式语言

时间:2022-09-29 19:09:18
JSF表达式语言

所有位于#号之后的一对花括号之中的字符串("#{...}")。EL的主要用途是使你可以引用和更新bean的属性,或者执行简单的语句,而不用写完整的Java 代码。在JSF中,表达式通常用来关联UI组件属性至后台bean或者应用中的模型对象。它们在运行时求解(通常是视图被显示时),而不是在应用被编译时。

JSF EL基于JSP 2.0中的表达式语言(原来是作为Java标准标签库[JSTL] 1.0的一部分创建的)。它借鉴了ECMAScript(JavaScript)和XPath(一种用于引用XML文档中部件的语言)的概念。这有一定的意义。首先,如果你熟悉JSP 2.0、JSTL、JavaScript、XPath或者Struts标签,那么JSF EL将非常直接。其次,除了引用简单的对象属性,如Hello, world! 示例,它还有访问Map、Collec­tion和数组中的元素条目的语法能力。第三,EL提供了一套隐含对象,使你可以访问请求参数以及HTTP首部之类的符号。最后,还可以使用EL来作为逻辑和数值语句,并且在表达式中混合使用字面值。

虽然JSF EL 是基于JSP 2.0的EL,二者仍有一些关键不同点:

l    JSF使用(#)来标记表达式的开始,而JSP使用($);

l    JSF表达式是双向的。换句话说,它可以引用属性的值也可以更新之;

l    JSF EL也允许引用对象方法;

l    某些JSP特定的特征无效,比如页面上下文范围;

l    JSF EL表达式可以通过常规Java代码求解(结果是可以不需要JSP);

l    JSF EL不官方支持函数。

表2-3列出了一些JSF EL表达式的例子。

表2-3    可以使用JSF EL来关联组件与暴露JavaBean 属性的对象、集合以及简单数据类型。EL也可用于引用方法和创建逻辑或者数值语句。除此之外,还指出嵌套属性

示    例

说    明

#{myBean.value}

返回保存在关键字myBean 下的对象的value属性,或者如果myBean是一个Map,则返回存储在关键字value下的元素

#{myBean['value']}

同"#{myBean.value}"

#{myArrayList[5]}

返回保存在关键字myArrayList下的List的第5个元素

#{myMap['foo']}

从保存在关键字myMap下的Map中返回保存在关键字foo下的对象

#{myMap[foo.bar]}

从保存在myMap关键字下的Map中返回保存在等于表达式foo.bar的值的关键字下的对象

#{myMap['foo'].value}

从保存在关键字myMap下的Map中返回保存在关键字foo下的对象的value属性

#{myMap['foo'].value[5]}

从保存在关键字myMap下的Map返回关键字foo下的List或者数组的第5个元素

#{myString}

返回存储在myString关键字下的String对象

#{myInteger}

返回存储在myInteger关键字下的Integer对象

#{user.role == 'normal'}

如果关键字user下的对象的role 属性等于normal则返回true.否则返回false

(续)

示    例

说    明

#{(user.balance - 200)== 0}

如果关键字user下的对象的balance属性减去200等于0,则返回true。否则返回false

Hello #{user.name}!

返回字符串"Hello" ,后跟关键字user下的对象的name属性。所以,如果用户的名称是Sean,则会返回"Hello Sean!"

You are #{(user.balance > 100)? 'loaded' : 'not loaded'}

如果关键字user下的对象的balance 属性小于100,返回"You are loaded",否则返回"You are not loaded"

#{myBean.methodName}

返回关键字myBean下名为methodName的对象的方法

#{20 + 3}

返回23

如表中所示,EL十分灵活。它可以引用属性和方法,也可以用作任意逻辑和数学表达式。甚至可以对EL表达式混合使用字面文本。然而最重要的是,所有EL引用的属性都必须是JavaBean属性(换言之,foo属性可能由Java方法getFoo和setFoo实现)。为了强调这个事实,我们有时把在EL表达式中引用的对象——后台bean和模型对象等,简称为bean。

我们来深入讨论一下表中的某些例子。首先,在使用方括号("[]")语法时,可以在其中嵌套使用另一个表达式,而不是讨厌的旧式字符串。所以,如果有个存放在myMap下的Map,并且想要从中使用关键字foo获取对象,则表达式可以写成"#{myMap['foo']}"。然而,如果你不使用引号,那些文本将作为子表达式来求解。所以,"#{myMap[foo]}" 就类似于"#{myMap [#{foo}]}"——即foo 首先被求解,其结果再作为myMap的关键字。因此,如果foo 是具有值"bar"的简单字符串,则此表达式的结果和"#{myMap['bar']}"相同。

另外,还可以嵌套分隔符。假定"#{myMap['foo']}"返回RandomValue对象。而RandomValue对象具有value属性,则该属性可以通过"#{myMap['foo'].value}"来引用。如果value 属性是另一个集合,可以这样访问集合中的一个元素:"#{myMap['foo'].value[5]}"。还可以对子表达式嵌套分隔符,所以表达式"#{myMap[foo.bar.baz]"也是有效的。

就像在JavaScript中一样,可以使用单引号('),也可以使用双引号(")。在基于XML的文档,如JSP中,EL表达式和标签属性(attribute)需要使用不同的引号。所以在标签内部,这两个语句都是有效的:"#{myBean['value']} " 和 '#{myBean ["value"]}'。然而,下面这两个语句无效,因为引号的类型相同:"#{myBean ["value"]}"and'#{myBean ['value']}'。

这些例子中,我们仅涉及了部分操作符,比如EL支持的“.”和“[]”。它支持全范围的逻辑和数值操作符,如表2-4所示。

表2-4  JSF EL支持大量的通用操作符

语    法

替    代

操    作

.

 

访问bean属性、方法或者Map条目

(续)

语    法

替    代

操    作

[]

 

访问数组、List元素或者Map条目

()

 

创建子表达式并控制求解顺序

? :

 

条件表达式: ifCondition ? trueValue : falseValue

+

 

-

 

减 或者负号

*

 

/

div

%

mod

模(求余)

==

eq

等于(对对象,使用equals()方法)

!=

ne

不等于

<

lt

小于

>

gt

大于

<=

le

小于或等于

>=

ge

大于或等于

&&

and

逻辑AND

||

or

逻辑OR

!

not

逻辑NOT

empty

 

测试空值(null、空字符串、数组、Map或者没有值的Collection)

    到此,我们已经讨论了能求解出特定属性值的表达式。而这种表达式,以及像"#{user.demands == 'too much'}" 之类的逻辑表达式和像"#{weather.temp/32}"之类的数学表达式,可认为是值绑定表达式(value-binding expression)。值绑定表达式可用来将UI组件的值关联至bean属性,绑定UI组件到bean属性,或者初始化UI组件的属性。

当使用EL来引用bean方法时,则称之为方法绑定表达式(method-binding expression)。方法绑定表达式,如"#{myBean.methodName}",用来将事件句柄或者验证方法关联至组件。即使在实际的语法中并没有要求参数,组件也将根据方法的使用意图来假设特定的方法签名。

从此开始,必要时,我们将使用特定的术语值绑定表达式或者方法绑定表达式。如果想要开发JSF应用代码,区分它们是很重要的,因为EL API使用这些术语。

到此为止,你应该对JSF EL 有些印象了,即关于它是什么样子、如何构建以及可以用它来做些什么。然而,它还要有一些特征才能使它成为Web应用世界中的一个和谐成员。作用域变量便是其中一个基本部分,我们将在下一节讨论。

2.4.1  作用域变量

我们已经谈到过在关键字下面保存bean。但是到底这些不可思议的bean位于何处,而关键字又是针对什么呢? 记住,Java Web应用有四种引用范围:应用、会话、请求和页面。每个范围都可以在关键字下面保存任意对象。将上述这些范围总结在表2-5之中。

表2-5  JSF应用支持应用、会话和请求范围的引用变量

Web应用范围

说    明

JSF支持有否?

应用(application)

保存在此范围内的变量对应用的整个生命周期都有效

会话(session)

保存在此范围内的变量仅在用户会话期间有效

请求(request)

保存在此范围内的变量仅对当前请求有效

页面(page)

保存在此范围内的变量仅对当前页面有效

应用范围内的变量在Web整个生命周期内有效;会话范围内的变量在用户访问期间有效;请求范围内的变量保持在一个单一请求的处理期间;而页面范围内的变量仅在页面被呈现时存活。保存在这些范围内的变量称为是作用域变量(scoped variable)。在Hello, world! 应用中,我们在会话范围内保存了HelloBean对象。

因为JSF并一定要锁定到JSP,所以EL并不支持页面范围。但是它可以查找其他范围中以匹配特定关键字的对象。所以表达式"#{helloBean.numControls}",只要对象以关键字helloBean保存在请求、会话或者应用的范围内,且该对象具有numControls属性,就能够正常工作。

因为JSF EL表达式搜索常规的Java Web应用范围,所以它们可以像JSP、JSTL表达式或者常规Java代码(通过Servlet API)一样引用同一个bean。这使得可以在具有JSF标签的同一个JSP页面中,混合使用JSTL和JSP表达式,二者通常使得对现有的Java Web应用集成JSF功能变得更加容易。

在传统的Java Web应用中,通常使用原生的servlet API或者定制标签(比如JSTL标签)将bean 放置在这些范围中。然而,在JSF世界里,它们通常是在Java代码中使用受管bean创建工具来创建。这种机制是在JSF 配置文件中配置的;我们曾在第1章接触过。关于此工具(我们将在下一章讨论),也可以用EL表达式将不同的对象关联到另一个对象。

现在你已经清楚了什么是作用域变量,下面来探讨如何在EL中引用它们。

2.4.2  使用隐含变量

隐含变量是一些特殊的EL 标识符,它们对应于特定的、常用的对象。这之前,我们通过表达式"#{helloBean.numControls}"访问了HelloBean。我们知道HelloBean是被保存在session 范围内,所以可以使用sessionScope 隐含变量来引用它,像这样:"#{sessionScope. helloBean.numControls}"。对于这个表达式,JSF将仅搜索会话范围。

针对每种范围都有一个隐含变量,但还有其他一些便捷的变量。它们使得对Web开发所需的典型元素的存取十分方便:比如请求参数、HTTP首部值、cookie等。表2-6列出了所有JSF EL支持的隐含变量。

表2-6  JSF EL支持访问常用对象的隐含变量。它们中大多数同时也被JSP 2.0 EL所支持

隐含变量

说    明

实    例

JSP 2.0 EL是否支持?

applicationScope

应用作用域变量的Map,以名称作为关键字

#{application-Scope.myVariable}

cookie

一个当前请求的cookie值的Map,以cookie名称作为关键字

#{cookie.myCookie}

facesContext

当前请求的FacesContext 实例

#{facesContext}

header

当前请求的HTTP首部值的 Map,以header名称作为关键字。如果给定的header 名称有多个值,仅返回第1个值

#{header['User-Agent']}

headerValues

当前请求的HTTP首部值的 Map,以header名称作为关键字。对每个关键字,返回一个String数组(以便所有的值都能访问)

#{headerValues

['Accept-Encoding'][3]}

initParam

应用初始化参数的Map,以参数名称为关键字。(也称为servlet 上下文初始化参数,在部署描述符中设置)

#{initParam.adminEmail}

param

请求参数的Map,以header 名称作为关键字。如果对给定的参数名称有多个值,仅返回第1个值

#{param.address}

paramValues

请求参数的Map,以header 名称作为关键字。对每个关键字,返回一个String数组(以便可以访问所有的值)

#{param.address[2]}

requestScope

请求范围内的变量的Map,以名称作为关键字

#{requestScope.user-Preferences}

sessionScope

会话范围内的变量的Map,以名称作为关键字

#{sessionScope['user']}

view

当前视图

#{view.locale}

我们并不想讨论所有变量;如果你以前开发过Java Web应用,你应该对它们很熟悉(事实上,它们大部分都是JSP 2.0 EL中有的)。另外,通常并不需要直接引用当前请求和HTTP 头部(除非用于调试)。

有两个变量是针对JSF的:facesContext和view。我们在2.2节中介绍过FacesContext 类。FacesContext实例代表当前正在处理的请求。它持有对当前应用消息栈、当前呈现包和其他一些有用对象的引用。这在EL表达式中一般没什么用处,虽然其大部分属性并没有设计成可以通过表达式进行访问。此变量在通过受管bean创建工具初始化bean的属性时,较为有用。见第3章关于这个问题的详细信息。

对前端开发来说,view具有一些很有用的属性:viewId、renderKitId和locale。这些值对显示没什么用(除非进行调试)。但是,理论上讲,可以在条件表达式中使用它们。例如,表达式"#{view.renderKitId == 'HTML_BASIC'}",在当前页面的呈现包是"HTML_BASIC"时(默认值),会返回true。

也可以使用locale属性,比如根据当前场所隐藏或者显示某个组件。例如,表达式"#{view.locale != 'en_US'}",在用户讲美国英语时,将返回true。与facesContext类似,view隐含变量是初始化bean属性的绝佳选择。

要记住的重要之处是,对范围隐含变量来说(requestScope、sessionScope和applicationScope),除了正在保存变量时,通常它们是不必要的。也可以在Java代码中使用JSF EL 表达式来保存变量(见第13章的例子)。

现在,已经讨论了EL语法、应用范围以及隐含变量,可以开始详细讨论在JSF 开发世界中如何使用表达式了。

2.4.3  在组件中使用EL

JSF 具有其自己的表达式语言的原因是,它可以使你动态地将UI组件与后台bean或者模型对象关联在一起。事实上,你可能希望IDE帮助你完成这个过程(见图2-7)。

JSF表达式语言

图2-7    Sun的Java Studio Creator 提供了对话框,以帮助你为可以通过当前后台bean访问的对象创建值绑定表达式

目前为止,值绑定表达式的最强大之处是将bean的属性关联至组件的值。这也是在Hello, world!应用中的hello.jsp里面,将HelloBean. numControls 属性关联到HtmlInputText 组件的用法。这里有个例子:假定在请求范围中在关键字loginForm下保存了一个后台bean。我们就可以使HtmlInputTextArea控件的值与bean的 comments属性保持同步,像这样:

JSF表达式语言

    当HtmlInputText组件被显示给用户时,其值将等于registrationForm的 comments 属性。而不论何时用户在控件中输入一些内容并且将表单提交给服务器,registrationForm的 comments属性都将据此进行更新(假设通过了验证)。

你可以对任何类型的引用bean属性的EL表达式使用相同的技术,而不管该属性是简单类型、复杂对象、Map、数组或者List:

JSF表达式语言

这里,这个HtmlGraphicImage控件将显示一个图像,其URL 等于user bean的icons属性的第五个元素(此例中的实际元素是个字符串)。bean 可以保存在任何有效的范围中,只要是在关键字user之下。

最后的例子强调了一个重点——user代表业务概念,所以它是模型对象而不是像loginForm一样的后台bean。你可以使用值绑定表达式对它们进行更新,只要属性是用JavaBean模式来实现的。

注解    JSF使用值绑定对象来实现国际化。一个特殊的JSP 定制标签用来将资源束载为Map,所以本地化字符串可以这样引用:"#{myBundle.welcomeString}"。国际化的详细信息见第6章。

我们引用了JavaBean,但是实际上可以直接将组件的值与List、Map、简单类型的字符串或者整数进行关联,只要它们是以某个关键字保存在某个范围之中。假定有个字符串在关键字clientName下保存在应用范围中。可以这样引用它:

JSF表达式语言

这样就使控件直接与以用户名称为关键字的String发生了关联;这样就直接显示和更新字符串的值。如果JSF不能在该关键字下面找到对象,它将创建一个,并将其保存在请求范围中。

就像在上一章看到的那样,也可以直接绑定组件到后台bean属性,以便该组件能够在Java代码中被操作。某些工具在产生后台bean类时,可以自动完成这个工作。如下所示:

JSF表达式语言

这个例子将确保registrationForm的commentsInput属性能够引用HtmlInputText组件,这样就允许后台bean操作其属性。使用binding属性要求被引用的属性的类型和引用该属性的组件的类型相同。所以,在这个例子中,commentsInput属性必须是HtmlInputText类型(或者其子类)。后台bean也可以选择在其访问器方法中初始化组件。绑定组件至后台bean属性,仅在你的Java 事件监听器需要操作组件属性时,是必要的。

我们也看到方法绑定表达式用于关联UI组件到事件监听器方法:

JSF表达式语言

这个valueChangeListener属性接受指向registrationForm 的commentsChanged 方法的方法绑定表达式。该方法必须与值改变监听器方法所期望的方法签名相匹配。如果控件的值发生改变,将执行该方法。

也可以使用方法绑定表达式来关联UI组件至后台bean中的验证器方法:

JSF表达式语言

这就将HtmlInputText 的validator属性和后台bean的checkSpelling方法相关联了,同时该方法必须具有特定的方法签名。

现在,在页面中使用UI组件时,也可以一起使用EL。这是将组件与应用中的其他部分相关联的基本部件。但是,类似于转换器,还有更多表面之下的东西。可以对任何标准的组件属性使用值绑定表达式。与我们之前讨论过的情形不同,这里并没有实际的限制,只需引用的对象具有正确的类型(并且如果必要,它必须是JavaBean)。所以,我们来扩展上一个例子,对其他属性使用表达式:

JSF表达式语言

有几点需要注意:首先,并不一定要对组件属性使用表达式,尽可以*地混合使用静态值,就像此例中的size和width属性;第二,用于特定组件的表达式并不一定要关联到后台bean。例如,styleClass就引用commentStyle初始化参数,而该参数是在Web应用部署描述符中设置的。而title属性则引用appBundle对象,该对象是提供本地化字符串的Java资源束。所以commentsTitle属性将返回字符串,该字符串取决于用户的语言(资源束和国际化将在第6章讨论)。

如果还想知道以上的所有内容又是如何与非JSP 世界发生联系的,不要担心——值绑定表达式和方法绑定表达式的API在Java层面是完全暴露的。事实上,你很可能需要在Java 代码层面使用它们——见第三部分和第四部分的示例。