UML软件工程组织

使用 Google Web Toolkit、Apache Derby 和 Eclipse 构建 Ajax 应用程序,第 1 部分: 梦幻前端

 

作者:Noel Rappin (noelrappin@gmail.com), 高级软件工程师, Motorola, Inc. 文章出处:IBM

 

本文内容包括:

在通往创建在用户 Web 浏览器中运行的动态 Java?Script 应用程序的征途中,Google Web Toolkit(GWT)是举足轻重的一步。使用 GWT,开发人员可以利用熟知的 Java 技术设计用户界面(UI)和事件模型,而 GWT 会完成使代码对所有主流浏览器友好的繁重工作。这是本系列的第一篇文章,介绍 GWT 基础知识,包括 GWT 是怎样使您创建一个 Asynchronous JavaScript + XML(Ajax)应用程序,而且依然在 Java 语言中编写代码的。探索如何创建和运行一个小型的样本 GWT 应用程序 —— 近来十分流行的一项 Web 2.0 新业务,称为 Slicr,在线销售比萨饼。

 GWT 使得构建富 Ajax 浏览器客户机界面比构建传统 Java GUI 界面还要轻松。然而,即使是 GWT 这样出色的技术也无法独自构建出一个完整的 Web 应用程序。您还必须有一个服务器上的数据存储和某种类型的框架,以便将数据转换成 GWT 可从服务器传递给其客户的 Java 对象。在这一系列文章中,您将使用 Apache Derby 这个 100% 纯 Java 数据库,可将其嵌入与其余服务器端代码相同的 Java 虚拟机(JVM)之中。

本系列的第一篇文章主要探讨 GWT。在这里,您将了解如何设置 GWT,并创建一个简单的客户机界面来响应用户的操作。后续文章将为您展示如何设置 Derby 数据库,并将 GWT 前端与基于 Derby 的后端连接。最终,您将学会如何将在开发环境之外部署您的系统。

 Google Web Toolkit 是什么?

借助于 GWT,您可以使用 Java 编程语言开发 Ajax 应用程序。Ajax 应用程序的特色就是丰富、交互式的环境,往往与传统 UI 应用程序相关联。图 1 展示了一个示例 GWT 界面,它效仿了桌面电子邮件应用程序。这个演示程序可在 GWT 的 Web 站点看到。

查看 Ajax 资源中心,这是您的一站式 Ajax 编程模型信息中心,包括文章和教程、新手入门、推荐阅读清单等。是您了解 Ajax 相关参考资源的最佳去处。

图 1. GWT 电子邮件演示
 
 GWT 最独到的特性就在于:您可以创建 Ajax 应用程序,同时依然使用 Java 语言编写代码。您可以使用自己喜爱的 Java 集成化开发环境(IDE),而更好的事情是,还可以在 Java IDE 中调试您的客户机。可以使用 Java 对象在客户机与服务器之间通信,这样的通信在客户机中比使用 Java applet 时要轻量得多。

从根本上来说,GWT 是一种编译器。它将您编写的 Java 代码转换成 JavaScript 代码,这些代码随后会插入 HTML 页面,并用于运行应用程序的客户端。这样的功能使您免于处理在多种浏览器上支持 JavaScript 代码的细枝末节,从而使您能够专注于程序的界面和交互之上。

当然,如果编译器是 GWT 提供的惟一功能,那也不会令人太过兴奋。幸运的是,它给我们带来的不仅如此。有了 GWT,编译器仅仅是一种交付整个客户机/服务器应用程序架构的机制。其特性包括:

  • 一组标准 UI 小部件(widget),外观良好、灵活性高,并且已进行了调优,可在所有主流浏览器(包括 Safari 和 Opera)中工作。
  • 一种完全在客户端捕捉并响应事件的事件机制。
  • 一个管理 Web 应用程序与服务器间的异步调用的框架。
  • 一种创建有状态浏览器历史记录的机制,以使您的 Ajax 应用程序不会因为有可能出现的后退(Back) 按钮行为而变得混乱。
  • 一个使用 JUnit 为客户机应用程序编写单元测试的测试框架。

本系列将探索上述特性中的大多数。但首先请下载和安装 GWT。

获取 GWT

在本文撰写之时,GWT 的最新版本是 V1.2(本文末尾处的 参考资料 部分给出了下载站点的链接)。Microsoft? Windows? XP、Windows 2000、运行 GTK+ 2.2.1 或更新版本的 Linux? 系统和 Mac SO X 完全支持 GWT。

