IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Web development | Java technology  >

不使用 JSP 技术的 Java Web 开发,第一部分

用 Tea 简化 Web 开发

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Craig Walls (javadude@habuma.com), 经理,Web 应用程序开发, Michaels Stores, Inc.

2002 年 10 月 21 日

在开发基于 Java 的网站的视图层时,JavaServer Pages 被广泛地认为是自然而然的(或唯一的)选择。事实上,每个建立在 Java 平台上的站点实际上都用到了 JSP 技术。JSP 技术非常有名,大多数应用程序服务器都支持它,J2EE 计划也认可了它。为什么还要选择其它技术呢?在这篇由两部分组成的文章的第一部分中,我向您介绍了 Tea 模板语言,还向您演示了 Tea 是如何能够成为 JSP 技术的一种可行的替代方案。在第二部分中,您将探索如何将 Tea 集成为模型-视图-控制器(Model-View-Controller (MVC))2 体系结构(尤其是 Struts 应用程序)中的 视图

不宜使用 JSP 技术的情形

尽管 JSP 技术广受欢迎,但它却不是唯一选择。而且,也有一些充分的理由不使用这项技术。在 Servlets.com 上的一篇 文章中,Jason Hunter 指出了 JSP 环境的七个问题(参阅 参考资料)。总结起来,那些问题是:

  1. 将(原始 Java 代码形式的)业务逻辑置于页面上的诱惑很大。
  2. 大多数普通任务的性能需要 java 代码。
  3. 简单的任务很难;因此,非 Java 设计者无法执行这些任务。
  4. JSP 环境中的循环很笨拙 ― 定制标记令人信服地解决了这一问题。
  5. 会出现无用的错误消息。
  6. JSP 编程需要 Java 编译器。
  7. 生成的中间 servlet 浪费了空间。

注:自 Jason 最初写这篇文章以来,JSP 技术规范已经经历了两次修订,上面的一些问题已经得到了部分解决。然而,JSP 技术仍然有其不足。

除了 Jason 列举的那些问题之外,我也想补充 JSP 技术的另一个问题:

  1. 很容易由于部署一个带有错误的 JSP 页面而无意中破坏网站中的某个页面(或多个页面)。

一些模板引擎已经被开发出来以替代 JSP 技术。它们包括 WebMacro、Velocity、Enhydra XMLC 以及本文的主题 ― Tea。





回页首


介绍 Tea

Tea 是一种简单的模板语言,它已由 Walt Disney Internet Group(WDIG)作为开放源码发布。Tea 是 Disney 的许多网站背后的技术选择,这些网站包括 ESPN.com、Disney.com 和 ABCNews.com。

Tea 的目标是提供简单安全的模板引擎以便用最少的错误构建高度动态的网站。这一目标是通过最少的编程构造和更简单、更不易混淆的语言机制来实现的。Tea 留给我们的是类似于 HTML 但动态性一样高的语言。由于 Tea 的简单性,Web 开发团队可以聘请专业性不太强的程序员 ― WDIG 称这样的人员为 技术制作者― 来制作站点的表示层。

使用 Tea 构建的网站有三种主要组件:

  • TeaServlet
  • 一个或多个 Tea 模板
  • 一个或多个 Tea 应用程序

TeaServlet 是 Tea 模板编译器和执行引擎。它对于 Tea 模板就象 Tomcat 的 Jasper 对于 JSP 页面。TeaServlet 编译 Tea 模板(如果必要的话),在收到请求时执行它们。

Tea 模板是 JSP 页面的 Tea 等价物。然而,和 JSP 编程不一样,Tea 模板不经过到 Java Servlet 代码这一中间转换。相反,Tea 编译器直接将 Tea 模板编译成类文件。这是一项重要的差异,它直接解决了 Jason Hunter 的 JSP 问题列表中的第六和第七个问题,间接地解决了第五个问题(随后您将看到)。

