sdk

88250 的 Blog

请移步:http://88250.b3log.org

Tuesday, August 4, 2009

NetBeans 时事通讯(刊号 # 67 - Aug 04, 2009)

项目新闻

下载 NetBeans IDE 6.8 里程碑 1

NetBeans 团队很高兴的发布了 NetBeans IDE 6.8 里程碑 1 。发布的最值得注意的功能包括嵌入式浏览器,对 Java EE 6 和 Jira 的支持, 以及对PHP, Maven, Ruby 和 C/C++ 的改进。关于其更多的新特性和增强功能请访问 新的 NetBeans 6.8 和值得关注的网页 。最后 NetBeans IDE 6.8 计划在2009年秋季发布。

插件——CronJob 1.0.2:启动和关机工作

另一个来自 Aljoscha Rittner 很酷的插件。(他最后一个模块是 NetBeans 教程 中构建的)。这是一个新插件:允许用户定义 CronJobs,用 Ant-Targets 启动任务和关闭任务。该插件的源代码可以从 kenai.com 下载。有关截图和更多相关信息请参见 Aljoscha 的博客 。(德文。)

文章

"The Definitive Guide to NetBeans Platform" 由谁翻译?

NetBeans 社区 开设了一个崭新的系列,跟您介绍德语英语翻译的"The Definitive Guide to the NetBeans Platform" 。在第一部分,有来自柏林的 Stefan Flemming。

社区

Netbeans 中文社区每周时讯发布项目

image 您正在阅读的这篇 NetBeans 每周时事通讯,由中文社区成员 // --> Vanessa 翻译。中文社区计划长期支持此项目,并期待您的参与。详情请见:NetBeans中文社区首页 以及中文时讯项目首页


NetBeans #50 播客回报!

image NetBeans 播客回来了!到 #50 下载和收听来自 NetBeans 团队的新闻,主要包含了在 NetBeans IDE 中对项目 Kenai 的整合,对 C/C++ 的支持,Jarda's API 设计提示,当然也包含了 NetBeans Puzzler!登录这里 后可在线收听。


博客

JSLint 更紧密集成到 NetBeans

给使用 JavaScript 编码质量工具 JSLint 用户的好消息:Ari Shamash 和 Dominic Mitchell 发布了一个新的 jslint4java,将更好的与 NetBeans 集成。新版本不仅列出了检测到的问题,还让使用者在 JavaScript 文件中只需要点击一下输出窗口便可导航到受影响的行。

还有多少路要走?框架体验

系紧你的运动鞋!使用 JRuby,Ruby-on-Rails,GlassFish,NetBeans,MySQL,和 YUI Charts。Arun Gupta 构建了一个应用,可以提供基本的运行过程跟踪和生成进展图标。这个应用是一个使用不同 Web 框架的实验。了解更多请访问 Arun's 博客。

练习

Windows Mobile 中 JavaFX Mobile 2.0 初期入门

该视频来自 Terrence Barr,展示了 JavaFX Mobile 1.2 早期访问在 Windows Mobile 中是如何创建的。它展现了如何在 HTC Diamond 上运行一个简单的应用程序(Mosaic),包括使用 deploy-via-USB 功能。

JAX-WS Web 服务客户端的开发

在这个教程中,可以了解到 NetBeans IDE 提供了使用 web 服务设备分析 Spell Checker web 服务,然后建立一个 web 客服端与之交互。

使用 JSON 连接 Dojo Tree 到数组列表

本教程演示了如何使用 JSON 添加和配置一个 Dojo Tree widget 到数组列表。


发布本期时事通讯的是: D. L. 88250
如果您不想接收时事通讯, 请由此退订

More......

JSF 2 简介,第 3 部分: 事件处理、JavaScript 和 Ajax

JSF 2 简介,第 3 部分: 事件处理、JavaScript 和 Ajax

使用更多新 JSF 2 特性增强复合组件





级别: 中级

David Geary , 总裁, Clarity Training, Inc.

2009 年 8 月 03 日

Java™Server Faces (JSF) 2 专家组成员 David Geary 将在这一期文章中结束这部有关 JSF 2 新特性的 系列文章(共 3 部分) 。本文介绍如何使用该框架的新事件模型和内置 Ajax 支持来增强可重用组件的功能。

JSF 的最大卖点在于它是一种基于组件的框架。这意味着您可以实现供其他人重用的组件。这种强大的重用机制在 JSF 1 中基本上是不可能实现的,因为在 JSF 1 中实现组件是非常困难的事情。

然而,正如 第 2 部分 所述,JSF 2 通过一种名为复合组件 的新特性简化了组件的实现 — 无需 Java 代码和配置。这一特性可以说是 JSF 2 中最重要的部分,因为它真正实现了 JSF 组件的潜力。

在这份有关 JSF 2 的第三篇也是最后一篇文章中,我将展示如何利用新的 Ajax 和事件处理功能(也在 JSF 2 中引入)构建复合组件特性,要从 JSF 2 中获得最大收益,需要遵循下面的技巧:

  • 技巧 1:组件化
  • 技巧 2:Ajax 化
  • 技巧 3:展示进度

对于第一个技巧,我将简要回顾已在 第 2 部分 中详细描述过的两个组件。对于后面的技巧,我将展示如何使用 Ajax 和事件处理功能来改造这些组件。

技巧 1:组件化

我在 第 1 部分 中引入的 places 应用程序包含有大量复合组件。其中之一便是 map 组件,它显示一个地址地图,其中包含一个缩放级别下拉菜单,如图 1 所示:


图 1. places 应用程序的 map 组件
map 组件

清单 1 显示了经过删减的 map 组件列表:


清单 1. map 组件
				






















...





<h:selectOneMenu
onchange="submit()"

value="#{cc.parent.attrs.location.zoomIndex
}"

valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"

>















<h:graphicImage
url="#{cc.parent.attrs.location.mapUrl}"

style="border: thin solid gray"/>

...





...