您下载到的是一个压缩文件。解压缩此文件,然后将得到的目录放在方便的位置。根据版本的不同,各发布版的细节可能会略有不同,但基本元素如下:

  • 三个 .jar 文件:名为 gwt-user.jar 的文件包含您将需要在项目 classpath 中使用的用户类,而另一个包含大量编译器代码,文件名为 gwt-dev-windows.jar 或 gwt-dev-linux.jar。第三个文件 gwt-servlet.jar 用于部署。
  • 三个命令行实用工具:applicationCreator、junitCreator 和 projectCreator。(在 Windows 上,它们的后缀为 .cmd。)稍后将介绍更多相关内容。
  • 一个样本代码目录。

在使用 GWT 时,还有其他一些文件会放在 GWT 主目录中,用于管理临时文件。

创建一个项目

既然您已经下载好了一切,那么接下来的首要任务就是创建一个项目。您将为近来十分流行的一项 Web 2.0 新业务(称为 Slicr)构建一个在线站点,在线销售比萨饼。根据您是否计划使用 IDE,设置 GWT 项目的方法的具体细节会有所不同。就本文目的而言,您将使用 Eclipse,因为它是免费的,而且直接被 GWT 命令行实用工具所支持。

首先使用那些命令行实用工具来创建您的 Eclipse 项目:

  1. 在您的硬盘上任意选择一个方便的位置,创建一个名为 slicr 的新目录。
  2. 在新的 slicr 目录下打开一个命令行提示符。
  3. 键入以下命令(您需要更改斜线和与您的操作系统习惯不符的所有符号):

<GWT_HOME>/projectCreator -eclipse slicr

这条命令将创建 GWT 项目所需的最基本内容。得到的结果是一个新的 src 子目录,另外还有新的 .project 和 .classpath 文件。

您可以就这样先使用建立好的项目,但 GWT 希望能有进一步的结构,您可以通过如下命令来设置它:

<GWT_HOME>/applicationCreator -eclipse slicr com.ibm.examples.client.Slicr

-eclipse slicr 参数是 Eclipse 项目的名称,必须与您在 projectCreator 中使用的相同。(如果不同,在 Eclipse 内启动您的 GWT 应用程序时就会出问题。)最后一个参数是您的应用程序的主类的完全限定类名。需要将最低级别的包命名为 client,但除此之外,您都可以自由选择。

这条命令设置了一些文件。.java 文件是您的主类(以及主类必须创建的所有父目录)。您得到了一个与 client 位于同一级别的 public 目录,此目录包含一个名为 Slicr.html 的文件。上级目录包含一个名为 Slicr.gwt.xml 的重要文件。GWT 还会创建一个供 Eclipse 使用的 Slicr.launch 文件以及一些 shell 脚本。

将项目迁移到 Eclipse 中

离成功不远了!现在,您必须将项目迁移到 Eclipse 中。

  1. 打开 Eclipse,然后单击 File > Import。
  2. 在出现的窗口中展开 General 树,然后选择 Existing Projects into Workspace。
  3. 单击 Next,然后单击 Browse 来选择 Slicr 根目录。
  4. 选中项目,确保未 设置 Copy projects into workspace 选项,您并不想移动项目。

这个过程会将您的代码置入 Eclipse,在这里您可以好好观察代码。GWT 已创建了三个重要的文件。第一个位于 com.ibm.examples 包中,名为 slicr.gwt.xml。这是您的 XML 配置文件。现在,它看上去如清单 1 所示。

清单 1. slicr.gwt.xml

<module>
<inherits name='com.google.gwt.user.User'/>
<entry-point class='com.ibm.examples.client.Slicr'/>
</module>

在这个 XML 文档中,您可以为 GWT 应用程序定义模块。模块 是 GWT 代码的基本单位,被客户机使用的 HTML 页面所引用。

public 目录中创建的第二个重要的文件是 Slicr.html 文件。这是实际上作为 Web 应用程序的首页发送给客户机的 .html 文件。默认情况下包含大量您不需要的注释。此文件的核心如清单 2 所示。

清单 2. Slicr.html

