Reasonable Server Faces (RSF)基于Spring的Java开源Web框架。它是一个极为轻量级的框架,有纯粹的XHTML模板和完整的生命周期处理。本文定义并演示RSF的三个主要原则:首要标记,零服务状态和POST/GET的正确使用。
RSF,不像其他的Java Web框架,主要侧重于标记和网页设计师的作用。网页设计师不会被框架产生的标记而限制,也不是被迫围绕内嵌代码或是伪代码来设计。模板是纯粹的XHTML不需要框架知识来创建或是维护。这个创建了完全分离的显示和Java代码隐藏。网页设计师和开发者可以独立工作,而且不需要协调他们的工作,这个你将在示例程序中看到。
RSF目的在于用零服务器环境创建一个框架,这样可以通过在循环的***扔掉组件树来节省宝贵的服务器资源。这个有别于很多常见的Java框架,例如JSF,凭借框架执行的***个动作来恢复使用从先前要求中出现的组件树。Session状态是以此来减小较低利用的服务器资源。
RSF目的是通过遵循一个严格的POST->GET重定向以便保留所有的GET请求幂等来遵循在服务器上的正确而有效的浏览器行为。这样,POST纯粹是用来发送数据到服务器上的,而GET是用来返回数据到浏览器上的。这个可以解决很多问题,就是其他框架所面对的浏览器返回钮行为或是深层链接问题。
Recipe List Application
为了演示这些RSF核心组件的主要目的,我们来构建一个非常简单的recipe list应用程序。这个程序将创建一个项目列表,通过允许用户通过网络形式添加更多的项目。这个简单的任务将展示很多的功能包括:模板建设,链接行为,简单的国际化和表格粘贴。
为了开始这个简单的程序,首先你需要为RSF准备好一个环境。RSF的wiki网站有一个很棒的指导来安装RSF Development Environment,但是不在本文的讨论之内。一旦完成安装,你可以通过在程序中创建XHTML模板来开始程序了。如上述所提到的,这些都是纯粹的XHTML模板,用它们唯一所关心的外观所建立的。对于这个简单的程序,要有两页,所以你需要创建两个XHTML模板:
recipelist.html
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
< html xmlns:rsf="http://ponder.org.uk/rsf" xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
< head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> < title rsf:id="msg=title">Recipe List< /title>
< link href="../css/styles.css" type="text/css" rel="stylesheet" media="all"/>
< /head>
< body>
< h1 rsf:id="msg=header">Recipe List< /h1>
< ul rsf:id="message-for:*" style="margin:0px;">
< li>Message for user here< /li>
< /ul>
< h4 rsf:id="current_count">There are currently {0} item(s).< /h4>
< ul>
< li rsf:id="current_items:">
< span rsf:id="row_item">An Item< /span>
< /li>
< /ul>
< a rsf:id="add_item">Add an Item< /a>
< /body>
< /html>
|
itemForm.html
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
< html xmlns:rsf="http://ponder.org.uk/rsf" xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> < head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
< title rsf:id="msg=title">Recipe List< /title>
< link href="../css/styles.css" type="text/css" rel="stylesheet" media="all"/>
< /head>
< body>
< h1 rsf:id="msg=header">Recipe List< /h1>
< ul rsf:id="message-for:*" style="margin:0px;">
< li>Message for user here< /li>
< /ul>
< form rsf:id="form"> < fieldset> < legend rsf:id="msg=form_legend">Add an Item to the List< /legend>
< ol>
< li>
< label rsf:id="msg=label_item">Item:< /label>
< input type="text" rsf:id="item" />
< /li>
< /ol>
< div class="buttonwrap">
< input type="submit" rsf:id="submit" value="Submit" />
< /div>
< /fieldset> < /form>
< /body>
< /html>
|
正如以上所看到的,这些是几乎完全有效的XHTML文件,除了额外的一个单一属性,rsf:id=""。这个是RSF在模板中所代表的唯一机制。事实上,这些模板可以在任何网页浏览器(是忽略额外属性的浏览器)中看到并且可以被验证。任何在模板中的文本或是其他的特性包括rsf:id属性将通过RSF被覆盖,所以模板可以包含如你所希望的"假数据"。这是极其有益的,通过允许假内容的存在,让设计者与开发者对标记结构的意图进行沟通。恰当的rsf:id值列表是唯一的契约可以让设计者和开发者必须互相维持。只要相同的ID以语义上相同的方法来运用,这个想法就会工作。
rsf:id标签指定一个ID让RSF rendering engine有地方连线它的数据。带有冒号(:)的ID使用是一个特殊的协议就是告诉rendering engine这个XHTML节点可能会重复。为了支持国际化,另一个特殊的标签协议是用于直接将文本从一个标准的Java属性组合中提取出来。这个使得开发者通过简单定义一个特殊的rsf:id="msg=property_key"直接连线rsf:id到一个properties bundle key 。对于这个应用程序,所有的页面文本都被拖动到以下的属性捆绑中。
messages.properties title = Recipe List header = Recipe List
form_legend = Add an Item to the List
label_item = Item:
current_items_count = There are currently {0} item(s). |
一旦你开始使用这些模板,你必须为每一个模板创建一个Component Producer。在RSF中的一个component producer是一种机制让Java中组件树可以在其中构建。每一个producer执行ViewComponentProducer接口并且有一个相应的ViewID,可以与模板的文件名相匹配。重载方法fillComponents是组件树构建的地方,用以在模板中符合rsf:ids。参数UIContainer tofill作为组件树(这里可以添加所有的组件)的一个父元素。producer将为recipe list的项目构建组件树,如下:
RecipeListProducer ... public class RecipeListProducer implements ViewComponentProducer, DefaultView {
public static String VIEW_ID = "recipelist";
public String getViewID() {
return VIEW_ID;
} private RecipeListService service; public void setService(RecipeListService service) {
this.service = service; }
public void fillComponents(UIContainer tofill, ViewParameters viewparams,
ComponentChecker checker) {
//Build Recipe List
List< String> items = service.getItems();
UIMessage.make(tofill, "current_count", "current_items_count", new Object[] {items.size()});
for (String item : items){
//Create a new < li> element
UIBranchContainer row = UIBranchContainer.make(tofill, "current_items:");
UIOutput.make(row, "row_item", item);
}
//Create Link to Add Item Form
UIInternalLink.make(tofill, "add_item", new SimpleViewParameters(ItemFormProducer.VIEW_ID));
}
}
|
有了这个producer,一些主要的概念被采用了,还有一些RSF的内置组件。上述的RecipeListService简单的恢复了代表每个recipe list项目的字符串列表。以前,根据研究结果表明用于国际化的信息包文本可以直接通过模板来利用。但是,如果你需要做的不仅仅是输出静态文本,你可以选择使用RSF的UIMessage组件来执行包查找。这里,在从服务器检索recipe项目的列表之后,你想要显示项目的总数,可以使用"current_items_count"信息包。使用UIMessage组件,你可以查找绑定的字符串"current_items_count",编排文本格式添加size()到一个占位符,并且附加它到带有rsf:id="current_count"的DOM节点。
下一步是从服务器上显示每个项目。如以上所提到的,当创建rsf:id属性到designate(在DOM中重复的元素)的时候,你使用冒号标记。在模板中,你想要对每一个在recipe list上的项目重复一个< li>元素。要做到这一点, 把rsf:id="current_items:"(注意***的冒号) 给在模板中的元素。还有另一个注意的是显示的分离。在Java中构建的组件树不会关注使用哪些标记标签;它只知道DOM元素是被重复的。通过设计者的自由裁量,< p>很容易用来代替< li>。为了创建匹配的组件树项目,你可以创建一个UIBranchContainer组件。这个组件可以指示rendering engine,其中在DOM中项目被"绑定"允许循环的发生。每个反复的循环都要创建一个新的UIBranchContainer来告诉组件树有多少个< li>DOM节点你需要去创建。
一旦你有了分支的< li>,你需要输出文本项目。使用UIBranchContainer行作为一个父体(parent),你使用UIOutput来输出recipe list项目的文本到DOM元素,用rsf:id="row_item"。通过设置父体到UIBranchContainer,你会使rsf:id="row_item"元素作为一个子rsf:id="row_item:"元素来呈现。
在producer中的***一步是创建一个内部链接到itemform.html网页。所有链接通过RSF来进行内部管理。为了创建链接,附加组件树上一个UIInternalLink组件到模板的rsf:id="add_item"上。既然你不以GET参数形式来传送数据,你可以利用RSF的SimpleViewParameters。但是,创建自定义的ViewParameters可以传递你希望的数据,但是这超出了本程序的范围。
下一个你创建的producer将构建组件树来处理允许recipe项目被添加到列表上的表格:
ItemFormProducer.java ...
public class ItemFormProducer
implements ViewComponentProducer, NavigationCaseReporter { public static String VIEW_ID = "itemForm";
public String getViewID() {
return VIEW_ID;
}
public void fillComponents(UIContainer tofill, ViewParameters viewparams,
ComponentChecker checker) {
//Build Form
UIForm form = UIForm.make(tofill, "form");
UIInput.make(form, "item", "${RecipeListBean.item}");
UICommand.make(form, "submit", "#{RecipeListBean.processActionSubmit}");
}
public List< NavigationCase> reportNavigationCases() { List< NavigationCase> nav = new ArrayList< NavigationCase>();
nav.add(new NavigationCase("success", new SimpleViewParameters(RecipeListProducer.VIEW_ID)));
return nav; } }
|
这个简单的producer构建了需要处理一个成功的POST的表格元素和滚条。首先,使用RFS组件UIForm添加一个表格组件到组件树中,通过使用在模板中相同的rsf:id="form"。下一步,添加一个组件,UIInput,到UIForm 元素中,这个可以运行用户可以接收的输入。同样的用UICommand组件来构建一个提交按钮。UIInput 和UICommand组件同时采用一个第三方字符串参数来创建一个有价值的binding。这就是RFS的Expression Language的形成。这个要比在JSF中同样的EL简单得多,RSF的EL仅仅指定一个简单的bean路径。这样,当按下提交按钮时,在调用被UICommand value binding 定义的RecipeListBean.processActionSubmit()之前,在UIInput中的值被直接传递到RecipeListBean 的"item"属性。
这个Producer的***一块就是NavigationCaseReporter接口的执行。记住RSF通过一个重定向的GET,遵循每个POST提交。默认情况下,GET请求会遵循来自它们的相同看法。为了重定向用户到不同的地方,可以利用RSF的NavigationCases。Navigation cases被JSF的同样的命名功能所激发并允许来自POST提交的简单的流。在表格中,在成功的添加一个项目到列表中之后,你想重新定向recipe项目的列表。NavigationCase告诉RSF当POST绑定方法RecipeListBean.processActionSubmit的时候,返回字符串"success"然后GET 重定向需要返回RecipeListProducer。除了这个简单的方法之外,RSF也允许其他更先进的创建流的方法。
RecipeListBean.java ...
public class RecipeListBean {
private RecipeListService service;
private String item;
public String processActionSubmit(){
if (item != null && item.length() > 0) {
service.addRecipe(item);
//Return string for NavigationCase
return "success";
}else{
return "failure";
}
}
... Getters and Setters Omitted... |
***一步包括添加eans到在web.xml中定义过的Spring定义中。这个遵循Spring框架的反向控制方法而且非常的直截了当。所有这些配置和整个源代码提供,以供参考。虽然这个程序非常简单平常,但是它强调了RSF的一些核心组件。
【编辑推荐】
- 使用jQuery和PHP构建一个受Ajax驱动的Web页面
- 视频教程:ASP.NET Web开发详解
- .NET平台下Web测试工具横向比较
【责任编辑:彭凡 TEL:(010)68476606】