组件的一大优点就是可以使用更有效的替代方法替换它们,同时不会影响到相关的功能。比如,在图 2 中,我使用一个 Google Maps 组件替换了 清单 1 中的 image 组件,Google Maps 组件由 GMaps4JSF 提供(见 参考资料 ):


图 2. GMaps4JSF 的 map 图像
GMaps4JSF map 组件

map 组件的更新后的代码(进行了删减)如清单 2 所示:


清单 2. 使用一个 GMaps4JSF 组件替换 map 图形
				


onchange="submit()"


value
="#{cc.parent.attrs.location.zoomIndex
}"

valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"

style="font-size:13px;font-family:Palatino">











...



<m:map
id="map" width="420px" height="400px"

address="#{cc.parent.attrs.location.streetAddress}, ..."

zoom
="#{cc.parent.attrs.location.zoomIndex
}"

renderOnWindowLoad="false">
















要使用 GMaps4JSF 组件,我从 GMaps4JSF 组件集合中使用 标记替换了 标记。将 GMaps4JSF 组件与缩放下拉菜单连接起来也很简单,只需为 标记的 zoom 属性指定正确的 backing-bean 属性。

关于缩放级别需要注意一点,那就是当一名用户修改缩放级别时,我将通过 onchange 属性强制执行表单提交,如 清单 1 中第一处使用粗体显示的代码行所示。这个表单提交将触发 JSF 生命周期,这实际上将把新的缩放级别推入到保存在父复合组件中的 location bean 的 zoomIndex 属性中。这个 bean 属性被绑定到输入组件,如 清单 2 中的第一行所示。

由于我没有为与缩放级别修改相关的表单提交指定任何导航,JSF 在处理请求后刷新了同一页面,重新绘制地图以反映新的缩放级别。然而,页面刷新还重新绘制了整个页面,即使只修改了地图图像。在 技巧 2:Ajax 化 中,我将展示如何使用 Ajax,只对图像部分重新绘制,以响应缩放级别的修改。

login 组件

places 应用程序中使用的另一个组件是 login 组件。图 3 展示了这个 login 组件的实际使用:


图 3. login 组件
login 组件

清单 3 展示了创建 图 3 所示的 login 组件的标记:


清单 3. 最基础的 login :只包含必需的属性
				






loginAction
="#{user.login}"

managedBean
="#{user}"/>






login 组件只包含两个必需的属性:

  • loginAction :登录 action 方法
  • managedBean :包含名称和密码属性的托管

清单 3 中指定的托管 bean 如清单 4 所示:


清单 4. User.groovy
				


package com.clarity



import javax.faces.context.FacesContext

import javax.faces.bean.ManagedBean

import javax.faces.bean.SessionScoped



@ManagedBean()

@SessionScoped



public class User {

private final String VALID_NAME = "Hiro"

private final String VALID_PASSWORD = "jsf"



private String name, password
;



public String getName() { name }

public void setName(String newValue) { name = newValue }



public String getPassword() { return password }

public void setPassword(String newValue) { password = newValue }



public String login()
{

"/views/places"

}



public String logout() {

name = password = nameError = null

"/views/login"

}

}


清单 4 中的托管 bean 是一个 Groovy bean。在这里使用 Groovy 替代 Java 语言并不会带来多少好处,只是减少了处理分号和返回语句的麻烦。然而,在技巧 2 的 Validation 部分中,我将展示一个对 User 托管 bean 使用 Groovy 的更有说服力的原因。

大多数情况下,您将需要使用提示和按钮文本来配置登录组件,如图 4 所示:


图 4. 充分配置的 login 组件
充分配置的 login 组件

清单 5 展示了生成 图 4 所示的 login 组件的标记:


清单 5. 配置 login 组件
				













清单 5 中,我从一个资源包中获取了用于提示的字符串和登录按钮的文本。

清单 6 定义了 login 组件:


清单 6. 定义 login 组件
				
















<composite:interface
>









































<composite:implementation
>



#{cc.attrs.loginPrompt
}















#{cc.attrs.namePrompt
}

cc.attrs.managedBean.name
}"/>



#{cc.attrs.passwordPrompt
}

cc.attrs.managedBean.password
}"/>











cc.attrs.loginButtonText
}"

action="#{cc.attrs.loginAction
}"/>










map 组件一样,login 也可以使用一个 Ajax 升级。在下一个技巧介绍 Validation 时,我将展示如何将 Ajax 验证添加到 login 组件中。





回页首


技巧 2:Ajax 化

与非 Ajax HTTP 请求相比,Ajax 请求通常需要额外执行两个步骤:在服务器中对表单进行局部处理,接着在客户机上对 Document Object Model (DOM) 进行局部呈现。

局部处理和呈现

通过将 JSF 生命周期分解为两个不同的逻辑部分 —— 执行和呈现,JSF 2 现在支持局部处理和局部呈现。图 5 突出显示了执行部分:


图 5. JSF 生命周期的执行部分
JSF 生命周期的执行部分

图 6 突出显示了 JSF 生命周期的呈现部分:


图 6. JSF 生命周期的呈现部分
JSF 生命周期的呈现部分

将生命周期划分为执行和呈现部分的原理很简单:您可以指定 JSF 在服务器上执行(处理)的组件,以及在返回 Ajax 调用时 JSF 呈现的组件。将使用 JSF 2 中新增的 实现这个目的,如清单 7 所示:


清单 7. 一个 Ajax 缩放菜单
				


value
="#{cc.parent.attrs.location.zoomIndex}"

style="font-size:13px;font-family:Palatino">














map
"...>


清单 7清单 2 中的第一行所示的菜单进行了修改:我从 清单 2 中删除了 onchange 属性,并添加了一个 标记。这个 标记指定了以下内容:

  • 触发 Ajax 调用的事件
  • 在服务器上执行的组件
  • 在客户机上呈现的组件

当用户从缩放菜单中选择一个菜单项时,JSF 将对服务器发出 Ajax 调用。随后,JSF 将菜单传递给生命周期的执行部分(@this 表示 周围的组件),并在生命周期的 Update Model Values 阶段更新菜单的 zoomIndex 。当 Ajax 调用返回后,JSF 呈现地图组件,后者使用(新设置的)缩放指数重新绘制地图,现在您就有了一个 Ajax 化的缩放菜单,其中添加了一行 XHTML。

