|
级别: 中级 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 原则 在我作为软件开发人员从事的第一项工作中,我的任务是为基于 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
与很多 Web 应用程序一样,这个 places 应用程序包含多个具有相同布局的视图。JSF 模板功能让您能够在一个模板内封装该布局 — 及其他共享工件,比如 JavaScript 和 Cascading Style Sheets(CSS)。清单 1 是 图 1 中所示的这三个视图的模板: 清单 1. places 模板:/templates/masterLayout.xhtml
清单 1 中的模板为此应用程序的所有视图提供了如下的基础设施:
正如 清单 1
所示,模板通过 如为 清单 2. login 视图
这个 login 视图为窗口的标题、头和右菜单使用了模板的默认内容。它只定义了特定于此 login 视图的功能:内容部分和左菜单。 通过为窗口标题、头或右菜单提供 清单 3. source-viewer 视图
source-viewer 视图定义了内容部分以及右菜单的内容。它还覆盖了由 清单 1 中的模板定义的针对左菜单的默认内容。 清单 4 显示了 places 视图(图 1 底部的图片): 清单 4. places 视图
请注意清单 2 、3 和 4 之间的相似性。所有这三个视图均指定模板并定义内容。另外,也请注意创建新视图十分容易,因为大多数视图的基础设施都封装在模板及所包含的文件内。 使用 JSF 模板功能的另一个有趣之处是类似清单 2 、3 和 4 中的这些视图并不会随时间有太多变化,所以大部分视图代码基本不需要维护。 与使用模板的视图类似,模板本身也更改甚少。由于大量常见功能都封装在几乎不用维护的代码中,这样一来,您就可以将精力集中于视图的实际内容 — 比如,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. login 页面的左菜单 清单 5 显示了定义该菜单内容的文件: 清单 5. login 视图左菜单的实现
清单 5 内的标记很简单,这就使文件更易于阅读、理解、维护和扩展。如果相同的代码埋藏在一个很长的、包含实现 login 视图所需的全部内容的 XHTML 页面内,那么它更改起来将会很繁琐。 图 3 显示了 places 视图的左菜单: 图 3. places 视图的左菜单 places 视图的左菜单的实现如清单 6 所示: 清单 6. places 视图的左菜单的实现
清单 6
实现了一个表单,并且此表单使用了一个图标组件。(我随后会在
图标组件
一节对该图标组件进行详细讨论。目前,只需知道页面作者可以用一个图标关联图像和方法。)这个 logout 图标的图像显示在 图 3
的底部,而此 logout 图标的方法 —
清单 7. Places.logout()
方法
对我而言,清单 6 — places 视图的左菜单的实现 — 已经十分接近 30 行的代码长度限制。此清单有点难于读懂,并且该代码片段内的表单和图标可被重构成各自的文件。清单 8 显示了 清单 6 的重构版,其中,表单和图标被封装进各自的 XHTML 文件: 清单 8. 重构 places 视图的左菜单
清单 9 显示了 addressForm.xhtml: 清单 9. addressForm.xhtml
清单 10 显示了 logoutIcon.xhtml: 清单 10. logoutIcon.xhtml
在从多个小文件组装视图时,就可享受到 Smalltalk 的 Composed Method 模式的益处。您还可以组织这些文件以便更易于对更改做出反应。例如,图 4 显示了构成 places 应用程序内的这三个视图的文件: 图 4. 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,您就能够轻松实现定制组件:
在本文的剩余部分,我将向您介绍如何为 places 应用程序实现三个定制组件:一个图标、一个 login 面板和一个显示了地址地图和天气信息的面板。但是首先,让我先来概括介绍一下 JSF 2 复合组件。 实现定制组件 JSF 2 综合了 Facelets 模板 、资源处理(在 第 1 部分 中讨论过)和一个简单的命名约定来实现复合组件 。复合组件,正如其名字所示,让您能够从现有组件组装一个新组件。 一般情况下,是在 resources 目录下的 XHTML 内实现复合组件,并将它们完全通过约定链接到一个名称空间和标记。图 5 展示了我是如何为 places 应用程序组织这些复合组件的: 图 5. places 应用程序的组件 要使用复合组件,需要声明一个名称空间并使用标记。此名称空间通常为
而要使用
最后,若要使用 place 组件,则可按如下所示的这样做:
places 应用程序使用了图 6 所示的这两个图标: 图 6. places 应用程序的图标
每个图标都是一个链接。当用户单击 图 6 左侧的图标时,JSF 就会显示当前视图的标记,而激活右侧图标则会使用户登出此应用程序。 可以为链接指定一个 CSS 类名和图像,并且还可以向链接附加方法。当用户单击一个被关联的链接时,JSF 就会调用那些方法。 清单 11 给出了 清单 11. 使用 icon
组件显示标记
清单 12 给出了如何使用 清单 12. 使用 icon
组件执行登出
清单 13 给出了 清单 13. icon
组件
与其他复合组件类似,清单 13
中的
请注意,清单 13
中的 清单 14. 重构后的 icon
组件
在 清单 14
中,我已经向这个图标组件的界面添加了一个属性,名为
如果不能指定
有了 JSF 2,就可以实现完全可配置的复合组件。例如,places 应用程序就包含了一个 图 7. places 应用程序的 login
组件
清单 15 显示了这个 places 应用程序是如何使用 清单 15. 使用 login
组件
清单 15
不仅参数化 清单 16. login
组件
在 login 组件的界面,我已经在 与 清单 16 内的 Log In 按钮相关联的动作侦听器如清单 17 所示: 清单 17. Log In 按钮的动作侦听器
清单 17 内的动作侦听器完全是为了展示的目的 — 当用户登录时,我只简单地将一条消息写出到 servlet 容器日志文件。但是我希望您能体会到这样一个概念:有了 JSF 2,您可以实现完全可配置的组件,并且还可以向这些组件附加功能,所有这些均不需要任何 Java 代码或 XML 配置。这才真正称得上是功能强大。
JSF 2 让您能够在无需任何 Java 代码或配置的情况下实现完全可配置的组件。除此之外,您还可以嵌套复合组件,这样一来,您就可以将复杂的组件拆分成更小的、更易于管理的块。比如,图 8 所示的 图 8. places 应用程序的 place
组件
清单 18 给出了 places 应用程序是如何使用 清单 18. 使用 place
组件
清单 19. place
组件
在 清单 19
中, 清单 20. map
组件
请注意 清单 20
中表达式 但是,您无需严格依赖于嵌套组件中的父属性,正如我在 清单 19 中对 place 组件所做的那样,您也可以将属性(比如地图的标题)从父组件传递给其内嵌套的组件,与向其他任何组件(不管嵌套与否)传递属性无异。 清单 21 显示了这个 清单 21. weather
组件
因此,在想要实现嵌套组件时,您就有了选择。您可以让嵌套的组件依赖于其父组件的属性,也可以要求父组件将属性显式地传递给其内嵌套的组件。比如,清单 19
中的 是选择实现组件-显式属性,还是选择依赖于父属性,这是耦合和方便性之间的权衡问题。在本例中,
结束语 在本文中,我向您展示了如何使用 JSF 2 的模板和复合组件特性来实现易于维护和扩展的 UI。在本系列的最后一篇文章,我将探讨如何在复合组件中使用 JavaScript、如何使用 JSF 2 的新事件模型以及如何利用 JSF 2 对 Ajax 的内置支持。
下载
参考资料 学习
获得产品和技术
讨论
关于作者
|