跨越边界: 动态类型语言中的 Web 开发策略

超越 JSP

过去十年的大部分时间里,Java™ 社区一直在使用 JavaServer Pages (JSP) 技术,但现在已开始显现出衰退的迹象。目前,长期以来的惯例阻止了 Java 程序员在 Web 页面中使用 Java 代码,并且即便扩展简单的组件也很繁琐。超越 JSP 编程的 Java Web 开发框架已经出现,但却缺少动态语言功能。本文将向您展示 Ruby 的 Web 页面开发策略并会提及有关 Seaside 的基本方法。

Bruce Tate (bruce.tate@j2life.com), 总裁, RapidRed

Bruce TateBruce Tate 居住在德克萨斯州的首府奥斯汀,他是一位父亲,同时也是山地车手和皮艇手。他是三本 Java 畅销书的作者,包括荣获 Jolt 大奖的 Better, Faster, Lighter Java。他最近又出版了 Beyond Java 一书。他在 IBM 工作了 13 年,现在是 RapidRed 顾问公司 的创始人,在这里他潜心研究基于 Java 技术和 Ruby on Rails 的轻量级开发策略和架构。



2006 年 7 月 28 日

在 Java Web 开发的早些时候,Sun 和 Microsoft 曾经争夺过有关构建动态 Web 页面的实际标准的控制权。Sun 公司引入了 Servlet API。通过 servlet,您可以用 Java 语言的所有功能快速生成一个 Web 页面。具备编译 servlet 能力的免费 servlet 容器实现 (Apache Tomcat) 的存在使得 servlet 变得非常流行。与之相对的,Microsoft 公司引入了称作 Active Server Pages (ASP) 的 API。该技术很容易:您可以快速掌握并创建更多的高级 Web 页面,其中包括一些具有数据库支持或其他动态内容的页面。

关于本系列

跨越边界 系列中,作者 Bruce Tate 提出了这样一个观点:如今的 Java 程序员可以通过学习其他方法和语言得到很好的其他思路。自从 Java 技术明显成为所有开发项目的最佳选择以来,编程前景已经改变。其他框架正影响着构建 Java 框架的方式,从其他语言中学到的概念也影响着 Java 编程。您编写的 Python(或 Ruby、或 Smalltalk 等) 代码可以改变您处理 Java 编码的方式。

本系列向您介绍与 Java 开发截然不同但可直接应用于 Java 开发的编程概念和技术。在一些情况下,需要集成该技术以对其进行利用。而在另一些情况下,则可以直接应用这些概念。单独的开发工具并不重要,重要的是这种思想,即能够影响 Java 社区中开发人员、框架,甚至基本方法的其他语言和框架。

首先来说说 JSP 技术。JSP 被设计来直接与 ASP 竞争(顾名思义)。使用 JSP 技术,您可以使用标记构建一个 Web 页面并将 Java 代码直接放入页面中。JSP 容器将 JSP 文件编译成 servlet 形式。然后 servlet 引擎像执行其他 servlet 一样执行该页面。JSP 和 ASP 一样,都实现了一个基于模板的方式。模板可以像您想要在用户浏览器中所呈现的那样,帮助您构建一个简单的 Web 页面。当模板引擎处理简单的占位符元素时,您可以用值、组件或作为页面一部分的结构来替换这些元素。尽管 Java 平台拥有众多具有通用目的的模板引擎,但 JSP 已经占领了绝对的市场份额,部分原因在于大量 ASP 开发人员转换来使用 JSP。

比起 Java 语言或 ASP,一些其他的语言在处理 Web 开发方面表现要强得多。了解一下动态语言中相互竞争的方法,会让您更加清楚在 Java 平台上能够使用哪些方法。在本文中,我讨论了 Ruby 中代码生成是如何工作的,并且深入讲解了 Seaside 中一种更加根本的基于组件的方法。

Ruby 模板

Ruby 模板依赖此语言的简单功能来为 Web 开发提供一种简单却行之有效的方法。通过掌握一些层次化的概念即可快速理解 Ruby 模板,这些概念每一层要比其上一层更加强大。

Ruby 模板最基础的部分是 String。Ruby 字符串是一级对象。当您在 Ruby 中创建一个字符串时,您既能用单引号,也能用双引号把它括起来。清单 1 展示了一些例子,即您可在 Ruby 解释器中键入来创建字符串的三条不同命令:

清单 1. 创建一个 Ruby 字符串
irb(main):001:0> "This is a string.".class
=> String
irb(main):002:0> 'This is also a string.'.class
=> String
irb(main):003:0> String.new('This is yet another string.').class
=> String