但是还可以进一步简化,因为 JSF 为 eventexecute 属性提供了默认值。

每个 JSF 组件都有一个默认事件,当在组件标记内部嵌入 标记时,该事件将触发 Ajax 调用。对于菜单,该事件为 change 事件。这意味着我可以删除 清单 7 中的 event 属性。execute 属性的默认值是 @this ,这表示围绕在 周围的组件。在本例中,该组件为菜单,因此还可以删除 execute 属性。

通过对 使用默认属性值,我可以将 清单 7 简化为清单 8:


清单 8. 简化后的 Ajax 缩放菜单
				




















这演示了使用 JSF 2 向组件添加 Ajax 有多么容易。当然,前面的例子非常简单:我仅仅是在用户选择某个缩放级别时重新绘制了地图而不是整个页面。验证表单中的各个字段等操作要更加复杂一些,因此接下来我将讨论这些用例。

验证

当用户移出某个字段后对字段进行验证并提供即时的反馈,这始终是一个好的做法。例如,在图 7 中,我使用了 Ajax 对名称字段进行了验证:


图 7. Ajax 验证
Ajax 验证

该名称字段的标记如清单 9 所示:


清单 9. 名称字段
				




#{cc.attrs.namePrompt}



valueChangeListener
="#{cc.attrs.managedBean.validateName}">



<f:ajax
event="blur
" render="nameError
"/>







nameError
"

value="#{cc.attrs.managedBean.nameError
}"

style="color: red;font-style: italic;"/>



...




我再一次使用了 ,只不过这一次没有执行输入的默认事件 — change ,因此我将 blur 指定为触发 Ajax 调用的事件。当用户移出名称字段时,JSF 将对服务器发出 Ajax 调用并在生命周期的执行部分运行 name 输入组件。这意味着 JSF 将在生命周期的 Process Validations 阶段调用 name 输入的值修改监听程序(在 清单 9 中指定)。清单 10 展示了这个值修改监听程序:


清单 10. validateName() 方法
				


package com.clarity



import javax.faces.context.FacesContext

import javax.faces.bean.ManagedBean

import javax.faces.bean.SessionScoped

import javax.faces.event.ValueChangeEvent

import javax.faces.component.UIInput



@ManagedBean()

@SessionScoped



public class User {

private String name, password, nameError;



...



public void validateName
(ValueChangeEvent e) {

UIInput nameInput = e.getComponent()

String name = nameInput.getValue()



if (name.contains("_")) nameError
= "Name cannot contain underscores"

else if (name.equals("")) nameError
= "Name cannot be blank"

else nameError
= ""

}



...

}


这个修改值的监听程序(user 托管 bean 的 validateName() 方法)将验证名称字段并更新 user 托管 bean 的 nameError 属性。

返回 Ajax 调用后,借助 清单 9 中的 标记的 render 属性,JSF 呈现 nameError 输出。该输出显示了 user 托管 bean 的 nameError 属性。

多字段验证

在前面的小节中,我展示了如何对单一字段执行 Ajax 验证。但是,有些情况下,需要同时对多个字段进行验证。比如,图 8 展示了 places 应用程序同时验证名称和密码字段:


图 8. 验证多个字段
验证多个字段

我在用户提交表单时同时验证了名称和密码字段,因此对这个例子不需要用到 Ajax。相反,我将使用 JSF 2 的新事件系统,如清单 11 所示:


清单 11. 使用
				






<f:event
type="postValidate
"

listener="#{cc.attrs.managedBean.validate}"/>

...












清单 11 中,我使用了 — 类似于 ,它是 JSF 2 中新增的内容。 标记在另一方面还类似于 :使用起来很简单。

将一个 标记放到组件标记的内部,当该组件发生指定的事件(使用 type 属性指定)时,JSF 将调用一个使用 listener 属性指定的方法。因此, 标记在 清单 11 中的含义就是:对表单进行验证后,对用户传递给这个复合组件的托管 bean 调用 validate() 方法。该方法如清单 12 所示:


清单 12. validate() 方法
				


package com.clarity



import javax.faces.context.FacesContext

import javax.faces.bean.ManagedBean

import javax.faces.bean.SessionScoped

import javax.faces.event.ValueChangeEvent

import javax.faces.component.UIInput



@ManagedBean()

@SessionScoped



public class User {

private final String VALID_NAME = "Hiro";

private final String VALID_PASSWORD = "jsf";



...



public void validate
(ComponentSystemEvent e) {

UIForm form = e.getComponent()

UIInput nameInput = form.findComponent("name")

UIInput pwdInput = form.findComponent("password")



if ( ! (nameInput.getValue().equals(VALID_NAME) &&

pwdInput.getValue().equals(VALID_PASSWORD))) {



FacesContext fc = FacesContext.getCurrentInstance()

fc.addMessage(form.getClientId(),

new FacesMessage("Name and password are invalid. Please try again."))

fc.renderResponse()

}

}



...

}


JSF 将一个组件系统事件传递给 清单 12 中的 validate() 方法,方法从这个事件中获得对(适用于事件的)组件的引用 — 登录表单。对于这个表单,我使用 findComponent() 方法获得名称和密码组件。如果这些组件的值不为 Hiro 和 jsf,那么我将把一条消息存储到 faces 上下文并要求 JSF 继续处理生命周期的 Render Response 阶段。通过这种方法,就可以避免 Update Model Values 阶段,后者会将坏的名称和密码传递给模型(见 图 5 )。

您可能已经注意到,清单 10清单 12 中的验证方法是使用 Groovy 编写的。与 清单 4 不同,后者使用 Groovy 的惟一好处就是避免了分号和返回语句,清单 10清单 12 中的 Groovy 代码使我不必进行类型转换。例如,在 清单 10 中,ComponentSystemEvent.getComponent()UIComponent.findComponent() 都返回类型 UIComponent 。对于 Java 语言,我需要转换这些方法的返回值。Groovy 为我做了这一转换工作。





