精通 Grails: 用 Groovy 服务器页面(GSP)改变视图 |
级别: 初级 2008 年 4 月 01 日 Groovy 服务器页面(Groovy Server Pages,GSP)将 Web 置于 Grails Web 框架之内。在精通 Grails 系列的第三期中,Scott Davis 介绍了如何使用 GSP 工作。您将了解到可以非常松地使用 Grails TagLibs、将 GSP 的部分片断组合在一起以及为自动生成(搭建)的视图自定义默认模板。 本系列的前两篇文章介绍了 Grails Web 框架的基本构建块。我曾反复强调过 —Grails 基于模型-视图-控制器(Model-View-Controller,MVC)架构模式(请参阅 参考资料),Grails 利用约定优于配置 将框架的各个部分组合在一起。Grails 用命名直观的文件和目录代替了更容易出错的在外部配置文件中手工对这些链接进行归类的老方法。例如,在 第一篇文章 可以看到控制器拥有 在本月的文章中,我将通过讨论 Grails 视图进一步介绍 MVC。视图(正如您所料)存储在 grails-app/views 目录内。但是视图远不止直观的目录名称这么简单。本文将讨论 Groovy 服务器页面(GSP)并介绍许多替代的视图选项。在本文中将学习标准的 Grails 标记库(TagLibs),并了解到创建自定义 TagLib 有多么容易。还会看到如何将 GSP 的常用片断提取出来放在自己的片段模板(partial template)内,从而遵循 DRY(Don't Repeate Yourself,不要重复自己)(请参阅 参考资料)原则。最后,将学习如何为搭建的视图调整默认模板,从而在方便地自动创建视图和跳出 Grail 应用程序默认外观之间进行平衡。 Grails 使用 GSP 作为表示层。Groovy 服务器页面中的 Groovy 不仅代表底层技术,还代表可以快速编写一两个 scriptlet 的语言。从这方面来说,GSP 非常类似于 Java™ 服务器页面(JSP)技术,JSP 允许在 Web 页面上混合使用一些 Java 代码,也和 RHTML(Ruby on Rails 的核心视图技术)非常相像,RHTML 允许在 HTML 标记之间插入一些 Ruby 代码。 当然,Java 社区长期以来都不欣赏小脚本。scriptlet 会导致最低形式的技术重用 —复制与粘贴 — 以及其他一些在技术方面为人所不齿的恶行(因为你能 和因为你应该 之间有巨大区别)。GSP 中的 G 对优秀、正直的 Java 人员来说只应该表示一种实现语言而不是其他。Groovy TagLibs 和片段模板提供了在 Web 页面之间共享代码和行为的一种更成熟的方式。 GSP 是 Grails 以页面为中心的 MVC 观点的基础。页面是基本衡量单位。列表页面提供了到 Show 页面的链接。Show 页面支持单击到编辑页面,诸如此类。不论是熟练的 Struts 开发人员还是最近的 Rails 爱好者,都熟悉这种 Web 生命周期。 之所以提到这点,是因为近几年出现了大量不以页面为中心的视图技术(请参阅 参考资料)。面向组件的 Web 框架(例如 JavaServer Faces (JSF) 和 Tapestry 越来越受到青睐。Ajax 革命派生出大量基于 JavaScript 的解决方案,例如 Dojo 和 Yahoo! UI (YUI) 库。富 Internet 应用程序(RIA)平台,例如 Adobe Flash 和 Google Web Toolkit (GWT) 承诺能够实现方便的 Web 部署,并提供更加丰富、与桌面类似的用户体验。幸运的是,Grails 能够轻松地处理所有这些视图技术。 MVC 关注点隔离的整体要点在于:它能够使您轻松地用自己喜欢的任何视图作为 Web 应用程序的外观。Grails 流行的插件基础设施意味着许多 GSP 替代物不过是 虽然 Grails 没有内置 JSF 的自动挂勾(hook),但是仍然可以结合使用这两种技术。Grails 应用程序是标准的 Java EE 应用程序,因此可以将相应的 JAR 放在 lib 目录内,将需要的设置放在 WEB-INF/web.xml 配置文件内,并像平常一样编写应用程序。Grails 应用程序部署在标准的 servlet 容器内,所以 Grails 对 JSP 的支持同对 GSP 的支持一样好。Grails 有针对 Echo2 和 Wicket 的插件(两者都是面向组件的 Web 框架),所以在使用 JSF 或 Tapestry 插件方面没有任何障碍。 类似地,向 Grails 添加 Ajax 框架(例如 Dojo 和 YUI)的步骤也没有什么特别之处:只要将它们的 JavaScript 库复制到 web-app/js 目录即可。Prototype 和 Scriptaculous 是 Grails 的默认安装。RichUI 插件则从各种 Ajax 库选择 UI 部件。 如果查看插件列表,那么就会看到对 RIA 客户机的支持 —— 例如 Flex、OpenLazlo、GWT 和 ZK。显然,Grails 应用程序并不缺少备选的视图解决方案。但是在这里我们还是采用 Grail 直接支持的视图技术 — GSP。 可以使用多种方法查找 GSP 页面。文件扩展名 .gsp 就是一种很明显的方法,就好像很多以 但是要在目前的 Trip Planner 应用程序中查找 GSP 的话,则会比较费力(这个系列的前两篇文章开始构建 Trip Planner 程序。如果没有跟上进度,那么现在是赶上来的好时机)。现在视图正在使用动态搭建(scaffold),所以 trip-planner/grails-app/views 目录还是空的。请在文本编辑器打开如清单 1 所示的 grails-app/controller/TripController.groovy,查看用来启用动态搭建的命令: 清单 1. TripController 类
请在 trip-planner 根目录下输入 在这里起作用的是 “约定优于配置”。当访问 http://localhost:9090/trip-planner/trip/list 时,就是要求 清单 2. 完全填充的 TripController 类
这个简单的闭包从数据库中检索到 10 条 下一节研究许多流行的 Grails 标记,包括用来在 Web 页面上显示每条
清单 3.list.gsp 视图
另一个常用 Grails 标记是 清单 4. link 标记 vs. createLinkTo 标记
注意,在清单 4 中,可以交替使用两种不同的形式调用 Grails 标记 — 一种是在尖括号内包含标记,一种是仿效方法调用在大括号内包含标记。当在另一个标记的属性中调用方法时,大括号表示法(正式名称为表达式语言或 EL 语法)更合适。 在 list.gsp 下面的几行,能够看到另一个流行的 Grails 标记: 清单 5. <g:if> 标记
在浏览生成的视图时,还会看到其他许多 Grails 标记。 虽然标准 Grails 标记很有帮助,但是最终会遇到需要自定义标记的情况。许多资深 Java 开发人员(包括我自己)公开表示,“自定义 TagLib 是合适的架构性解决方案。”,然后却偷偷地编写 scriptlet,以为别人看不到。编写自定义 JSP TagLib 需要的工作太多,所以难以抵抗 scriptlet 的诱惑。scriptlet 并不是正确的方法,但不幸的是,它是一种容易实现的方法。 Scriptlet 破坏了 HTML 基于标记的范式,将原始代码直接引入视图。错误的并不是代码本身,而是它们缺少封装和重用的潜力。重用 Scriptlet 的惟一方式就是 “复制-粘贴”。这导致 bug、代码膨胀,并严重违背了 DRY 原则。更不用说 Scriptlet 在可测试性方面的匮乏了。 这就是说,我必须坦白,随着开发期限越来越紧迫,我写的 JSP scriptlet 的比例也相当大。JPS 标准标记库(JSP Standard Tag Library,JSTL)在这方面帮助了我很多,但是编写我自己的自定义 JSP 标记则完全是另一回事。在我用 Java 代码编写自定义 JSP 标记、编译标记,并将大量时间浪费在将标记库描述符(Tag Library Descriptor,TLD)设置为正确的格式和位置时,我已经完全忘记了当初编写这个标记的理由是什么。编写测试来验证我的新 JSP 标记是否正确也同样麻烦 — 只能说我的出发点是好的。 对比之下,用 Grails 编写自定义 TagLibs 简直就是举手之劳。Grails 框架使得做正确的事(包括编写测试)变得很容易。例如,我经常需要在 Web 页面底部加上标准的版权声明。版权声明应该是这样的: © 2002 - 2008, FakeCo Inc. All Rights Reserved.。问题在于,我希望第二个年份总是当前的年份。清单 6 显示了用 scriptlet 如何完成这个任务: 清单 6. 用 scriptlet 完成的版权声明
既然知道了如何处理当前年份,那么下面就要创建一个执行同样任务的自定义标记。请输入 清单 7.简单的自定义 Grails 标记
清单 7 创建一个 清单 8.使用自定义标记的版权声明
您可能以为这就完成了。非常抱歉,这只完成了一半。 即使现在看起来一切正常,还是应该编写一个测试,确保这个标记日后不会出错。Working Effectively with Legacy Code 的作者 Michael Feathers 说过,任何没有测试的代码都是遗留代码。为了防止 Feathers 先生大发雷霆,请将清单 9 的代码添加到 DateTagLibTests.groovy: 清单 9.自定义标记的测试
清单 10.在 Grails 中通过测试
如果
编写并测试过简单的自定义标记之后,现在要构建一个略微复杂一些的标记。 更复杂的标记中可以处理属性和标记体。例如,现在的版权标记还需要许多复制/粘贴工作才能满足需求。我想像下面这样将当前的行为放在真正可重用的标记内: 清单 11.处理属性和标记体的 Grails 标记
请注意: 您可能注意到,自定义 TagLibs 使用与标准 Grails TagLibs 相同的名称空间 自定义标记是重用简短代码的好方法,从而避免成为只能复制/粘贴的 scriptlet。但是对于更大块的 GSP 标记来说,可以使用片断模板。 片断模板在 Grails 文档中的官方称谓是模板。惟一的问题是模板 这个词用得太多了,在 Grails 中有许多不同的意义。下一节就会看到,将安装改变搭建视图的默认模板。对这些模板的修改也包括本节要讨论的片断模板。为了减少混淆,我从 Rails 社区借用了一个术语,将要表达的内容称为片断模板,或者就称为片断。 片断模板是一大块能够在多个 Web 页面之间共享的 GSP 代码。例如,假设我要在所有页面底部使用一个标准的页脚。为了实现这一目的,我要创建一个名为 _footer.gsp 的代码片断。前面的下划线是对框架的提示(对开发人员也是个明显的提示),告诉框架这不是个完整的格式良好的 GSP。如果我在 grails-app/views/trip 目录中创建这个文件,那么只有 清单 12. Grails 片断模板
可以看到,片断模板支持用 HTML/GSP 语法进行表达。对比之下,自定义 TagLib 是用 Groovy 编写的。简要来说,TagLibs 一般情况下用来封闭小行为更合适,而片断模板更适于重用布局元素。 为了让这个示例能正常工作,还需要将 “Powered by Grails” 按钮下载到 grails-app/web-app/images 目录(请参阅 参考资料)。在下载页面上会看到其他许多附属内容,从高分辨率的 logo 到 16x16 大小的 favicons(浏览网站时在浏览器地址栏前显示的图标)。 清单 13 显示了如何在 list.gsp 页面底部包含新建的页脚: 清单 13.呈现片断模板
请注意,在呈现模板时,要去掉下划线。如果在 trip 目录下保存 _footer.gsp,那么前面的斜杠也要省略。可以这样认为:grails-app/views 目录是视图层次结构的根。 有了一些良好的、可测试的、可重用的组件之后,可以将它们做为默认搭建的一部分。这部分内容是在将 要定制默认搭建,请输入 artifacts 目录容纳各种 Groovy 类的模板: war 目录包含所有 Java EE 开发人员都熟悉的 web.xml 文件。如果需要添加自己的参数、过滤器或 servlet,请在这里进行操作(JSF 爱好者们:注意到了吗?)在输入 scaffolding 目录包含动态生成的视图的原始内容。请打开 list.gsp 并将 调整了列表视图之后,现在需要验证修改是否生效。对默认模板的修改是少数需要重新启动服务器的操作之一。Grails 重新启动之后,请用浏览器访问 http://localhost:9090/trip-planner/airline/list。如果正在使用 本期文章总结了 精通 Grails 的另一篇文章。现在您对 GSP 以及 Grails 可以使用的其他视图技术应该有了进一步的了解,并更好地理解了在生成的众多页面中使用的默认标记。下次您再编写 scriptlet 时,肯定会觉得有点 “不舒服”,因为通过编写自定义 Taglib 可以更轻松地完成正确的事情。您看到了如何创建片断模板,还看到了将它们添加到默认搭建视图有多么容易。 下个月的 Grails Web 框架之旅的重点是 Ajax。不用重新加载整个页面就能发送 “微型” HTTP 请求,这一能力是 Google Maps、Flickr 以及其他许多流行 Web 站点背后的诀窍。下个月您将在 Grails 中体会到相同的魔力。具体来讲,将创建一个多对多关系,并通过 Ajax 使用户体验变得自然而有趣。 现在要说再见了,希望您喜欢 “精通 Grails”系列。 学习
|