Ruby 不处理任何用单引号括起来的字符串信息。如果用双引号括起来,Ruby 会在运行代码的时候进行替换。清单 2 是一个使用换行符的示例。使用单引号时,Ruby 保持换行符不变。使用双引号时,则会解释换行符:

清单 2. 单引号与双引号的比较
irb(main):004:0> puts 'Use \n to specify a new line in Ruby.'
Use \n to specify a new line in Ruby.
=> nil
irb(main):005:0> puts "A \\n causes a line break \n like this."
A \n will cause a line break 
 like this.
=> nil

在第二个 puts 语句中(puts 代表 put string),Ruby 处理了两次替换。单个 \ 后跟一个字符表示不在键盘上的特殊字符,如 \n 代表换行。正如反斜号本身有特殊的意义一样,在一个字符前的 \ 也有着特殊的意义,即转义该字符,这同在 Java 语言中一致。清单 2 处理了 \\\n 两个替换。

#{any_expression} 是一个更有趣的替换命令。如果您使用双引号将含有此命令的 String 括起来,Ruby 将返回此字符串,用 any_expression 的值取代 #{any_expression}。清单 3 给出了一个例子:

清单 3. 简单变量替换
irb(main):006:0> name = "Elvis"
=> "Elvis"
irb(main):007:0> puts "Your name is #{name}"
Your name is Elvis
=> nil

您已经做好了创建一个基本的模板所需的绝大部分准备。只差一步即可完成简单的模板处理。您可以通过在具有单引号的字符串中使用 #{} 替换来推迟赋值。稍后,通过给模板加双引号并调用 eval() 方法,您可以随时将模板与变量绑定,如清单 4 所示:

清单 4.推迟赋值
irb(main):008:0> template = 'Your name is #{name}'
=> "Your name is \#{name}"
irb(main):009:0> name = gets
Elvis
=> "Elvis\n"
irb(main):011:0> puts eval('"' + template + '"')
Your name is Elvis
=> nil

注意,template 在初始化 name 前就已经存在了。您可以使用这个简单的模板策略来处理非常简单的 Web 页面,但通常您还需要用到更多知识。包含简单编码结构(如含有动态数据的表循环)的能力形成了大多数动态 Web 页面的主干。

用 eRuby 嵌入代码

在 HTML 页面中嵌入 Ruby 代码的典型方法(包括在 Rails 中使用的方法)是一个被称作 eRuby 的过滤器。现有的几个实现中包括了一个被称作 ERb 的基于 Ruby 的实现,和一个被称作 eruby(参见 参考资料)的速度更快且基于 C 语言的实现。它们每一个都能作为 Web 服务器的插件运行,但在便利地处理本地文件时,您通常会使用 ERb,而当需要更快的速度时,例如对于生产用的 Web 服务器,您会使用 eruby。eRuby 过滤器处理文本文件时,除了下列三种情况以外都会完整地保持原文本:

  • <% 和 %> 之间的文本是 Ruby 代码,eRuby 将如实执行该代码。
  • <%= 和 %> 之间的文本是 Ruby 表达式。eRuby 执行该表达式并用其返回的值替换整个表达式。
  • 一行中以单个 % 开头的内容,将被作为 Ruby 代码执行。

例如在清单 5 中名为 test.rhtml 的文件(按照惯例,.rhtml 是针对 eRuby 标记的、包含有 HTML 的文件的扩展名)。清单 5 使用了 Ruby 代码和表达式。请注意 do 循环体中的文本:

清单 5.一个简单的 rhtml 文件
<% 4.times do |i|%>
  <h1>This code is inside the loop.</h1>
  <p>This line is pass number <%= i %> through the loop.</p>
<% end %>

您可以简单地通过 ERb 运行此代码,如清单 6 所示:

清单 6.用 ERb 处理文件
  > erb test.rhtml

   <h1>This code is inside the loop.</h1>
   <p>This line is pass number 0 through the loop.</p>

   <h1>This code is inside the loop.</h1>
   <p>This line is pass number 1 through the loop.</p>

   <h1>This code is inside the loop.</h1>
   <p>This line is pass number 2 through the loop.</p>

   <h1>This code is inside the loop.</h1>
   <p>This line is pass number 3 through the loop.</p>

调用模板

Ruby 是一系列处理基于 LAMP 的 Web 开发的语言之一。LAMP 代表:

  • Linux
  • Apache
  • MySQL
  • 一种开放源码的 P-语言:Python、Perl 或 PHP。(如果不考虑基础的话,Ruby 是一种名义上的 P-语言。)

在 LAMP 中,通常您可以通过简单的操作系统脚本,使用诸如公共网关接口 (CGI) 之类的框架来提供 Web 应用程序。