回页首


技巧 3:展示进度

Ajax 化 中,我展示了如何为 map 组件 Ajax 化缩放菜单,因此,当用户修改缩放级别时,places 应用程序将只重新绘制页面的地图部分。另一个常见 Ajax 用例是向用户提供反馈,表示一个 Ajax 事件正在处理中,如图 9 所示:


图 9. 进度条
进度条

图 9 中,我将使用一个动画 GIF 替换缩放菜单,这个动画 GIF 将在 Ajax 调用期间显示。当 Ajax 调用完成后,我将使用缩放菜单替换进度指示器。清单 13 展示了这一过程:


清单 13. 监视 Ajax 调用
				






onevent="zoomChanging"
/>





...



...

progressbar
" style="display: none"

library="images" name="orange-barber-pole.gif"/>


清单 13 中,我添加了一个进度条图像(该图像最初不会显示出来),并为 指定了 onevent 属性。该属性引用一个 JavaScript 函数,如 清单 14 所示,这个函数将在 清单 13 中的 Ajax 调用被初始化时由 JSF 调用:


清单 14. 响应 Ajax 请求的 JavaScript
				


function zoomChanging(data) {

var menuId = data.source.id;

var progressbarId = menuId.substring(0, menuId.length - "menu".length)

+ "progressbar";



if (data.name == "begin") {

Element.hide(menuId);

Element.show(progressbarId);

}

else if (data.name == "success") {

Element.show(menuId);

Element.hide(progressbarId);

}

}


JSF 向 清单 14 中的函数传递一个对象,该对象中包含有一些信息,比如触发了事件的组件的客户机标识符(在本例中为缩放级别菜单),以及 Ajax 请求的当前状态,使用 name 属性表示。

清单 14 中的 zoomChanging() 函数计算进度条图像的客户机标识符,然后使用 Prototype Element 对象在 Ajax 调用期间隐藏和显示对应的 HTML 元素。





回页首


结束语

在过去几年中,人们认为 JSF 1 是一个难以使用的框架。在许多方面上,这种评价是有一定道理的。JSF 1 在开发期间完全没有考虑到实际使用中遇到的问题。因此,JSF 在实现应用程序和组件方面比预先设想更加困难。

另一方面,JSF 2 经历了那些在 JSF 1 基础之上实现过开源项目的开发人员的实际体验。总结经验之后,JSF 2 是一个更加合理的框架,可以轻松地实现健壮的、Ajax 化的应用程序。

贯 穿本系列文章,我展示了一些最突出的 JSF 2 特性,比如注释和替换配置约定、简化后的导航、资源支持、复合组件、内置 Ajax 以及内嵌的事件模型。但是仍然有许多 JSF 2 特性未在本系列提及,比如 View 和 Page 范围、为页面添加书签的支持和 Project Stage。所有这些特性以及其他更多特性让 JSF 2 在 JSF 1 的基础上实现了巨大的改进。






回页首


下载

描述 名字 大小 下载方法
本文示例的源代码 j-jsf2-fu-3.zip 7.7MB HTTP
关于下载方法的信息


参考资料

学习

获得产品和技术

讨论


关于作者

David Geary

David Geary 是一名作家、演讲家和顾问,也是 Clarity Training, Inc. 的总裁,他指导开发人员使用 JSF 和 Google Web Toolkit (GWT) 实现 Web 应用程序。他是 JSTL 1.0 和 JSF 1.0/2.0 专家组的成员,与人合作编写了 Sun 的 Web Developer 认证考试的内容,并且为多个开源项目作出贡献,包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是关于 Java 的畅销书籍,而 Core JSF (与 Cay Horstman 合著)是关于 JSF 的畅销书。David 经常在各大会议和用户组发表演讲。他从 2003 年开始就一直是 NFJS tour 的固定演讲人,并且在 Java University 教授课程,两次当选为 JavaOne 之星。


More......

Monday, August 3, 2009

NetBeans IDE 6.8 Milestone 1 已经可用!

NetBeans 团队非常高兴地宣布 NetBeans IDE 6.8 Milestone 1 已经可用!


新版本的特性包括:

  • IDE 内置的浏览器
  • Java EE 6 支持
  • PHP
    • PHP 5.3.0 支持
  • Maven
    • 为 Groovy 与 Scala 增强了支持
  • C/C++
    • 远程开发
    • 代码辅助
  • Ruby
    • JRuby bundle 更新至 1.3.1
    • RSpec 1.2.7 支持
  • Jira 问题追踪支持

访问 NetBeans 6.8 New and Noteworthy page 以学习更多关于这个里程碑 发布的新特性与改进。NetBeans IDE 6.8 计划将于 2009 年秋正式发布。


More......

JSF 2 简介,第 2 部分: 模板及复合组件

JSF 2 简介,第 2 部分: 模板及复合组件

用 JavaServer Faces 2 实现可扩展 UI





级别: 中级

David Geary , 总裁, Clarity Training, Inc.

2009 年 6 月 25 日

模板和复合组件是 Java™Server Faces (JSF) 2 的两个功能强大的特性,借助这两个特性,您就可以实现易于修改和扩展的用户界面。在本文 — 共三部分的 系列文章 的第 2 部分 — 中,JSF 2 专家组成员 David Geary 将向您展示如何在您的 Web 应用程序中利用模板和复合组件。

早在 2000,当我还是 JavaServer Pages(JSP)邮件列表中的一个活跃分子的时候,我遇到了 Craig McClanahan,当时他正忙着开发一个新的 Web 框架,称为 Struts。在那时,我还正在从 Swing 转向服务器端 Java 编程,所以我已经实现了一个很小的框架来分离 JSP 视图布局及其内容,这非常类似于 Swing 布局管理器的理念。Craig 问我,是否愿意将我的模板 库包含在 Struts 内,我欣然同意了。这样一来,与 Struts 1.0 捆绑的 Struts Template Library 遂成为了 Struts 流行的 Tiles 库的基础,而 Tiles 库最终成为了一个顶级的 Apache 框架。