<html>
<head>
<title>Wrapper HTML for Slicr</title>
<meta name='gwt:module'
content='com.ibm.examples.Slicr'>
</head>
<body>
<script language="javascript"
src="gwt.js"></script>
<iframe id="__gwt_historyFrame"
style="width:0;height:0;border:0"></iframe>
<h1>Slicr</h1>
<p>
This is an example of a host page for
the Slicr application.
</p>
<table align=center>
<tr>
<td id="slot1"></td>
<td id="slot2"></td>
</tr>
</table>
</body>
</html>

在这一方面,要认识到的最重要的一点就是:最终它将是一个普通的 .html 文件。尽管在其中包含您需要的任何 HTML。在这个 .html 文件中,有四个元素将其标识为由 GWT 使用的文件。包括:

  • meta 标记: name 属性必须出现,content属性是模块的完全限定逻辑名。(也就是包含 XML 模块文件的包再加上 XML 的文件名,不带扩展名。)该标记将您的 HTML 页面与特定模块相关联。调用页面将启动模块。(明确地说,该模块中的所有入口点类都被初始化。)
  • script 标记:此标记载入一个名为 gwt.js 的文件,这是将 GWT Java 代码转换为 JavaScript 代码时创建的文件之一。由于此文件控制所有客户机代码的加载,所以要运行程序,将这个文件包含进来是非常重要的。
  • iframe 标记:准确地包含此标记(准确地编写)允许您的 Web 程序记录历史情况和状态,这也就意味着,您的 GWT 应用程序不会禁用用户的后退(Back)按钮。
  • td 标记:这个特殊 .html 文件中的这些标记包含 JavaScript 标识符。这方面没有什么特别不寻常的东西,但随后您会看到,GWT 将这些标识符用作放置元素的地方。

样本 Java 启动类

GWT 还为您创建了一个样本 Java 启动类,如清单 3 所示。

清单 3. 样本 Java 启动类

public class Slicr implements EntryPoint {

public void onModuleLoad() {
final Button button = new Button("Click me");
final Label label = new Label();
button.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if (label.getText().equals(""))
label.setText("Hello World!");
else
label.setText("");
}
});
RootPanel.get("slot1").add(button);
RootPanel.get("slot2").add(label);
}
}

目前,关于这个类只有几件事情需要注意。Java 类位于您的 client 包中,表示着它意图被 GWT 编译到 JavaScript 代码中。这会给哪些内容可放在此文件中造成了一些限制,但在大多数时候,其中都是基本的 Java 1.4 代码。所创建的类实现接口 EntryPoint,此接口仅定义了一个方法,即 onModuleLoad()。在载入引用了此模块的 HTML 页面时,GWT 将自动调用此方法。

这个特殊的 EntryPoint 非常简单。它的头两行定义了一个按钮和一个标签。在最后两行中,它使用 RootPanel.get() 方法,将这些小部件与特定的 HTML 页面元素相关联。传递给此方法的参数是元素的 JavaScript ID,与其在 HTML 页面中的定义相同。

在头两行和最后两行之间,您使用 Swing 中用于绑定事件的同一风格来定义一个事件侦听器。在本例中,当您的按钮被单击时,ClickListener 将被调用,并简单地来回切换标签小部件中的文本。

运行您的 GWT 程序

现在,您可以尝试运行 GWT 创建的样本程序。有两种不同的方法可以运行 GWT 程序:Web 模式 和 托管模式(hosted mode)。Web 模式是完全部署模式,在将您的 GWT 程序编译为 JavaScript 代码之后,您将使用这种模式。

在开发过程中使用的是托管模式,它是一个模拟器,通过它可以同时模拟客户机和服务器代码,在开发时使部署大为简化。(托管模式目前在 Mac OS X 上尚不可用。)如果您正在使用带有调试器的 IDE,那么可以在调试器下于托管模式中运行您的 GWT 程序,这允许您设置断点和变量观察(variable watch),甚至可以在即将编译为 JavaScripy 代码的客户机代码部分中进行设置。毫无疑问,这非常酷,也的确非常有用。在 GWT 中工作时,您将在托管模式中花费许多时间。

有多种调用托管模式的方法,您可任选其中的一种。前面您运行的 applicationCreator 脚本创建了一个 Slicr-shell 脚本,可通过命令行调用它来启动托管模式。在上文中,您看到了如何将您的 GWT 项目导入 Eclipse。在 Eclipse 项目中,可以从 Run 菜单或工具栏选择 Debug 或 Run。在出现的窗口中,单击 Java Application 可看到 Slicr 选项。该选项是由 GWT 创建的一个 Slicr.launch 文件提供的,您会将该文件与项目的其余部分一起导入 Eclipse。这个启动文件为 Eclipse 描述了 classpath 和启动类。当然,一旦运行了启动程序(launcher),在您单击工具栏按钮时它将是默认选择。图 2 展示了实际情况下,该窗口的运行效果。