eRuby 有效地提升了典型的基于 CGI 编程的水平。正如您所预料的那样,您可以直接将 ERb (或是基于 C 语言的 eruby 的等价物)插入 Apache HTTP 服务器并调用 Web 页面。随后您可以将 Ruby 视为 Java servlet 等价物了。但新的框架常将 ERb 作为一个 Ruby 库来调用。如果想要从 API 中创建一个 Ruby 模板,您应该使用清单 5 的修正版本,如清单 7 所示:

清单 7. 在 Ruby 应用程序中运行 ERb 模板
require 'erb'

template = ERB.new <<-EOF
<% 4.times do |i|%>
  <h1>This code is inside the loop.</h1>
  <p>This line is pass number <%= i %> through the loop.</p>
<% end %>
EOF

puts template.result

通过这种方式执行这些模板,您将 Ruby 编程语言下的所有能力赋予了您的模板。随后您可以在模板中的代码和应用程序其余部分的代码之间实现无缝重构。例如,您可以创建一个帮助器来基于散列映射的内容构建简单的表,如清单 8 所示:

清单 8. 创建帮助器
require 'erb'

def table_helper(map)
  body = map.collect do |item|
    "<tr><td>#{item[0]}</td>
     <td>$#{item[1]}</td></tr>\n" 
  end
  return("<table>\n#{body}</table>")
end  

map = {
       "Peaches" => "1.95", 
       "Apples" => ".95"
      }

template = ERB.new <<-EOF
<p>Here's our price list</p>
<%= table_helper(map) %>

EOF

puts template.result

给定一个散列映射,清单 8 显示了一个用于创建表的帮助器。本例中先声明一个散列映射和一个模板,然后把它们放在一起使用(通常您可以将这个模板放到一个单独的文件中;而为了保持清单的简单,我将其写作一行)。注意,您会很自然地在 Ruby 编程语言的功能的基础上扩展模板系统的功能。本例将 ERb 方式的替换与提供简单字符串的基本 Ruby 字符串替换结合了起来。Ruby on Rails 用以下几种强制的方法将这些替换方式组合在一起:

  • Rails 的 partials 使小的子表单能够更好地被重用和组织。
  • Rails 的 layouts 使包含标题、菜单栏和版权信息在内的公共元素能够在所有页面中重用。
  • Rails 的 helpers,类似于清单 8 中的帮助器,使简化和重用视图结构变得简单。

事实上,Rails 的设计为用户贡献库提供了简单的方式,这些库使用 JavaScript 来呈现诸如 Ajax 元素、树控制或弹出式菜单之类的复杂特性。Rails 社区提供了功能远远强大于 Rails 基本功能的帮助器。


Seaside 策略

Seaside 是一个不使用传统模板的 HTML 呈现策略的例子,是针对使用 Smalltalk 开发 Web 应用程序的框架。如今,Web 开发人员通过一个名为层叠样式表 (CSS)(参见 参考资料)的策略将样式放到了一个更加重要的位置。在 CSS 中,您可以使用模板友好的语言来构建包括呈现位置、颜色、背景以及字体在内的所有的样式信息。Seaside 在样式上非常依赖 CSS,但却从不用模板来呈现基本组件。

在 Seaside 中,每一个组件都能够自我呈现。最基本的情况是,一个组件仅仅显示基本的 HTML 标记和必要的动态内容以呈现此组件。例如,清单 9 定义了 renderContentOn 方法来创建一个简单 Seaside 计数器:

清单 9.一个简单的 Seaside 组件
renderContentOn: t1 
   t1 heading: count.
   t1
      anchorWithAction: [self increase]
      text: '++'.
   t1 space.
   t1
      anchorWithAction: [self decrease]
      text: '--'

在清单 9 中,t1 heading: count. 使用实例变量 count 中的内容呈现了一个标题,该变量包含了一个简单计数器的值。然后,代码呈现了一个在 Seaside 术语中称作的链接,该链接包含了 '++' 文本并调用 increase 方法。最后,代码中针对 decrease 方法呈现了一个相似的链接。图 1 展示了含有一个在清单 9 中呈现的计数器的 Web 页面:

图 1. 一个简单的 Seaside 组件
一个简单的 Seaside 组件

更加复杂的组件能够使其子组件自我呈现。用这种方法,Seaside 能够用最小的努力产生相当复杂的组件。例如,表能够呈现一系列行,而这些行又能够依次呈现一系列单元格。清单 10 是一个复杂组件的例子:

清单 10. 一个更加复杂的 Seaside 组件
renderContentOn: t1 
   counters
      do: [:t2 | t1 render: t2]
      separatedBy: [t1 horizontalRule]