JSF 2 现在的默认显示技术 — Facelets — 就是一个模板框架,在很大程度上基于的是 Tiles。JSF 2 还提供了一个功能强大的机制,称为复合组件 ,该机制构建在 Facelets 的模板特性之上,因此,在无需任何 Java 代码和 XML 配置的情况下就可以实现定制组件。在本文中,我将向您介绍模板和复合组件,并且还会给出如何充分利用 JSF 2 的三个技巧:

  • 技巧 1:遵守 DRY 原则
  • 技巧 2:使用组合的方式
  • 技巧 3:牢记 LEGO 拼装玩具的理念
Facelets 和 JSF 2

在标准化开源 Facelets 实现的同时,JSF 2 专家组还对底层的 Facelets API 进行了更改,但保留了与标记库的后向兼容性。这意味着用开源 Facelets 所实现的现有视图均应适用于 JSF 2。

在 Rick Hightower 的这两篇文章 “Facelets 非常适合 JSF ” 和 “高级 Facelets 编程 ” 中可以找到有关 Facelets 众多特性的更多信息。

技巧 1:遵守 DRY 原则

在我作为软件开发人员从事的第一项工作中,我的任务是为基于 UNIX® 的计算机辅助设计和计算机辅助制造(CAD/CAM)系统实现一个 GUI。

最初,一切进行顺利,但是一段时间后,我的代码开始问题不断。待到代码发布的时候,系统已经相当脆弱,我甚至都害怕修复 bug,而这次的代码发布自然也伴随着一连串的 bug 报告。

