第 1 部分: 梦幻前端
在通往创建在用户 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 站点看到。
图 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 项目:
- 在您的硬盘上任意选择一个方便的位置,创建一个名为 slicr 的新目录。
- 在新的 slicr 目录下打开一个命令行提示符。
- 键入以下命令(您需要更改斜线和与您的操作系统习惯不符的所有符号):
<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 中。
- 打开 Eclipse,然后单击 File > Import。
- 在出现的窗口中展开 General 树,然后选择 Existing Projects into Workspace。
- 单击 Next,然后单击 Browse 来选择 Slicr 根目录。
- 选中项目,确保未 设置 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);
}
}
|
下期精彩预告
您已经使用了创建这个简单版本的 Slicr 客户机所需的全部代码。下一期文章将为您展示如何使用 Derby 数据库在服务器端构建一个数据层,并将数据从其数据库形式转换为可发送给 GWT 客户机的 Java 对象。随后,您会接触到远程过程架构,它连接着服务器与客户机。
如果您的服务器端必须独立运行,那么您就必须去考虑如何为开发和生产环境部署它。您还会学会一些方法,使界面更美观、更令人愉快。现在,去访问 GWT 下载站点,亲自动手试试吧。