图 2. 调用托管模式

在托管模式下运行时,将出现两个窗口。(请注意,托管模式设置初始化需要占用一分钟左右的时间,特别是在第一次运行时。)第一个窗口如图 3 所示,名为 Google Web Toolkit Development Shell / Port 8888。此窗口包含来自 GWT 的错误和日志消息。使用工具栏,您可以打开一个新的托管浏览器,也可以展开、折叠和清除屏幕上的日志记录。

 图 3. 托管模式的 shell 窗口
 
 第二个窗口如图 4 所表,是模拟浏览器。如您所见,它包含来自 slicr.html 页面的静态 HTML,还包括 Slicr.java EntryPoint 类中创建的按钮小部件。单击按钮将切换标签。如果您在设置步骤中进行了某些错误的操作,那么就不会看到这个窗口,而是在 shell 窗口中看到一条错误消息。验证所有名称都是正确的。(特别要主要查看 .launch 文件,验证它指定了正确的项目目录。)

图 4. 托管模式模拟浏览器
 

设置一个简单的客户机

就本文而言,我们集中关注在屏幕上获得小部件,并提供一点交互性。您最终将得到如图 5 所示的屏幕,大体上来说,它简单而又实用。

图 5. Slicr
 
 要在页面载入时创建这些小部件,您必须将代码放在 EntryPoint 类的 onModuleLoad() 方法中。清单 4 定义了一些实例数据成员和一个调用 helper 来构建各面板的顶级方法。

清单 4. Module 加载事件处理程序

private DockPanel panel;
private List clearables;

public void onModuleLoad() {
clearables = new ArrayList();
initDockPanel();
panel.add(buildActionPanel(), DockPanel.SOUTH);
panel.add(buildPizzaTypePanel(), DockPanel.WEST);
panel.add(buildToppingPanel(), DockPanel.EAST);
RootPanel.get("slicr").add(panel);
}

设置小部件

您将所有一切都放在一个 DockPanel 内,这是使用 BorderLayout 的 Swing 面板的 GWT 等价部分。Swing 具有一个 Panel 类和多个布局管理器,而 GWT 具有多个 panel 子类,每个子类都使用自己的算法来安排子小部件的布局。其他面板类还包括 SimplePanel、HTMLTable、FlowPanel 和 StackPanel。创建停靠面板并不困难:setter 会帮助您完成所需的一切工作, 如清单 5 所示。

清单 5. 初始化主面板

private void initDockPanel() {
panel = new DockPanel();
panel.setBorderWidth(1);
panel.setSpacing(5);
}

创建南侧(按钮)面板

首先要定义南侧(按钮)面板,因为在请求角落处时,DockPanel 是最先出现、最先被服务的。通过这种方式,南侧的小部件才会在整个面板上运行。您将操作面板构建为一个 HorizontalPanel,它大致上相当于 Swing 中的一个框,如清单 6 所示。

清单 6. 南侧(按钮)面板

public HorizontalPanel buildActionPanel() {
HorizontalPanel actions = new HorizontalPanel();
actions.setSpacing(10);
Button clear = new Button("Clear");
clear.addClickListener(new ClearClickListener());
Button newPizza = new Button("Another Pizza");
Button submitOrder = new Button("Submit");
actions.add(clear);
actions.add(newPizza);
actions.add(submitOrder);
return actions;
}

您使用了 GWT Button 小部件来创建三个按钮,然后将其添加到面板中。还为 Clear 按钮创建了一个 ClickListener,稍后我们将定义它。GWT 划分其事件侦听器的方式与 Swing 不同:ClickListener 侦听且仅侦听鼠标点击。(通常,您会看到侦听器被定义为内嵌匿名类。我发现这种风格难于读取和测试,所以我创建了一个有名称的内部类。)

创建西侧(比萨饼类型)面板

带有比萨饼类型的面板并不复杂。需要使用 GWT RadioButton 小部件,如清单 7 所示。

清单 7. 西侧(比萨饼类型)面板

public static final String[] PIZZA_TYPES = new String[] {
"Thin Crust Medium", "Thin Crust Large",
"Thin Crust X-Large", "Thick Crust Medium",
"Thick Crust Large"
};