如果我在这个项目中遵循了 DRY 原则 — 不重复自己(Don't Repeat Yourself),我本可以让自己不至于这么悲惨。DRY 原则最初由 Dave Thomas 和 Andy Huntprinciple 提出(参见 参考资料 ),它要求:

每条知识都必须在系统内具有一个单一、清晰和权威的表示。

我的 CAD/CAM 应用程序并不符合 DRY 原则 — 它具有太多关注点之间的交叉 — 因此在一个地方所做的更改常常会在其他地方引起意想不到的更改。

JSF 1 在几个方面违背了 DRY 原则,比如,它强迫您提供托管 beans 的两种表示 — 一个使用 XML,一个使用 Java 代码。对多重表示的需求让创建和更改托管 bean 更加困难。正如我在本系列 第 1 部分 中介绍的,JSF 2 让您能够使用注释取代 XML 来配置托管 bean,这样一来,托管 bean 就具有了一个单一、权威的表示。

除托管 beans 之外,就连一些看似有益的实践 — 比如在所有视图中包括相同的样式表 — 也违背了 DRY 原则,并会导致混乱。比如,如果要更改样式表的名字,就必须更改多个视图。如果可能,最好是封装此样式表包含。

DRY 原则同样适用于代码设计。如果多个方法均包含遍历树的代码,一种好的做法是(比如在一个子类中)封装遍历树的算法。

在实现 UI 时,因大多数更改均在开发过程中发生,所以遵守 DRY 原则尤其重要。

JSF 2 模板

JSF 2 在很多方面都支持 DRY 原则,其中之一就是通过模板 。模板能够封装在应用程序视图中十分常见的功能,因此该功能只需被指定一次。在 JSF 2 应用程序中,一个模板可供多个组装(compositions) 用于创建视图。

我在 第 1 部分 中所介绍的 places 应用程序具有三个视图,如图 1 所示:


图 1. places 应用程序的视图:Login、source viewer 和 places

places 应用程序的视图 places 应用程序的图标 places 应用程序的图标

与很多 Web 应用程序一样,这个 places 应用程序包含多个具有相同布局的视图。JSF 模板功能让您能够在一个模板内封装该布局 — 及其他共享工件,比如 JavaScript 和 Cascading Style Sheets(CSS)。清单 1 是 图 1 中所示的这三个视图的模板:


清单 1. places 模板:/templates/masterLayout.xhtml
				












<br /><br /> <<span class="boldcode">ui:insert</span><br />name="windowTitle"><br /><br /> #{msgs.placesWindowTitle}<br /><br /> </ui:insert><br /><br />















<ui:insert
name="heading">

#{msgs.placesHeading}














清单 1 中的模板为此应用程序的所有视图提供了如下的基础设施:

  • HTML
  • 一个默认标题(可由使用此模板的那些组装覆盖)
  • 一个 CSS 样式表
  • 某些实用 JavaScript
  • 一个布局,格式为
    ,以及对应的 CSS 类
  • 头的默认内容(可被覆盖)
  • 右菜单的默认内容(可被覆盖)

正如 清单 1 所示,模板通过 标记将内容插入到布局中。

如为 标记指定了主体,正如我在 清单 1 中为窗口标题、头和右菜单所做的,JSF 会将此标记的主体作为默认内容 。借助 标记,使用此模板的那些封装可以定义内容或者覆盖默认内容,如清单 2 所示,它给出了 login 视图的标记:


清单 2. login 视图
				


template="/templates/masterLayout.xhtml"
>



<ui:define
name="menuLeft">







<ui:define
name="content">










这个 login 视图为窗口的标题、头和右菜单使用了模板的默认内容。它只定义了特定于此 login 视图的功能:内容部分和左菜单。

通过为窗口标题、头或右菜单提供 标记,我也可以覆盖此模板的默认内容。比如,清单 3 显示了这个 source-viewer 视图(图 1 中间的图片):


清单 3. source-viewer 视图
				


template="/templates/masterLayout.xhtml"
>



<ui:define
name="content">







<ui:define
name="menuLeft
">







<ui:define
name="menuRight">










source-viewer 视图定义了内容部分以及右菜单的内容。它还覆盖了由 清单 1 中的模板定义的针对左菜单的默认内容。

清单 4 显示了 places 视图(图 1 底部的图片):


清单 4. places 视图
				


template="/templates/masterLayout.xhtml"
>



<ui:define
name="menuLeft">







<ui:define
name="content">










JSF 2 模板功能

模板功能背后的概念十分简单。定义一个模板来封装在多个视图中常见的功能。每个视图由一个组装和一个模板组成。

当 JSF 创建视图时,它加载组装的模板,然后将由组装所定义的内容插入模板。

请注意清单 234 之间的相似性。所有这三个视图均指定模板并定义内容。另外,也请注意创建新视图十分容易,因为大多数视图的基础设施都封装在模板及所包含的文件内。

使用 JSF 模板功能的另一个有趣之处是类似清单 234 中的这些视图并不会随时间有太多变化,所以大部分视图代码基本不需要维护。

与使用模板的视图类似,模板本身也更改甚少。由于大量常见功能都封装在几乎不用维护的代码中,这样一来,您就可以将精力集中于视图的实际内容 — 比如,login 页面的左菜单应该有些什么内容。专心于视图的实际内容就是下一个技巧的主旨所在。





回页首


技巧 2:使用组合的方式

在我的 CAD/CAM GUI 发布后不久,我花了几个月的时间与另一位开发人员 Bob 致力于一个新的项目。我们以 Bob 的代码为基础,而且不可思议地是,我们还能轻松进行更改并修复 bug。

我很快意识到 Bob 的代码和我的代码之间的最大区别是他编写了 方法 — 通常是在代码的 5 至 15 行之间 — 并且他的整个系统都是由这些小方法拼接而成的。在我还在忙着修改我之前项目中具有很多关注点的长方法时,Bob 已经开始机敏地组合小方法和原子功能性了。Bob 的代码和我的代码在维护性和可扩展性方面自然也有着天壤之别,从那以后,我开始信服小方法。

虽然 Bob 和我那时都没有意识到,但是我们过去一直在使用 Smalltalk 的一种设计模式,称为 Composed Method(参见 参考资料 ):

在一个抽象级别,将软件分成能执行单个任务的多个方法。

使用 Composed Method 模式的好处已经有大量书面记载(详细说明,请参见 Neal Ford 的 “演化架构与紧急设计:组合方法和 SLAP ” )。在这里,我将侧重于介绍如何在 JSF 视图中使用 Composed Method 模式。

JSF 2 鼓励使用较小的视图段组装视图。模板封装了常见功能,进而将视图分成了更小的块。JSF 2 还提供了一个 标记,正如我在先前的代码清单中所展示的,这个标记可以让您将视图进一步分成更小的功能块。比如,图 2 展示了 places 应用程序的 login 页面的左菜单:


图 2. login 页面的左菜单
login 视图的左菜单

清单 5 显示了定义该菜单内容的文件:


清单 5. login 视图左菜单的实现
				













清单 5 内的标记很简单,这就使文件更易于阅读、理解、维护和扩展。如果相同的代码埋藏在一个很长的、包含实现 login 视图所需的全部内容的 XHTML 页面内,那么它更改起来将会很繁琐。

图 3 显示了 places 视图的左菜单:


图 3. places 视图的左菜单
places 视图的左菜单

places 视图的左菜单的实现如清单 6 所示:


清单 6. places 视图的左菜单的实现
				










#{msgs.findAPlace}











#{msgs.streetAddress}





#{msgs.city}

#{msgs.state}

#{msgs.zip}















image
="#{resource['images:back.jpg']}"

actionMethod
="#{places.logout}"

style="border: thin solid lightBlue"/>






清单 6 实现了一个表单,并且此表单使用了一个图标组件。(我随后会在 图标组件 一节对该图标组件进行详细讨论。目前,只需知道页面作者可以用一个图标关联图像和方法。)这个 logout 图标的图像显示在 图 3 的底部,而此 logout 图标的方法 — places.logout() — 则如清单 7 所示:


清单 7. Places.logout() 方法
				


package com.clarity;

...

@ManagedBean()

@SessionScoped



public class Places {

private ArrayList places = null;

...

private static SelectItem[] zoomLevelItems = {

...

public String logout() {

FacesContext fc = FacesContext.getCurrentInstance();

ELResolver elResolver = fc.getApplication().getELResolver();



User user = (User)elResolver.getValue(

fc.getELContext(), null, "user");



user.setName("");

user.setPassword("");



setPlacesList(null);



return "login";

}

}


对我而言,清单 6 — places 视图的左菜单的实现 — 已经十分接近 30 行的代码长度限制。此清单有点难于读懂,并且该代码片段内的表单和图标可被重构成各自的文件。清单 8 显示了 清单 6 的重构版,其中,表单和图标被封装进各自的 XHTML 文件:


清单 8. 重构 places 视图的左菜单
				










#{msgs.findAPlace}





addressForm.xhtml
">

logoutIcon.xhtml
">








清单 9 显示了 addressForm.xhtml:


清单 9. addressForm.xhtml
				












#{msgs.streetAddress}





#{msgs.city}

#{msgs.state}

#{msgs.zip}
















清单 10 显示了 logoutIcon.xhtml:


清单 10. logoutIcon.xhtml
				













在从多个小文件组装视图时,就可享受到 Smalltalk 的 Composed Method 模式的益处。您还可以组织这些文件以便更易于对更改做出反应。例如,图 4 显示了构成 places 应用程序内的这三个视图的文件:


图 4. places 应用程序的视图
places 应用程序的视图

我 所创建的这三个目录 — views、sections 和 templates — 包含了用来实现 places 应用程序视图的大多数 XHTML 文件。由于 views 和 templates 目录内的文件很少更改,因此我更多关注的是 sections 目录。例如,若我想要更改 login 页面左菜单内的图标,我就知道该到哪里去更改:sections/login/menuLeft.xhtml。

当然,您可以使用任何目录结构来组织您的 XHTML 文件。如果组织得合理,定位想要修改的代码就会非常容易。

除了遵循 DRY 原则和使用 Composed Method 模式之外,还有一种好的做法是在定制组件内封装功能。组件是一种功能强大的重用机制,而且您应该充分利用这种强大性。与 JSF 1 不同,使用 JSF 2 更易于实现定制组件。





回页首


技巧 3:牢记 LEGO 拼装玩具的理念

在我还是一个男孩的时候,我有两个最喜欢的玩具:一个是化学组合(chemistry set),一个是 LEGO 拼装玩具。这两种玩具让我能够通过组合基本的构建块来创建东西,而这也成为了我一生的爱好,只不过现在是打着软件开发的幌子。

JSF 的优势一直都在于其组件模型,但这种优势直到现在才完全实现,因为用 JSF 1 很难实现定制组件。您必须要编写 Java 代码、指定 XML 配置,并对 JSF 的生命周期有深刻的理解。有了 JSF 2,您就能够轻松实现定制组件:

  • 无需配置、XML 或其他。
  • 无需 Java 代码。
  • 开发人员可以向其附加功能。
  • 修改后执行热部署。

在本文的剩余部分,我将向您介绍如何为 places 应用程序实现三个定制组件:一个图标、一个 login 面板和一个显示了地址地图和天气信息的面板。但是首先,让我先来概括介绍一下 JSF 2 复合组件。

实现定制组件

JSF 2 综合了 Facelets 模板 、资源处理(在 第 1 部分 中讨论过)和一个简单的命名约定来实现复合组件 。复合组件,正如其名字所示,让您能够从现有组件组装一个新组件。

一般情况下,是在 resources 目录下的 XHTML 内实现复合组件,并将它们完全通过约定链接到一个名称空间和标记。图 5 展示了我是如何为 places 应用程序组织这些复合组件的:


图 5. places 应用程序的组件
places 应用程序的组件

要使用复合组件,需要声明一个名称空间并使用标记。此名称空间通常为 http://java.sun.com/jsf/composite 外加目录名,这个目录就是 resources 目录下组件所在之处。组件名本身是其 XHTML 文件的名字,只不过没有 .xhtml 扩展名。这种约定消除了对配置的需要。比如,要在 places 应用程序中使用 login 组件,应该这样做:

/component/util

">

...

<util:login
.../>

...




而要使用 icon 组件,则需要像下面这样:

/components/util

">

...

<util:icon
.../>

...




最后,若要使用 place 组件,则可按如下所示的这样做:

/components/places

">

...

<places:place
.../>

...




icon 组件:一个简单的复合组件

places 应用程序使用了图 6 所示的这两个图标:


图 6. places 应用程序的图标

places 应用程序的图标 places 应用程序的图标

每个图标都是一个链接。当用户单击 图 6 左侧的图标时,JSF 就会显示当前视图的标记,而激活右侧图标则会使用户登出此应用程序。

可以为链接指定一个 CSS 类名和图像,并且还可以向链接附加方法。当用户单击一个被关联的链接时,JSF 就会调用那些方法。

清单 11 给出了 icon 组件是如何被用来在 places 应用程序中显示标记的:


清单 11. 使用 icon 组件显示标记
				






actionMethod
="#{sourceViewer.showSource
}"

image
="#{resource['images:disk-icon.jpg
']}"/>

...






清单 12 给出了如何使用 icon 组件执行登出:


清单 12. 使用 icon 组件执行登出
				






actionMethod
="#{places.logout
}"