清单 10 中的代码呈现了一组计数器,遍历一个容器集合,为每一个容器调用 render 方法,并用水平线将每个容器分隔。您可以在图 2 中看到结果:

图 2. 一组 Seaside 计数器
一组 Seaside 计数器

Seaside 模型不使用模板,却可以依靠每个个体对象的自我呈现来构建异常强大的具有复杂交互功能的 Web 页面。Seaside 依靠样式表来维持设计人员与编程人员间的良好关系。分层、布局、回溯和样式在 Seaside 中是家常便饭。


超越 JSP

在 Jason Hunter 六年前撰写的一篇出色的文章中,他列举了 JSP 的缺点(参见 参考资料)。我解释为:

  • JSP 比它需要的要难。
  • 循环很困难。
  • 错误消息不友好。
  • 在编程人员和设计人员之间保持平衡太难了。

多年来,Java 开发人员已经提出了改变这些不足的实践和框架。JavaServer 页面标准标记库 (JSTL)(JSP编程的自定义标记系统)试图简化从 JSP 文件中分解出代码以及将代码放入共享库。但那个标记系统远比其需要的复杂。它深具诱惑,却不能直接将 Java 代码插入 Web 页面,因而 Java Web 页面开发人员通过避免所有的代码来抵制这种诱惑,这像避免瘟疫一样。几个建立在 JSP 技术上的框架试图简化此问题。更新的框架则试图改善 Java Web 开发体验。例如,JavaServer Faces (JSF) 试图重新发明 Struts —— 但其最初却使用了 JSP(这违背了一些专家的意见)。

在最近的几年,一些不依赖于 JSP 的创新性框架已经开始出现。通过依靠更简单的模板哲学(一种更接近于纯 HTML 语言的思想),RIFE、Wicket、Spring Web MVC、Tapestry 以及其他一些框架都声称为组件的集成提供了更简单的策略(参见 参考资料)。它们都可以让 Web 开发人员使用本机设计工具而非为 JSP 编程专门定制的工具。这对 Java 语言来说是个健康的方向。然而,其他语言,如 Ruby 在处理 Web 开发上比 Java 技术要好得多。

能将 Java 丰富的企业功能同简易的动态语言结合起来的可行性策略是使用嵌入式虚拟机。依据 JRuby 的领袖 Thomas E. Enebo 的说法,开发人员已经在研究一种 servlet,这种 servlet 允许嵌入 Ruby 代码来生成 Ruby 风格的 Web 页面。在很多方法中,这一种能够让您利用到这两者的优势。

我希望您对其他语言呈现 Web 页面的一些方法有了一些认识。下一次,我将会探讨使用函数语言解决通用问题的不同方法。

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • Beyond Java (Bruce Tate, O'Reilly, 2005年):本文作者所著的书,内容涉及 Java 语言的兴起和兴盛,以及能够在某些领域挑战 Java 平台的技术。
  • The Problems with JSP”(Jason Hunter,Servlets.com,2000年 6 月):一篇历史悠久的文章,讲述有关 JSP 技术的问题。
  • Ruby:一种卓越的用于表示模板的语言。
  • Seaside:此框架使您能够用 Smalltalk 开发复杂的 Web 应用程序。
  • 层叠样式表 (CSS):一项用于为 Web 文档添加诸如字体、颜色及空格等样式元素的机制,该机制由万维网联盟开发。
  • ERb:一个用于处理 CGI 脚本的 Ruby 类,也可以被用在 Ruby 应用程序中以转化 Ruby 模板。
  • eruby:用 C 语言写成的一个 eRuby 实现。
  • From Java To Ruby: Things Your Manager Should Know(Pragmatic Bookshelf,2006年):作者作著的书,讲述了在何时何处将 Java 编程转换成 Ruby on Rails 是有意义的,以及如何做出这种改变。
  • Programming Ruby(Pragmatic Bookshelf,2005年):Ruby 编程方面的畅销书。
  • RIFE, Wicket, Spring Web MVC, Jakarta Tapestry:使用模板策略的 Java Web 开发框架。
  • JRuby:JRuby 是用 100% 纯 Java 语言写成的与 Ruby 1.8.2 版兼容的 Ruby 解释器。
  • Java 技术专区:数百篇有关 Java 编程各方面的文章。

获得产品和技术

  • Ruby:从该项目的 Web 站点获取 Ruby。
  • Ruby on Rails:下载开放源码的 Ruby on Rails Web 框架。
  • Seaside:下载截然不同的 Smalltalk Web 框架。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Web development
ArticleID=150612
ArticleTitle=跨越边界: 动态类型语言中的 Web 开发策略
publish-date=07282006