http://dev2dev.bea.com.cn/techdoc/wlportal/20031027.html
简介
页面流提供了一种方便构建Web应用业务和导航逻辑的编程模型。为了显示和修改页面流动作所收集的数据,WebLogic Workshop 8.1 数据绑定框架将用户界面中的NetUI JSP标签绑定到Web应用数据上。两者相互结合为构建基于Web的用户界面提供了一种令人瞩目的开发模型。
数据绑定是一个重要主题;这里我们将介绍用于绑定、显示、更新数据的表达式语言和JSP标签;并将演示如何创建一个简单的表单。本文假定读者对基本页面流和JSP程序设计较为熟悉。文中用到的所有代码都可以从附带的下载文件
Web application (1.4 MB)中获得。
参与者 绑定UI到数据的过程需要三个参与者--业务对象/数据、NetUI JSP标签库、表达式语言。业务对象中含有需要绑定到JSP页面的数据。JSP标签以特定视图(只读或可更新)的方式在Web浏览器中绘制数据,而表达式语言则将两者粘合在一起,从而可以通过JSP标签引用业务对象的属性。借助三个参与者的实例,Workshop开发人员可以用灵活且优雅的方式创建用于显示、更新、及创建数据的用户界面。
整篇文章我们都会用到一个定义了字段和属性的JavaBean,属性更适合说明数据是如何绑定到不同类型之上的。该表单的一部分如下;其中定义了一个公共属性name 和一个公共字段eMail。可以把该类看作一个针对简单Web页面的业务对象,该Web页面演示了如何绑定到表单的属性。
public class Customer { public String eMail = ""; private String name;
public String getName() { return this.name; } . . . } [binding/Controller.jpf] |
URL 也是该bean的一个属性。除此之外,同一页面流中还定义了一个LocationInfo 类,它暴露了可以进行数据绑定的复杂类型。
为了更快将读者引进门,我们假定代表用户"Dave Smith"的Customer对象已经存在,并且可以通过String类型的关键字"customer"在request的属性映射中进行访问。这个Customer的姓名可以借助JSP的NetUI标签显示在Web页中。
Customer Name: <netui:label value="{request.customer.name}"/> [binding/simple.jsp] |
它在Workshop JSP 设计器中显示如下:
Customer Name: {request.customer.name} |
并生成如下的HTML输出:
Customer Name: <span>John Smith</span> |
下面我们将从整体上更进一步分析这个例子和表达式。
表达式语言
表达式语言使得Workshop开发人员能够使用一种简单的语法在JSP页面中引用业务对象的数据。通常,一个表达式引用业务对象上的一个或一系列属性,从而可以唯一标识JSP标签所绑定的数据。在以上例子中,表达式{request.customer.name}引用Customer JavaBean中的name 属性,保存在request中的该JavaBean被称作"customer"。
在JSP标签属性中,表达式分别以'{' 和'}'作为开始和结束标记。标签探测一个属性是否是表达式、或者其中是否包含表达式,从而对属性进行求值。
一个表达式含有两个独立的部分--数据绑定上下文:在其中进行表达式求值;属性:它是表达式求值返回的结果。在表达式{request.customer.name}中, request是绑定上下文,它引用JSP页面request的属性映射。表达式剩下的部分用来引用唯一的对象属性。"customer"标识符用来在request的属性映射中查找相关对象,name标识符用来获取Customer 对象上的JavaBean 属性name。从功能上讲,该表达式等同于以下语句:
String name = ((Customer)request.getAttribute("customer")).getName(); |
同样是引用相同的数据,表达式语法更为简单。
除了request 上下文,还可以在其它数据绑定上下文中求值表达式,这些上下文用来访问Web应用和页面流环境的不同部分。绑定上下文还可以引用其它资源,比如通过url 或 bundle 上下文,它可以分别引用URL参数或资源包中的国际化字符串。完整的上下文及其引用对象/资源的列表如下:
上下文名称 |
上下文引用的对象 |
actionForm |
和当前NetUI 表单标签相关的动作表单 |
pageFlow |
某个用户的当前页面流 |
globalApp |
webapp中某个用户的Global.app |
bundle |
用于引用声明或隐含的资源包属性 |
pageContext |
JSP 页面的PageContext属性映射 |
request |
request的属性映射 |
session |
session的属性映射 |
application |
servlet上下文的属性映射 |
url |
用于当前请求的URL 查询参数 |
container |
当使用重复标签绘制某个数据集时,复杂数据绑定标签用它来访问当前条目 |
现在进一步讨论上表中提到的一些上下文。
同样是使用表达式,仍然存在多种不同方法来引用同一对象的同一属性。例如,在我们的例子中,以下几个表达式都是等价的:
- request["customer"]["name"] - request.customer.name - request["customer"].name - request.customer["name"] [binding/index.jsp] |
而且,表达式既可以访问字段,也可以访问属性,这些属性返回任意基本类型或Java Object类型,也可以返回它们的数组、列表和映射类型。一般说来,属性命名遵循JavaBean属性的setter和getter方法命名规范。关于暴露、命名、引用数据绑定对象属性的特殊规则将在下面进行讨论。
注意:对象的值既可以是只读的也可以是可读/写的;对象对应的值的暴露方式决定了它对于表达式仅是只读的,还是当Web浏览器向服务器提交数据时该值是可更新的。
访问属性
为了识别类暴露的可绑定数据属性,表达式语言采用了JavaBean命名规范。符合以下条件的方法可以暴露一个只读属性:
- public 访问权限 - 返回非void类型 - 无参数 - 以"get"作为名称的开头 |
可以使用表达式来引用数据绑定上下文中符合以上条件的任意方法。JavaBean属性的名称被包含在表达式中。例如,类中定义了一个属性:
public String getName(); [binding/index.jsp] [binding/Controller.jpf] |
在表达式中它被引用为:
{request.customer.name} [binding/index.jsp] [binding/index.jsp] |
前提是一个Customer 对象被指定为“customer”并可以从request中得到。
在本例中,方法 getName 对应名为name的JavaBean属性;这是通过去掉“get”并将第一个字符变成小写之后得到的。一个例外是如果“get”之后的头两个字符都是大写的,JavaBean属性的名称将在表达式中保持不变。例如,属性:
public String getURL(); [binding/index.jsp] [binding/Controller.jpf] |
在表达式中被引用为:
{request.customer.URL} [binding/index.jsp] [binding/index.jsp] |
迄今为止,以上的name 和URL 属性都是只读的,因为我们没有为它们定义setter方法,该方法可以用来更新对象的属性值。一个JavaBean的setter方法需要符合以下条件:
- 以"set"作为名称开头 - 返回void类型 - 只有一个参数,它的类型与对应getter方法的返回类型相同 |
前面两个属性的JavaBean setter如下:
public void setName(String name); public void setURL(String url); [binding/index.jsp] [binding/Controller.jpf]
|
如果对象中同时存在正确定义的getter 和setter,属性就是可读/写的。这种类型的属性十分重要,因为可以从Web页面更新它们的值。
访问字段
可以通过表达式访问公共字段。上面Customer类中定义的“eMail”字段就是一个例子。公共字段总是可读/写的。Customer的email地址在Web页中可以显示为:
<netui:label value="{request.customer.eMail}"/> [binding/index.jsp] [binding/Controller.jpf]
|
访问数组或列表中的元素
使用常用的[index] 语法可以引用数组或列表中的条目。如果一个属性被暴露为:
public String[] getZipArray(); [binding/Controller.jpf] [binding/index.jsp]
|
那么可以通过以下表达式访问数组的第四个条目:
{request.locationInfo.zipArray[3]} [binding/index.jsp] [binding/index.jsp] |
假定LocationInfo对象可以在request中通过关键字“locationInfo”进行访问,
如果一个属性被暴露为:
public List getZipList(); [binding/index.jsp] [binding/Controller.jpf] |
那么以下表达式将会访问列表中的第四个条目:
{request.locationInfo.zipList[3]} [binding/index.jsp] [binding/index.jsp] |
此类表达式求值还是略有不同。如果该表达式在一个长度仅为2的String[]上被求值,因为数组的长度小于表达式的引用长度,所以将会发生错误。如果属性是列表类型,表达式将被简单地求值为null。
使用NetUI Repeater标签集可以方便地绘制数组和列表。稍候一篇dev2dev文章将会涉及该标签集以及“container”绑定上下文。
访问映射中的元素
表达式还可以采用与访问属性相同的语法来引用映射中特定键值对应的条目。例如,如果LocationInfo 类暴露了一个含有州名缩写和州名的Map,并将其定义为带有JavaBean getter方法的可绑定数据属性:
public Map getAbbrevMap(); [binding/index.jsp] [binding/Controller.jpf] |
那么,以下两个表达式都可以使用键值“CO”在映射中进行查找:
{request.locationInfo.abbrevMap.CO} {request.locationInfo.abbrevMap["CO"]} [binding/index.jsp] [binding/index.jsp]
|
访问基本类型
采用以上任何机制暴露的数据类型可以是Java对象或基本类型。如果某个属性或字段的类型是基本类型,比如int 或 boolean ,表达式的求值结果将是基本类型的Java对象包装器。在本例中,将分别是Integer 和 Boolean。
表达式和NetUI JSP标签
在NetUI JSP标签上使用表达式、属性可以引用业务对象属性,并将数据显示在Web页面上。同一属性的显示方式取决于标签的选择。比如,绑定到NetUI label的字符串属性是只读的,而绑定到NetUI form内NetUI text box的字符串属性却是可读/写的。
目前存在三个NetUI标签库——HTML、复杂数据绑定、模板,本文不涉及后面两个。HTML标签集尽可能遵循HTML4.01规范并将自己的标签和HTML中定义的元素相互结合。例如,textBox、 checkBox、 和radioButton 标签分别显示HTML输入标签类型“text”、 “checkbox”、 和“radio”。可以在Workshop标签面板中找到这些标签。
也可以在Workshop 8.1的Insert菜单中找到这些标签。
NetUI HTML 标签进一步可以分为两类——只能显示数据的标签(或称为只读的)、能够在服务器和Web浏览器之间“往返”的标签(或称为可读/写的)。可以使用诸如netui:form 内的textBox这类标签在服务器和客户端之间传递数据。关于此类标签如何绑定数据,彼此之间也存在不同。
只读NetUI标签
只读标签使用表达式绑定属性“value”来指定在页面中绘制的数据。此类标签只是简单地通过表达式从业务对象中读取数值并在Web页面上绘制,可能还会对输出结果进行格式处理。我们已经知道netui:label 标签可以在页面上绘制表达式的值,其实它也可以绘制普通文本:
<netui:label value="Blue"/> [binding/index.jsp] [binding/index.jsp]
|
因为label标签不是到服务器的可往返标签,所以它的value属性可以是文本和表达式的组合。例如,想要显示用反斜杠分开的客户姓名和年龄,可以这样使用label:
<netui:label value="{request.customer.age} / {request.customer.age}"/> [binding/index.jsp] [customerEntry/display.jsp]
|
每个表达式都将被求值,文本“/”将会混合到表达式结果中。
可读/写NetUI标签
第二组标签使用“dataSource”属性,它们比较特殊因为被“dataSource”属性引用的数据可以在Web浏览器和服务器之间往返。尽管dataSource属性可以绑定表达式,但该属性的value必须是一个纯粹的表达式。与label标签的value属性不同,dataSource属性不能混合文本和表达式,因为dataSource属性的value被用在两个地方:
- 绘制JSP页面的时候通过表达式读取值 - 当表单所在页面POST的时候,通过同一个表达式将值写回到业务对象中去。
|
采取这种方式,诸如textBox这样的标签就可以在服务器和Web浏览器之间来回传输数据。
如果在dataSource 属性中混合使用文本,属性就不是一个纯粹的表达式并将报错。因为dataSource 属性通常被用于同数据库交换数据,所以这里使用的表达式必须引用那些可以更新的数据绑定上下文,它们是actionForm、pageFlow、和 globalApp。 除此以外,所有其它绑定上下文在数据被POST到服务器时都是只读的。
一个简单例子
让我们对前面的Customer例子稍作修改并利用NetUI编写一个“Hello World”,从而形成一个实用的例子。本例中含有一个页面流simpleForm 和一个JSP。JSP定义了一个绑定到页面流中某个action的NetUI表单。当表单从浏览器中POST的时候,动作表单将被创建,提交的数据将被添加到动作表单中。接着,输入页面被刷新,刚刚在只读label中显示的信息将会显示到可编辑的文本框中。这里所说的页面流是指本文示例应用中的simpleForm。
首先我们来看看动作表单,它们负责封装那些在页面中被显示并更新的数据。该类定义了通过NetUI HTML在JSP表单标签内可编辑的属性;它暴露了唯一一个属性——message。
public static class MessageForm extends FormData { private String message;
public void setMessage(String message) { this.message = message; }
public String getMessage() { return this.message; } } [binding/index.jsp] [simpleForm/Controller.jpf]
|
接着我们来看看JSP页面。这里的netui:form 标签用于引用在表单POST时将会执行的页面流动作。如果您对页面流不熟悉,请参阅参考材料部分的页面流参考手册。一般来说,从可读/写NetUI标签POST到服务器的更新必须在NetUI form 标签中进行。
<netui:form action="echoMessage" focus=""> <table> <tr> <td>Current message:</td> <td><netui:label value="{actionForm.message}"/></td> </tr> <tr><td colspan="2"> </td></tr> <tr> <td><netui:label value="Message:"/></td> <td><netui:textBox dataSource="{actionForm.message}"/></td> </tr> </table> <netui:button value="echoMessage"/> </netui:form> [binding/index.jsp] [simpleForm/index.jsp]
|
JSP中有两个附加NetUI标签;其中netui:textBox 利用表达式{actionForm.message}在当前动作表单中显示message的值。最初该值以及textBox都是空的。还有一个button标签用来把表单提交到服务器。运行例子的时候,请在文本框中输入“Hello World”并按下“Submit”。
看!当表单提交到服务器时,页面流生命周期创建了接收POST数据(即本例中的message)的动作表单。因为textBox的dataSource 属性对应的表达式被映射为HTML输入标签——{actionForm.message},所以message的值被路由到该属性上。
一旦表单填充完毕,postback 动作就会执行并在页面中重新绘制相同的表单。运行结果是:表达式{actionForm.message} 现在同时指向label和textBox 中显示的“Hello World”。在文本框中输入文本并重新提交表单可以修改该值。
技巧和经验
如何访问页面流的成员属性?
页面流可以像其它类一样暴露JavaBean属性;利用表达式和pageFlow 绑定上下文可以绑定这些属性。例如,如果一个页面流用以下方法暴露属性productName:
public String getProductName() { return productName; } [binding/index.jsp] [howdoi/Controller.jpf] |
就可以通过以下表达式在JSP中进行数据绑定:
{pageFlow.productName} [binding/index.jsp] [howdoi/index.jsp] |
你还可以绑定到页面流暴露的公共字段。
应该在页面流上暴露映射吗?
在任何可以接受POST数据的数据绑定上下文中暴露Map类型都是危险的。问题在于映射可以变得很大,这就在服务上开了一个安全漏洞:因为网页可以直接POST数据到Map,这将会导致map无限变大、消耗掉可观的服务器资源。
通过类似request这样的只读数据绑定上下文来暴露Map类型要安全很多。上面的例子中,LocationInfo对象通过abbrevMap 属性实现了这点。
如果在表单上暴露一个int 类型的属性,从页面POST出去的是什么类型?
当JSP页面POST数据到服务器时,所有的数据都以字符串形式到达。应用POST数据到页面流属性或动作表单的过程中,NetUI会设法把已经POST到底层属性或字段的字符串数据转换为原有的类型。
例如,如果动作表单暴露一个类型为int、名称为 age 的属性,并且该字段可以从NetUI表单更新,类型转换工作即是把String 类型的“42”的转换成int 类型的42。如果转换失败,失败信息将会显示在运行WebLogic的命令提示窗口中。
如何显示一个数据列表或数组?
利用NetUI复杂数据绑定标签可以绘制诸如列表、数组、映射、和行集这类的数据集。这些将在接下来的dev2dev文章中讨论。
表达式求值失败会怎样?
当JSP准备绘制时,如果一个表达式求值失败了,JSP页面上将会显示一个失败信息。假定有属性绑定的例子:
Customer Name: <netui:label value="{request.customer.name}"/> [binding/index.jsp] [binding/index.jsp] |
如果request的属性映射中不包含名为“customer”的对象,那么绑定将会失败,因为name属性无法访问null对象。请尝试将例子改为:
Customer Name: [binding/index.jsp] [binding/index.jsp] |
这样就可以知道表达式求值是如何失败的了。
Workshop 8.1 IDE如何帮我进行数据绑定?
Workshop可以用多种方式帮助JSP作者进行数据绑定,其中最重要的一种是通过数据面板。使用数据面板可以在JSP页面中创建能够显示或更新数据的NetUI标签。
例如,在Workshop中打开JSP页面howdoi/index.jsp;在页面上,数据面板显示如下:
您可以看见productName属性在面板中是可用的。如果拖动它到JSP页面中,以下代码将被生成并将在页面中显示产品名称:
<netui:label value="{pageFlow.productName}"></netui:label>
|
试着添加其它属性到页面流中并拖动它们到JSP页面中,从而生成可以显示数据的NetUI标签。
如果页面上有动作表单,它上面的属性也可以被拖动到JSP上。
结论
本文覆盖的范围很广。现在重新回顾一下,表达式语言使得NetUI JSP能够引用业务对象暴露的数据、页面流、以及数据绑定上下文暴露的其它对象和资源。表达式可以引用这些对象的属性和字段,并且NetUI HTML标签既可以用于显示数据,也可以用于从Web浏览器更新数据。
本文附带的应用程序包含多个示例,其中包括基本数据绑定规则的例子、Hello World例子、和一个创建、显示、和编辑Customer对象的复杂例子。
在以后的文章中我们还会介绍更多的内容,希望本文使您对WebLogic Workshop 8.1的从UI到数据的绑定有了初步了解。