image
="#{resource['images:back-arrow.jpg
']}"/>

...




清单 13 给出了 icon 组件的代码:


清单 13. icon 组件
				








<composite:interface
>

<composite:attribute
name="image"/>

<composite:attribute
name="actionMethod"

method-signature="java.lang.String action()"/>







<composite:implementation
>



cc.attrs.actionMethod
}" immediate="true">



cc.attrs.image
}"

styleClass
="icon"/>












与其他复合组件类似,清单 13 中的 icon 组件包含两节: 节定义了一个界面,可用来配置此组件。icon 组件具有两个属性:imageactionMethod ,前者定义了组件的外观,后者定义了组件的行为。

节包含组件的实现。它使用 #{cc.attrs.ATTRIBUTE_NAME } 表达式来访问组件的界面内定义的属性。(cc 是 JSF 2 表达式语言中的保留关键字,代表的是复合组件。)

请注意,清单 13 中的 icon 组件用 styleClass 属性为其图像指定了一个 CSS 类。该 CSS 类的名字被硬编码为 icon , 所以您就能够指定一个具有该名称的 CSS 类,JSF 将为应用程序中的所有图标使用该类。但是如果您想要覆盖该 CSS 类名,又该如何呢?在这种情况下,我可以为该 CSS 添加另一个属性并提供一个默认,可供 JSF 在未指定属性的时候使用。清单 14 给出了该属性:


清单 14. 重构后的 icon 组件
				








...

name="styleClass" default="icon" required="false"
/>

...







...

#{cc.attrs.styleClass}
"/>

...






清单 14 中,我已经向这个图标组件的界面添加了一个属性,名为 styleClass ,并已经在此组件的实现中引用了该属性。有了这种更改,现在就可以为此图标的图像指定一个可选的 CSS 类,如下所示:

styleClass

="customIconClass"/>


如果不能指定 styleClass 属性,JSF 将使用默认值 icon

login 组件:一个完全可配置的组件

有了 JSF 2,就可以实现完全可配置的复合组件。例如,places 应用程序就包含了一个 login 组件,如图 7 所示:


图 7. places 应用程序的 login 组件
places 应用程序的 login 组件

清单 15 显示了这个 places 应用程序是如何使用 login 组件的:


清单 15. 使用 login 组件
				






component/util
">



<util:login
loginPrompt="#{msgs.loginPrompt}"

namePrompt="#{msgs.namePrompt}"

passwordPrompt="#{msgs.passwordPrompt}"

loginAction="#{user.login}"

loginButtonText="#{msgs.loginButtonText}"

managedBean="#{user}">



<f:actionListener for="loginButton"


type="com.clarity.LoginActionListener"/>





...




清单 15 不仅参数化 login 组件的属性,比如名字和密码提示,它还将一个动作侦听器附加到了此组件的 Log In 按钮。该按钮由 login 组件的界面公开,如清单 16 所示:


清单 16. login 组件
				





































id="form"
prependId="false">





#{cc.attrs.loginPrompt}