Tea 应用程序是一种机制,通过这种机制使得业务逻辑可为 Tea 模板所用。Tea 应用程序通过 Tea 应用程序的上下文实现的 public 方法来暴露业务逻辑。这些方法可以作为可从任何 Tea 模板调用的函数获得。Tea 模板和 Tea 应用程序之间的交互类似于 JSP 环境中 JSP 技术和 JavaBeans 之间的交互。

Tea 和 Tomcat

在为本文编写代码时,我使用 Jakarta Tomcat 4.0.4 作为我的 servlet 容器。这些安装指示信息假定您正在使用 Tomcat 4.0.4 作为您的 servlet 容器,还假定 Web 应用程序名为“teademo”。您的安装可能会有些变化,这取决于您对服务器的选择以及 Web 应用程序名称。





回页首


安装 Tea

可以从网址为 http://opensource.go.com的 WDIG 网站下载 Tea,它可以以 Apache 样式许可证获得。除了其它内容以外,分发版中包含 TeaServlet.jar 文件,它组成了 Tea 引擎。您也可以在 WDIG 的网站上找到一个名为 Kettle 的用于 Tea 的 IDE,不过我在本文中不讨论 Kettle。

下载了 TeaServlet.zip 文件之后,将其解压缩然后把 TeaServlet.jar 文件移到 Tomcat 的 webapps/teademo/WEB-INF/lib目录。

接下来,需要在 webapps/teademo/WEB-INF 目录下创建一个 web.xml 文件。该文件将用您的应用程序注册 TeaServlet,并将某种 URL 模式同 TeaServlet 相关联。该 web.xml 文件如下:


清单 1. web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
  <!--      TEA SERVLET      -->
  <servlet>
    <servlet-name>
      teaservlet
    </servlet-name>
    <servlet-class>
      com.go.teaservlet.TeaServlet
    </servlet-class>
    <init-param>
      <param-name>properties.file</param-name>
      <param-value>
        /jakarta-tomcat-4.0.4/webapps/teademo/WEB-INF/TeaServlet.properties
      </param-value>
    </init-param>
  </servlet>
  <!--      TEA MAPPINGS     -->
  <servlet-mapping>
    <servlet-name>teaservlet</servlet-name>
    <url-pattern>*.tea</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>teaservlet</servlet-name>
    <url-pattern>/dynamic/*</url-pattern>
  </servlet-mapping>
</web-app>

请注意,TeaServlet 获取一个名为 properties.file 的参数,该参数指向 TeaServlet.properties 文件。随后将创建此文件。

还需要注意的是,您将两个 URL 模式映射到了 TeaServlet。任何具有 .tea 扩展名或以 /dynamic/* 开始的 URL 都将被委托给 TeaServlet 以进行处理。映射 .tea 扩展可能是很明显的,但映射 /dynamic/* 可能就不明显。创建这一映射是因为 Tea 模板可能会被作为预编译的类文件而不是未编译的模板分发。没有 .tea 扩展名,则需要另一种方法来访问这些预编译的模板。后面有更多关于预编译模板的内容。

现在需要设置 TeaServlet.properties 文件。TeaServlet.properties 文件是配置您的 Tea 应用程序的地方。它也是您指示 TeaServlet 找到其模板文件的地方,它还是为 TeaServlet 设置日志记录级别的地方。

如同 web.xml 文件所显示的那样,该文件应该位于 web.xml 文件旁的 WEB-INF 目录中。TeaServlet 下载中有一个一般的 TeaServlet.properties 文件,它起到一个好起点的作用。您会注意到,已经有了一个名为 System 的已配置应用程序。该应用程序提供了一些便捷的系统范围函数,这些函数可能在大多数 Tea 模板中都是必需的。后面将添加一个额外的应用程序。

在 TeaServlet.properties 文件中要设置的两个重要特性是 template.path 和 template.class。template.path 特性告诉 TeaServlet 在何处放置未编译的 Tea 模板。虽然可以将 Tea 模板置于任何地方,但我却更愿意将我的模板放置在 Web 应用程序的 WEB-INF 目录下的名为 tea_templates 的目录中。至于 template.class,该特性告诉 TeaServlet 在哪里放置和寻找已编译的模板类文件。我更愿意将我的模板类文件放置在名为 tea_classes 的目录中,该目录同样直接处于 Web 应用程序的 WEB-INF 目录下。

一旦设置了这些文件,请启动 Tomcat。现在,您已经准备好了,可以开始使用 Tea 进行开发。





回页首


Tea 管理

随 Tea 一起还提供了一个管理工具,可以通过您的 Web 浏览器访问该工具。您将使用此工具来部署您的 Tea 模板文件,因此在可以编写任何代码之前,需要熟悉该应用程序。可以通过 http://localhost:8080/teademo/dynamic/system/teaservlet/Admin?admin=true访问此 Tea 管理工具。如果您没有看到 Tea 管理屏幕,那么一定是安装中出了一些问题。请重复上面的安装步骤,然后再试。

该管理工具有五个子工具:

  • Templates
  • Functions
  • Applications
  • Logs
  • Servlet Engine

Templates 是一个您将逐渐熟悉的工具。此工具列出您的 Web 应用程序中的所有模板及其 URL 以及该模板所获取的任何参数。在靠近顶部的地方也有一个 reload 按钮。按这个按钮将导致 TeaServlet 寻找任何新的或更改了的模板并部署这些模板。

和 JSP 技术不一样,简单地将一个 Tea 模板复制到 templates 目录本身并不会部署该模板。直到您按下 reload 按钮,模板才会被部署。而且,如果模板中有任何错误,它们都不会被部署。请再读一次。如果有错误,绝对 没有模板被部署。即使众多模板中的一个里面有一个错误,也不会有模板会被部署。这防止了部署有问题的 Tea 模板,解决了我们的问题 #8。

Templates 工具的另外一个特征是 Recompile all复选框。如果您选择该复选框并单击 Reload,那么会重新编译所有模板而不仅仅是那些已经改变了模板。

Functions 和 Applications 工具分别归档已配置的 Tea 函数和应用程序。Functions 工具按照字母顺序列出 Tea 模板可用的所有函数。Applications 工具按照提供函数的 Tea 应用程序对函数进行分组。

Logs 工具简单地显示 Tea 日志文件。

最后,Servlet Engine 工具显示有关在其中部署 TeaServlet 的 servlet 引擎的信息。这些信息包括 servlet 引擎的名称和版本号、类路径以及 TeaServlet 的特性文件的位置。





回页首


Hello world

开始使用 Tea 语言的最佳方法是通过必不可少的 Hello World 示例来开始。


清单 2. hello.tea
<% template hello(String name) %>
<html>
  <head>
    <title>Hello World Template</title>
  </head>
  <body>
    <% nullFormat("world") %> 
    <h3>Hello <% name %>!</h3>
  </body>
</html>

将 hello.tea 文件放置于 tea_templates 目录中。现在,请将您的浏览器指向 http://localhost:8080/teademo/hello.tea,您将看到所期望的“Hello world!”消息。

让我们来分析这个简单示例,看看发生了什么。在第一行声明了模板名为 hello。这个名称必须与模板文件的基名称相同。您还声明:该模板获取一个 String 类型的名为 name的参数。稍后有更多关于该参数的内容。

nullFormat() 的调用告诉 Tea:在显示空值时,您希望显示单词“world”。如果不调用 nullFormat 函数,在遇到空值时将会显示单词“null”。 nullFormat() 函数也是 System 应用程序所给出的众多函数中的一个,它也是 Tea 的较便捷的特性之一。

最后,输出是 Hello 后跟 name 变量的值。如果 name 为空,那么 world 的空值将被输出。在上面给定的 URL 中,示例并没有指定 name,因此生成的消息是“Hello world!”。

请注意,在显示 name 的值时,没有象在 JSP 编程中所做的那样使用 <%=name%> 。Tea 中假定所有 scriptlet 代码都产生一些可显示的值。如果无值(例如,象调用一个返回 void 的函数那样),那么不会显示任何内容。

现在,请将您的浏览器指向 http://localhost:8080/teademo/hello.tea?name=Craig。这次您将获得“Hello Craig!”的问候语,因为,这次指定了 name 参数。





回页首


更有趣的示例

虽然标准 Hello World 示例易于上手而且突出了 Tea 语言的一些基本特性,但一个更有趣一些的示例却更适合。

在本示例中,我将编写一个 Web 应用程序来管理电话簿。电话簿中的每一项都由名、姓、电话号码和可选的电子邮件地址组成。显示的主要内容是电话簿中所有项的列表。用户也可以选择向列表中添加项。为了使该示例保持简单,我将不实现编辑和删除项的功能。

开发 Tea 应用程序

Tea 应用程序由两部分组成:Tea 应用程序类和 Tea 上下文类。Tea 上下文类的 public 方法作为函数暴露给所有 Tea 模板。Tea 应用程序类不完全是一个用于生成 Tea 上下文类的实例的工厂类。每个 Web 应用程序只有一个 Tea 应用程序实例,而通过 Tea 应用程序类中的 createContext() 方法为每个请求创建了一个新上下文。因此,建议在应用程序类而不是在 Tea 上下文中处理 Tea 应用程序的任何共享资源(例如数据库连接)。

PhoneApplication.java 代表了典型的 Tea 应用程序类。装入应用程序时,调用一次 init() 方法而且只调用一次。卸装 Tea 应用程序时,调用一次 destroy() 方法,而且只调用一次。这个示例无须安装和拆卸,因此这些方法的实现为空。 getContextType() 方法仅仅返回上下文类的类型。最后, createContext() 方法创建应用程序上下文的实例。


清单 3. PhoneApplication.java
import javax.servlet.ServletException;
import com.go.teaservlet.Application;
import com.go.teaservlet.ApplicationConfig;
import com.go.teaservlet.ApplicationRequest;
import com.go.teaservlet.ApplicationResponse;
public class PhoneApplication
  implements Application {
  public void init(ApplicationConfig config)
    throws ServletException {
  }
  public void destroy() {
  }
  public Class getContextType() {
    return PhoneContext.class;
  }
  public Object createContext(
    ApplicationRequest request, ApplicationResponse response) {
    return new PhoneContext();
  }
}

PhoneContext.java 包含暴露给我的模板的方法。暴露的方法是 addPhoneEntry() ,它用于向电话簿中添加项,暴露的方法还有 getPhoneList() ,它用于检索电话项的 Collection。这些方法将任务交给 PhoneListDAO 中同名的方法。


清单 4. PhoneContext.java
import java.util.Collection;
public class PhoneContext {
  protected PhoneContext() {
  }
  public void addPhoneEntry(String firstName, String lastName, 
          String phone, String email) {
    if (email.equals("")) { email = null; }
    PhoneListDAO.addPhoneEntry(new PhoneEntry(firstName, lastName, 
            phone, email));
  }
  public Collection getPhoneList() {
    return PhoneListDAO.getPhoneList();
  }
}

PhoneListDAO 担任对我的数据库的访问点。在现实实现中,我可能选择关系数据库作为电话列表,但为了使该示例保持简单,我选择 ArrayList 作为模拟的数据库。static 块用两个初始项设置该数据库。


清单 5. PhoneListDAO.java
import java.util.ArrayList;
import java.util.Collection;
public class PhoneListDAO {
  static ArrayList phoneListDB;
  
  static {
    phoneListDB = new ArrayList();
    phoneListDB.add(new PhoneEntry("John", "Doe", "555-3333", "jdoe@habuma.com"));
    phoneListDB.add(new PhoneEntry("Joe", "Schmoe", "555-2111", null));
  }
  
  public static void addPhoneEntry(PhoneEntry entry) {
    phoneListDB.add(entry);
  }
  
  public static Collection getPhoneList() {
    return phoneListDB;
  }
}

电话列表中的每一项都由一个 PhoneEntry bean 实例来表示。这是一个简单 JavaBean,它有四个属性,这四个属性组成了电话簿的项。

配置电话列表应用程序

为了使模板可以使用 PhoneApplication 及其上下文的方法,请向应用程序部分添加下面几行来配置 TeaServlet.properties 中的 PhoneApplication 类:


清单 6. 用 TeaServlet 配置 PhoneApplication
"PhoneList" {
      class = PhoneApplication
}

电话列表模板

phonelist.tea 模板显示电话项列表。


清单 7. phonelist.tea
<% template phonelist() %>
<html>
  <head>
    <title>Phone List</title>
  </head> 
  <body>
    <h3>Phone List</h3>
    <hr>
    <a href="phoneform.tea">Add Entry</a>
    <table border="1">
      <tr>
        <td>Name</td><td>Phone Number</td><td>E-mail</td>
      </tr>
      <%
        phoneList = getPhoneList()
        nullFormat("&nbsp;")
        foreach (entry in phoneList) {
          if(entry isa PhoneEntry) {
      %>
        <tr>
          <td><% entry.lastName %>,  
            <% entry.firstName %> </td>
          <td><% entry.phone %></td>
          <td><% entry.email %></td>
        </tr>
      <%
          }
        }
      %>
    </table>
  </body>
</html>

第一行中包含标准模板声明,就如同在 Hello World 示例中所见到的那样。这里,唯一的不同在于该模板不获取任何参数。

在设置页面结构的 HTML 后面是第一块 Tea scriptlet 代码。首先,调用了 getPhoneList() 以检索电话项的 Collection 并将该集合放入 phoneList 变量中。请注意,没有必要声明 phoneList 的类型。Tea 根据函数调用的返回类型确定该类型。接下来,将空格式设置为 HTML 非中断空格。这是为了确保在电子邮件地址为空时能够正确地格式化。

接下来,您将看到 Tea 语言中的一个也是唯一一个循环结构。 foreach 构造遍历数组或集合中的所有元素,将找到的每个元素都放置在一个变量中。因为集合只有有限多个项,所以比起 while 或传统的 for 循环这是一种更安全的循环结构,从而使得不可能无意中引入一个无限循环。在这个示例中,phoneList 集合中的每个元素都被放入 entry变量。

接下来一行中的 if 语句突出了 Tea 语言的一个引人注目的特征。由于 Collection 中每个元素的类型未知,因此只能将 Entry 的类型假定为 Object。但是,为了能够访问 PhoneEntry 数据,entry 需要具有 PhoneEntry 类型。 isa 运算符执行了 Java 的 typeof 和强制类型转换两种运算的结合。如果 entry 是 PhoneEntry 对象,那么就执行 if 块,并且在执行 if 块期间 entry会被自动强制类型转换为 PhoneEntry。这种做法方便地处理了强制类型转换,通过在强制类型转换之前检查类型避免了 ClassCastException。

if 块内,我只是访问 entry 的每个成员并在合适的表格单元格中显示它们。根据 Tea 的简单原则,这些特性是通过其名称而不是其相关的取值方法来获取的。Tea 将对 bean 属性的访问转换为适当的 getXXX()setXXX() 调用,使得 Tea 模板程序员再也不用为取值和赋值方法而烦恼。

phoneform.tea 模板包含一个表单,用户可以通过该表单向电话列表中添加一个新项。


清单 8. phoneform.tea
<% template phoneform(String firstName, String lastName, String phone, 
     String email, String error) %>
<html>
  <head>
    <title>Phone Form</title>
  </head>
  <body>
    <h3>Phone Form</h3>
    <%
      if(error != null) {
        "The following errors must be fixed:<br>"
        "<ul>"
          if(firstName == null or firstName == "") {
            "<li>You must enter a first name</li>"
          }
          if(lastName == null or lastName == "") {
            "<li>You must enter a last name</li>"
          }
          if(phone == null or phone == "") {
            "<li>You must enter a phone number</li>"
          }
        "</ul>"
      }
    %>
    <form method="POST" action="phoneadd.tea">
      <% nullFormat("") %>
      <b>First Name:</b> 
      <input type="text" name="firstName" value="<% firstName %>"><br>
      <b>Last Name:</b> 
      <input type="text" name="lastName" value="<% lastName %>"><br>
      <b>Phone:</b> 
      <input type="text" name="phone" value="<% phone %>"><br>
      <b>E-mail:</b> 
      <input type="text" name="email" value="<% email %>"><br>
      <input type="submit" value="Add">
    </form>
  </body>
</html>

和其它时候一样,第一行总是模板声明。该模板获取五个参数:

  • firstName
  • lastName
  • phone
  • email
  • 一个错误标志

只有在向电话列表中添加项出现错误时,才会使用这些可选参数。

第一块 scriptlet 代码定义了发生错误时的行为。例如,如果用户没有输入名,那么会出现一条消息指示他们输入名。请注意,当您在 Tea 中比较一个 String 时,您可以省略 equals() 方法。这是 Tea 使编程更加简单的另外一种方法。

实际的输入表单出现在清单 8 中的 scriptlet 代码后面的 HTML 中。我还将空格式设置为空字符串,以便即使某个字段没有定义,文本字段也不会显示“null”作为它的值。

phoneadd.tea 模板负责处理来自 phoneform.tea 的输入。


清单 9. phoneadd.tea
<% template phoneadd(String firstName, String lastName, String phone, 
     String email) %>
<%
  if(firstName == "" or lastName == "" or phone == "") {
    call phoneform(firstName, lastName, phone, email, 1)
  } else {
    addPhoneEntry(firstName, lastName, phone, email)
    call phonelist()
  }
%>

phoneadd.tea 本身并不显示任何内容 ― 它负责进行适当的调用以添加电话项,然后将控制权转给 phonelist.tea。

如果输入了所有所需的字段,那么就调用 PhoneContext 的 addPhoneEntry() 方法来添加项,然后调用 phonelist.tea 模板来显示电话列表。如果所需的任何字段被留为空白,那么控制权就被转回 phoneform.tea,phoneform.tea 要求用户填完表单。

要测试电话簿,请重新启动您的服务器并将您的浏览器指向 http://localhost:8080/teademo/phonelist.tea





回页首


结束语

在本文中,我向您介绍了 Tea 模板语言,并讨论了它是如何能够成为 JSP 技术的替代方案的。读完本文之后,我希望您会理解 Tea 的简单性以及如何用它来避免 JSP 环境中固有的许多问题。

在第二部分中,我将讨论 Tea 的 Model 1 设计问题,并向您演示怎样才能将 Tea 用作 Model 2 架构的 Web 应用程序 ― 尤其是 Jakarta Struts 应用程序中的视图层。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 查阅 Jason Hunter 的书 Java Servlet Programming (O'Reilly 2001 年),这本书用了一整章的篇幅来讲述 Tea。


  • 在 Jason Hunter 的“ The Problems with JSP”中阅读关于 JSP 技术不足之处的更多信息。


  • 探讨 WebMacro,它是 JSP 的另一种替代方案,也是 AltaVista 采用的技术。


  • 下载 Velocity,它是一种 JSP 替代方案,来自 Apache Jakarta 项目,它经常被用于 Apache Jakarta 的 Jetspeed门户网站框架。


  • 查阅 Enhyrdra XMLC,它是一种 JSP 技术的基于 XML 的替代方案。


关于作者

Craig 是 Michaels Stores, Inc. 的 Web 开发经理。他有八年软件开发经验,其中六年是用 Java。Craig 是 Sun 认证的 Java 程序员,他还是 Sun 认证的 Java 平台架构设计师,他拥有新墨西哥州大学计算机科学理学士学位。可以通过 javadude@habuma.com和 Craig 联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款