private VerticalPanel buildPizzaTypePanel() {
VerticalPanel pizzaTypes = new VerticalPanel();
HTML label = new HTML("<h2>Pizza</h2>");
pizzaTypes.add(label);
for (int i = 0; i < PIZZA_TYPES.length; i++) {
RadioButton radio = new RadioButton("pizzaGroup",
PIZZA_TYPES[i]);
clearables.add(radio);
pizzaTypes.add(radio);
}
return pizzaTypes;
}

稍后,您将对数据进行某些更聪明的处理。现在,您使用的是 VerticalPanel,它是 HorizontalPanel 的垂直对应部分。您还使用了 HTML 小部件,这只是一个呈现 HTML 的标签。(本质上,它是一个包围着 HTML <span> 标记的包装器。)RadioButton 构造函数接受两个参数。第一个是单选按钮组的字符串标签,第二个是一个文本标签。您将各个按钮同时添加到面板中和可清除项的实例列表中,这个列表将用于一个侦听器之中。

创建东侧(浇头)面板

浇头面板略微复杂一点。您需要允许用户制作每一半具有不同浇头的一张比萨饼。单击与某种浇头对应的按钮会为两半都选中浇头,但每一半都可单独选中或清除。您需要把一切排列整齐,所以使用一个网格,如清单 8 所示。

清单 8. 浇头网格

public static final String[] TOPPINGS = new String[] {
"Anchovy", "Gardineria", "Garlic",
"Green Pepper", "Mushrooms", "Olives",
"Onions", "Pepperoni", "Pineapple",
"Sausage", "Spinach"
};

private VerticalPanel buildToppingPanel() {
VerticalPanel toppings = new VerticalPanel();
toppings.add(new HTML("<h2>Toppings</h2>"));
Grid topGrid = new Grid(TOPPINGS.length + 1, 3);
topGrid.setText(0, 0, "Topping");
topGrid.setText(0, 1, "Left");
topGrid.setText(0, 2, "Right");
for (int i = 0; i < TOPPINGS.length; i++) {
Button button = new Button(TOPPINGS[i]);
CheckBox leftCheckBox = new CheckBox();
CheckBox rightCheckBox = new CheckBox();
clearables.add(leftCheckBox);
clearables.add(rightCheckBox);
button.addClickListener(new ToppingButtonListener(
leftCheckBox, rightCheckBox));
topGrid.setWidget(i + 1, 0, button);
topGrid.setWidget(i + 1, 1, leftCheckBox);
topGrid.setWidget(i + 1, 2, rightCheckBox);
}
toppings.add(topGrid);
return toppings;
}

又一次地,您使用了 VerticalPanel 和 HTML 小部件。您将所有的东西都放在了一个 GWT Grid 中,因此在创建时必须设置好网格的大小。网格中的每个单元格都包含纯文本或另外一个 GWT 小部件。对于每一行,创建按钮和两个复选框,然后将其指派给相应的单元格。为按钮添加一个侦听器,随后将复选框放在 clearable 列表中。

定义好的侦听器

设置好小部件后,就该看看那两个已经定义好的侦听器了。其中较为简单的一个是 Clear 按钮的侦听器。该按钮仅仅是遍历 clearable 列表并清除所有内容,如清单 9 所示。

清单 9. 为 Clear 按钮定义的侦听器

private class ClearClickListener implements ClickListener {
public void onClick(Widget sender) {
for (Iterator iter = clearables.iterator(); iter.hasNext();) {
CheckBox cb = (CheckBox) iter.next();
cb.setChecked(false);
}
}
}

注意:在 GWT 中,RadioButton 实际上是 CheckBox 的子类。因此上述代码不会触发类转换异常。

浇头按钮的侦听器只是稍微复杂一点。如果未选中任何相关复选框,则此侦听器将选中两个复选框。反之,则清除两个复选框,如清单 10 所示。

清单 10. 为浇头按钮定义的侦听器

private class ToppingButtonListener implements ClickListener {

private CheckBox cb1;
private CheckBox cb2;

public ToppingButtonListener(CheckBox cb1, CheckBox cb2) {
this.cb1 = cb1;
this.cb2 = cb2;
}

public void onClick(Widget sender) {
boolean unchecked = !cb1.isChecked() && !cb2.isChecked();
cb1.setChecked(unchecked);
cb2.setChecked(unchecked);
}
}

 


版权所有:UML软件工程组织