#{cc.attrs.namePrompt}





#{cc.attrs.passwordPrompt}











id="loginButton"


value="#{cc.attrs.loginButtonText}"

action="#{cc.attrs.loginAction}"/>


















在 login 组件的界面,我已经在 loginButton 名称下公开了 Log In 按钮。该名称所针对的是位于 form 表单内的 Log In 按钮,因此 targets 属性的值为:form:loginButton

清单 16 内的 Log In 按钮相关联的动作侦听器如清单 17 所示:


清单 17. Log In 按钮的动作侦听器
				


package com.clarity;



import javax.faces.event.AbortProcessingException;

import javax.faces.event.ActionEvent;

import javax.faces.event.ActionListener;



public class LoginActionListener implements ActionListener {

public void processAction(ActionEvent e)

throws AbortProcessingException {

System.out.println("logging in ...........");

}

}


清单 17 内的动作侦听器完全是为了展示的目的 — 当用户登录时,我只简单地将一条消息写出到 servlet 容器日志文件。但是我希望您能体会到这样一个概念:有了 JSF 2,您可以实现完全可配置的组件,并且还可以向这些组件附加功能,所有这些均不需要任何 Java 代码或 XML 配置。这才真正称得上是功能强大。

place 组件:嵌套复合组件

JSF 2 让您能够在无需任何 Java 代码或配置的情况下实现完全可配置的组件。除此之外,您还可以嵌套复合组件,这样一来,您就可以将复杂的组件拆分成更小的、更易于管理的块。比如,图 8 所示的 place 组件,它能显示针对给定地址的地图和天气信息。


图 8. places 应用程序的 place 组件
place 组件

清单 18 给出了 places 应用程序是如何使用 place 组件的:


清单 18. 使用 place 组件
				


places
="http://java.sun.com/jsf/composite/components/places
">

















place 组件的代码如清单 19 所示:


清单 19. place 组件
				






places
="http://java.sun.com/jsf/composite/components/places
">





































清单 19 中,place 组件使用了两个嵌套组件: 。清单 20 给出了 map 组件:


清单 20. map 组件
				





























#{cc.attrs.title}
"

style="color: blue"/>













parent.attrs
.location.streetAddress}, "/>





parent.attrs
.location.city}" />

parent.attrs
.location.state}"/>












#{msgs.zoomPrompt}





parent.attrs
.location.zoomIndex}"

valueChangeListener="#{cc.parent.attrs
.location.zoomChanged}"

>



parent.attrs
.location.zoomLevelItems}"/>









parent.attrs
.location.mapUrl}"

style="border: thin solid gray"/>












复合组件重构

清单 20 map 组件的标记 — 对我而言有些过长。它初看上去多少有些难于理解,而且其复杂性也很可能给今后带来问题。

您可以很轻松地对 清单 20 进行重构,将其分成多个更容易管理的文件,正如我之前在清单 8910 中重构 places 视图的左菜单时所做的那样。在本例中,我将重构的任务留给您作为练习。

请注意 清单 20 中表达式 #{cc.parent.attrs.location.ATTRIBUTE_NAME } 的使用。您可以使用一个复合组件的 parent 属性来访问父组件的属性,这一点极大地方便了组件的嵌套。

但是,您无需严格依赖于嵌套组件中的父属性,正如我在 清单 19 中对 place 组件所做的那样,您也可以将属性(比如地图的标题)从父组件传递给其内嵌套的组件,与向其他任何组件(不管嵌套与否)传递属性无异。

清单 21 显示了这个 weather 组件:


清单 21. weather 组件
				


































style="color: blue"/>







parent.attrs
.location.weather}"

escape="false"/>












weather 组件与 map 组件一样,使用了父组件属性(来自天气 Web 服务的天气 HTML )和一个特定于组件的属性(标题)。(参见 第 1 部分 来了解此应用程序是如何获得某个地区的地图和天气信息的。)

因此,在想要实现嵌套组件时,您就有了选择。您可以让嵌套的组件依赖于其父组件的属性,也可以要求父组件将属性显式地传递给其内嵌套的组件。比如,清单 19 中的 place 组件显式地将标题属性传递给了其内所嵌套的组件,但所嵌套的组件依赖于这个父组件的属性,比如地图 URL 和天气 HTML。

是选择实现组件-显式属性,还是选择依赖于父属性,这是耦合和方便性之间的权衡问题。在本例中,mapweather 组件紧密耦合到它们的父组件(place 组件),因为它们依赖于父组件的属性。我本可以通过将 mapweather 组件的属性指定为组件-显式属性来去掉 mapweather 组件与 place 组件间的耦合。但是如果那样做的话,我就会牺牲一些方便性,因为 place 组件需要将所有属性显式地传递给 mapweather 组件。





回页首


结束语

在本文中,我向您展示了如何使用 JSF 2 的模板和复合组件特性来实现易于维护和扩展的 UI。在本系列的最后一篇文章,我将探讨如何在复合组件中使用 JavaScript、如何使用 JSF 2 的新事件模型以及如何利用 JSF 2 对 Ajax 的内置支持。






回页首


下载

描述 名字 大小 下载方法
示例源代码 jsf2fu2.zip 7.4MB HTTP
关于下载方法的信息


参考资料

学习

获得产品和技术
  • JSF :下载 JSF 2.0。

讨论


关于作者

David Geary

David Geary 是一名作家、演讲家和顾问,也是 Clarity Training, Inc. 的总裁,他指导开发人员使用 JSF 和 Google Web Toolkit (GWT) 实现 Web 应用程序。他是 JSTL 1.0 和 JSF 1.0/2.0 专家组的成员,与人合作编写了 Sun 的 Web Developer 认证考试的内容,并且为多个开源项目作出贡献,包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是有关 Java 的畅销书籍,而 Core JSF (与 Cay Horstman 合著)是有关 JSF 的畅销书。David 经常在各大会议和用户组发表演讲。他从 2003 年开始就一直是 NFJS tour 的固定演讲人,并且在 Java University 教授课程,两次当选为 JavaOne 之星。


More......