如果你是一名Java软件或Ajax开发者,可能Google Web Toolkit(GWT)已经引起了你的注意。
2006年5月,Google发布了这一免费的工具箱,遵循Apache风格的许可证。GWT被设计用于以Java语言编写Ajax应用。Google已经提供了Windows和Linux下的beta版本,并承诺以后增加Mac OS X版本。
本文描述了在Max OS X上一个简单的Ajax应用的开发,使用GWT和常用的Java工具,如Apache Ant,Tomcat 5.0 Servlet容器,以及IntelliJ IDEA集成开发环境(后者是一款商业IDE)。本文假设读者具有Java和Ant的基本知识。
我下载了GWT Linux beta版本,用Java编写了我的应用程序,然后用一个Ant构建文件编译和在一个Tomcat 5.0实例上部署了此程序。Ant文件执行GWT Java-to-JavaScript编译器。此“编译器”是一个命令行脚本,它执行一个GWT Java类,而后者为应用程序输出JavaScript脚本。
使用GWT beta包括两种开发模式:宿主(host)模式和web模式。
宿主模式是一个开发中的中间步骤,它使用内嵌的GWT浏览器;在这种模式下,已编译代码继续在Java虚拟机(JVM)中运行。然而,宿主模式对于我们这些借用Linux版本的Mac OS X用户还不可用。Mac OS X下的宿主模式要等到Max OS X版本发布了。
本文探讨了GWT开发者在为远程过程调用(RPC)创建服务时可能面对的一些典型的web开发相关的任务。RPC是用于使用面向服务架构(SOA)的应用的软件模型的一部分。这些开发任务包括:
首先,我会简要描述该应用程序创建的服务。它被设计用来展示GWT采用的模式。
该应用程序在浏览器中显示一个表单,要求用户输入名字,年龄,以及国家。当用户通过点击按钮提交表单时,程序在一个文本区内显示一条服务器回应而不需刷新页面。图1是程序在Safari浏览器中的显示。
图1: GWT生成的一个简单视图
例如,当用户在一个文本框为空时点击OK,Submit按钮时,结果如图2所示。
图2: 应用程序以红色显示一条错误信息
在Ajax应用中使用RPC消除了显式地处理XMLHttpRequest和相关联的服务器返回值的需要,因为GWT对象会为你处理这些通讯工作。
程序定义的每个服务需要两个Java接口和一个Java类。为了编译这些类,需要确定gwt-user.jar库在classpath中(Ant文件中增加一个条目就可以解决)。以下代码示范了定义我们的服务的Java接口。
package com.parkerriver.gwt.testapp.client;
import com.google.gwt.user.client.rpc.RemoteService;
public interface ShowRespService extends RemoteService{
String displayResponse(String req);
}
服务接口必须扩展GWT接口RemoteService。它只定义了一个方法 displayResponse()。
你还需要定义一个接口,客户端或最终被下载的JavaScript代码将会用它调用服务方法。GWT使用了一种回调设计模式,当我给出客户端代码的时候会再来描述它(请看MyForm.java)。
要使一个服务在GWT中可用必须遵守命名约定;在服务接口名(ShowRespService)后增加Async后缀。AsyncCallback对象是 GWT API的一部分,它的目的是为客户端处理服务响应。不管怎样,等你看了这段代码应用的地方后会对这一行为有更清晰的了解。这些对象定义都位于用于生成客户端JavaScript的Java代码中。
最后,你需要定义一个Java类来实现远程服务接口。这个类将会存在于你的Ajax应用程序的服务器端。
该类必须扩展RemoteServiceServlet,这是一个GWT API对象,它本身扩展了javax.servlet.http.HttpServlet。也就是说,该类和它实现的接口需要被部署到你的servlet容器。
现在我们已经定义了服务,让我们退回几分钟来看一看程序的目录结构。Google Web Toolkit包括了一个名为applicationCreator的命令行脚本,它将会为你生成项目目录结构。解压缩GWT下载包后,你可以在顶层目录中找到applicationCreator。我使用下面的命令行来开始:
图3显示了目录结构
图3: 一个GWT和IntelliJ项目目录
applicationCreator生成./src目录和MyForm-compile以及MyForm-shell脚本。我的Ant文件执行MyForm-compile;另一个脚本运行宿主模式。./src目录包括与最初的包命名相符的嵌套目录结构,如图4所示。
图4:一个GWT应用程序的包和模块
文件MyForm.gwt.xml是生成的配置文件,GWT称之为“模块(module)”。它为你的程序定义了表示“入口点(entry point)”的Java类,类似于包含main()方法的Java类。
其它的文件或目录是IntelliJ Web application project的产物,包括./classes,./WEB-INF,和./gwtproj.ipr,所以不需要特别留意它们。
另外,./www目录之前并不会出现(除非你自己创建它),直到运行GWT编译器生成程序代码。我的项目使用Ant文件getproj.xml,而项目属性在gwtproj.properties中定义。在我展示Ant构建文件之前,我们先看一下代表程序入口点的MyForm.java类。
MyForm.jaca类实现了GWT API接口EntryPoint。所以,该类必须实现onModuleLoad()方法,当浏览器载入你的Ajax程序时,浏览器的JavaScript引擎将会调用它。
换句话说,GWT编译器将这个类编译为JavaScript代码。MyForm.java类为浏览器视图建立表单控件。该类同时决定了用户点击OK,Submit按钮时的响应。代码中的注释详细描述了到底发生了什么,所以我在正文中不再赘述。
代码中大多部分处理GWT API。值得一提的是如果你需要实现JavaScript DOM编程,如showRpcStatus()方法中那样,你可以通过使用com.google.gwt.user.client.DOM类实现这个任务。
以下是Ant构建文件的重点;它:
负责编译Java类和执行到JavaScript的转换的两个Ant任务定义为如果产生任何错误,则使整个构建失败。
这里是gwtproj.properties文件包含的部分内容:
下面的XML表示了上述Ant文件的主要内容;完整文件的链接在本文的资源一节中。
你可以在IDE中运行该Ant文件(比如在IntelliJ中)或者在包含构建文件的目录中运行下面的命令行:
大多数情况下,在更改应用程序并运行Ant后,你可以通过在浏览器中刷新页面看到所作的改变。
在最后的设置上,你可能需要了解的是添加gwt-user.jar库到你的web应用的/WEB-INF/lib目录。
我创建了我自己的JAR文件,去掉了其中的javax包,命名为gwt-user-deploy.jar,并添加到/WEB-INF/lib。这是因为Tomcat将不会载入web应用程序中包含servlet API类的库。
applicationCreator同时创建了你的Ajax应用程序的HTML前端,本例中名为MyForm.html。
如果你的应用程序的HTML需要遵从如XHTML transitional或Strict时怎么办呢?对于XHTML transitional的情况,我首先在MyForm.html顶端增加了所需的DOCTYPE,以及html标签的相关属性:
然后我上传MyForm.html到http://validator.w3.org/的World Wide Web协会的HTML验证器。
运行验证器后,我对HTML做了一些简单的修改,如合适地关闭元标签以及增加type="text/javascript"到script标签。
然而,如果你要遵从XHTML Strict标准,则可能需要做一些更复杂的更改。例如,W3C的验证器将iframe标签标为“undefined element”,iframe是GWT的历史支持所需要的(提供如浏览器的后退按钮同样的功能)。XHTML已经移除了iframe元素。
这对你可能不是一个问题(可能会同其它明显问题一起在GWT的未来版本中解决);但是,你可以自己实现替代策略,如扩展GWT的类和创建你自己的兼容的控件。
视觉外观设计是web开发中总是出现的问题。项目设计者希望页面看起来和他们在Adobe Illustrator中创建的一摸一样,是吗?
尽管在复杂的Ajax项目中你可能无法实现这一使程序养眼的理想,至少你可以使用Firefox的DOM Inspector来检视你的Java类最终生成的HTML。那么从这里开始。
打开Firefox的Tools=>DOM Inspector菜单项(如图5)
图5: 用DOM Inspector查看HTML
可见Java代码中的com.google.gwt.user.client.ui.Grid对象实现为一个HTML table标签。table中包含OK,Submit按钮的TD标签与一个样式属性“verticle-align:top”关联。
这使按钮与文本区顶端对齐。下面是MyForm.java类中设置对齐方式的代码:
如果代码中没有此调用,按钮就会很业余地挂在文本区的中间区域。
现在要做的只是使按钮与它上面的文本标签左对齐。
2006年5月,Google发布了这一免费的工具箱,遵循Apache风格的许可证。GWT被设计用于以Java语言编写Ajax应用。Google已经提供了Windows和Linux下的beta版本,并承诺以后增加Mac OS X版本。
本文描述了在Max OS X上一个简单的Ajax应用的开发,使用GWT和常用的Java工具,如Apache Ant,Tomcat 5.0 Servlet容器,以及IntelliJ IDEA集成开发环境(后者是一款商业IDE)。本文假设读者具有Java和Ant的基本知识。
结合使用Ant与GWT
我下载了GWT Linux beta版本,用Java编写了我的应用程序,然后用一个Ant构建文件编译和在一个Tomcat 5.0实例上部署了此程序。Ant文件执行GWT Java-to-JavaScript编译器。此“编译器”是一个命令行脚本,它执行一个GWT Java类,而后者为应用程序输出JavaScript脚本。
使用GWT beta包括两种开发模式:宿主(host)模式和web模式。
宿主模式是一个开发中的中间步骤,它使用内嵌的GWT浏览器;在这种模式下,已编译代码继续在Java虚拟机(JVM)中运行。然而,宿主模式对于我们这些借用Linux版本的Mac OS X用户还不可用。Mac OS X下的宿主模式要等到Max OS X版本发布了。
一种不同的web开发方式
本文探讨了GWT开发者在为远程过程调用(RPC)创建服务时可能面对的一些典型的web开发相关的任务。RPC是用于使用面向服务架构(SOA)的应用的软件模型的一部分。这些开发任务包括:
- 使用构建文件自动化开发和开发步骤(构建过程运行GWT编译器,然后将编译器的输出和服务器端Java类文件部署到一个servlet容器,如Tomcat,Jetty或Resin)。
- 使用Firefox的DOM Inspector查看GWT应用程序生成的HTML。
- 重新设计页面控件而不需改动底层HTML(因为你在使用GWT的Java API)。
- 确定HTML已合法地标记,例如,基于你的组织所要求的特定格式的XHTML文档。
为您服务
首先,我会简要描述该应用程序创建的服务。它被设计用来展示GWT采用的模式。
该应用程序在浏览器中显示一个表单,要求用户输入名字,年龄,以及国家。当用户通过点击按钮提交表单时,程序在一个文本区内显示一条服务器回应而不需刷新页面。图1是程序在Safari浏览器中的显示。

图1: GWT生成的一个简单视图
例如,当用户在一个文本框为空时点击OK,Submit按钮时,结果如图2所示。

图2: 应用程序以红色显示一条错误信息
巧妙的服务机制
在Ajax应用中使用RPC消除了显式地处理XMLHttpRequest和相关联的服务器返回值的需要,因为GWT对象会为你处理这些通讯工作。
程序定义的每个服务需要两个Java接口和一个Java类。为了编译这些类,需要确定gwt-user.jar库在classpath中(Ant文件中增加一个条目就可以解决)。以下代码示范了定义我们的服务的Java接口。
package com.parkerriver.gwt.testapp.client;
import com.google.gwt.user.client.rpc.RemoteService;
public interface ShowRespService extends RemoteService{
String displayResponse(String req);
}
服务接口必须扩展GWT接口RemoteService。它只定义了一个方法 displayResponse()。
你还需要定义一个接口,客户端或最终被下载的JavaScript代码将会用它调用服务方法。GWT使用了一种回调设计模式,当我给出客户端代码的时候会再来描述它(请看MyForm.java)。
package
com.parkerriver.gwt.testapp.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface ShowRespServiceAsync {
public void displayResponse(String s,
AsyncCallback callback);
}
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface ShowRespServiceAsync {
public void displayResponse(String s,
AsyncCallback callback);
}
要使一个服务在GWT中可用必须遵守命名约定;在服务接口名(ShowRespService)后增加Async后缀。AsyncCallback对象是 GWT API的一部分,它的目的是为客户端处理服务响应。不管怎样,等你看了这段代码应用的地方后会对这一行为有更清晰的了解。这些对象定义都位于用于生成客户端JavaScript的Java代码中。
一个改头换面的servlet
最后,你需要定义一个Java类来实现远程服务接口。这个类将会存在于你的Ajax应用程序的服务器端。
package
com.parkerriver.gwt.testapp.server;
import com.parkerriver.gwt.testapp.client.ShowRespService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import java.util.Date;
public class ShowRespServiceImpl extends RemoteServiceServlet
implements ShowRespService {
public String displayResponse(String req) {
if (req.length() < 1 ) {
throw new IllegalArgumentException(
" Blank submissions from the client are invalid. " );
}
StringBuffer buf = new StringBuffer( " Your submission: " );
Date date = new Date();
String serverInfo = this .getServletContext().getServerInfo();
buf.append(req);
buf.append( " /n " );
buf.append( " Server response: " );
buf.append(date.toString());
buf.append( " /n " );
buf.append(serverInfo);
return buf.toString();
}
}
import com.parkerriver.gwt.testapp.client.ShowRespService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import java.util.Date;
public class ShowRespServiceImpl extends RemoteServiceServlet
implements ShowRespService {
public String displayResponse(String req) {
if (req.length() < 1 ) {
throw new IllegalArgumentException(
" Blank submissions from the client are invalid. " );
}
StringBuffer buf = new StringBuffer( " Your submission: " );
Date date = new Date();
String serverInfo = this .getServletContext().getServerInfo();
buf.append(req);
buf.append( " /n " );
buf.append( " Server response: " );
buf.append(date.toString());
buf.append( " /n " );
buf.append(serverInfo);
return buf.toString();
}
}
该类必须扩展RemoteServiceServlet,这是一个GWT API对象,它本身扩展了javax.servlet.http.HttpServlet。也就是说,该类和它实现的接口需要被部署到你的servlet容器。
步骤
现在我们已经定义了服务,让我们退回几分钟来看一看程序的目录结构。Google Web Toolkit包括了一个名为applicationCreator的命令行脚本,它将会为你生成项目目录结构。解压缩GWT下载包后,你可以在顶层目录中找到applicationCreator。我使用下面的命令行来开始:
applicationCreator -out /Users/bruceperry/1gwt/secondapp/ com.parkerriver.gwt.testapp.client.MyForm
图3显示了目录结构

图3: 一个GWT和IntelliJ项目目录
applicationCreator生成./src目录和MyForm-compile以及MyForm-shell脚本。我的Ant文件执行MyForm-compile;另一个脚本运行宿主模式。./src目录包括与最初的包命名相符的嵌套目录结构,如图4所示。

图4:一个GWT应用程序的包和模块
文件MyForm.gwt.xml是生成的配置文件,GWT称之为“模块(module)”。它为你的程序定义了表示“入口点(entry point)”的Java类,类似于包含main()方法的Java类。
<
module
>
<!-- Inherit the core Web Toolkit stuff. -->
< inherits name ="com.google.gwt.user.User" />
<!-- Specify the app entry point class. -->
< entry-point class ="com.parkerriver.gwt.testapp.client.MyForm" />
</ module >
<!-- Inherit the core Web Toolkit stuff. -->
< inherits name ="com.google.gwt.user.User" />
<!-- Specify the app entry point class. -->
< entry-point class ="com.parkerriver.gwt.testapp.client.MyForm" />
</ module >
其它的文件或目录是IntelliJ Web application project的产物,包括./classes,./WEB-INF,和./gwtproj.ipr,所以不需要特别留意它们。
另外,./www目录之前并不会出现(除非你自己创建它),直到运行GWT编译器生成程序代码。我的项目使用Ant文件getproj.xml,而项目属性在gwtproj.properties中定义。在我展示Ant构建文件之前,我们先看一下代表程序入口点的MyForm.java类。
入口点
MyForm.jaca类实现了GWT API接口EntryPoint。所以,该类必须实现onModuleLoad()方法,当浏览器载入你的Ajax程序时,浏览器的JavaScript引擎将会调用它。
换句话说,GWT编译器将这个类编译为JavaScript代码。MyForm.java类为浏览器视图建立表单控件。该类同时决定了用户点击OK,Submit按钮时的响应。代码中的注释详细描述了到底发生了什么,所以我在正文中不再赘述。
package
com.parkerriver.gwt.testapp.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui. * ;
import java.util.Iterator;
public class MyForm implements EntryPoint {
// 提供状态信息的HTML div元素的id
private String statusId = " status " ;
// 一个Grid对象;实际上,是一个HTML table
private Grid grid = new Grid( 5 , 2 );
//其它用户界面对象
private Label nmLab = new Label();
private Label ageLab = new Label();
private Label homeLab = new Label();
private TextBox nmTxt = new TextBox();
private TextBox ageTxt = new TextBox();
private TextBox homeTxt = new TextBox();
private Button okBut = new Button();
private TextArea tarea = new TextArea();
/* 当浏览器载入应用程序时本方法被调用。
本方法设置3个标签和文本框,以及一个
按钮和用来显示服务器相应的文本区 */
public void onModuleLoad() {
//设置标签和文本域
nmLab.setText( " Full Name: " );
nmTxt.setMaxLength( 25 );
ageLab.setText( " Age: " );
ageTxt.setVisibleLength( 3 );
ageTxt.setMaxLength( 3 );
homeLab.setText( " Home country: " );
homeTxt.setMaxLength( 25 );
//将这些控件放入Grid中
grid.setWidget( 0 , 0 ,nmLab);
grid.setWidget( 0 , 1 ,nmTxt);
grid.setWidget( 1 , 0 ,ageLab);
grid.setWidget( 1 , 1 ,ageTxt);
grid.setWidget( 2 , 0 ,homeLab);
grid.setWidget( 2 , 1 ,homeTxt);
// 设置按钮和文本区
tarea.setCharacterWidth( 40 );
tarea.setVisibleLines( 25 );
okBut.setText( " OK, Submit " );
// 通过增加一个listener对象为按钮设置行为
// 详细地讲,一个带有onClick()事件处理器的
// ClickListener对象。
okBut.addClickListener( new ClickListener() {
public void onClick(Widget sender) {
//提示用户远程过程调用的状态;
//见下面的方法
showRpcStatus( true );
// 为服务器端服务创建一个客户端存根的实例
ShowRespServiceAsync respService =
(ShowRespServiceAsync) GWT
.create(ShowRespService. class );
ServiceDefTarget endpoint = (ServiceDefTarget) respService;
//我们的服务的实现是一个RemoteServiceServlet的实例,
//所以提供到该servlet的服务器路径
// 该路径为web.xml中设置的值
endpoint.setServiceEntryPoint( " /parkerriver/s/showresp " );
//该接口处理服务器响应。
// 它会在一个文本区中显示服务器的响应。
// 如果回复消息表示一个错误
// 则将用红色字体显示
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
//如有,则移除与错误消息外观相关的‘warning’CSS样式
//
if (tarea.getStyleName().
equalsIgnoreCase( " warning " )){
tarea.removeStyleName( " warning " );
}
//文本区显示服务器的返回值
tarea.setText((String)result);
}
public void onFailure(Throwable caught) {
//文本区显示任何异常消息
tarea.setStyleName( " warning " );
tarea.setText(
" Server request raised an error; Java exception : " +
caught == null ? " An unknown exception " :
caught.getMessage());
}
};
// 调用服务方法。
// 首先验证表单值。
try {
respService.displayResponse(
getPanelTextContent(grid, true ),
callback);
} catch (Exception e) {
tarea.setStyleName( " warning " );
tarea.setText( " Server request raised an error: " +
e.getMessage());
} finally {
// 当我们完成RPC调用时
// 移除状态信息
showRpcStatus( false );
}
}
});
//现在将这些控件加到Grid上
grid.setWidget( 3 , 0 ,okBut);
grid.setWidget( 3 , 1 ,tarea);
//为OK按钮的单元设定垂直对齐属性
grid.getCellFormatter().setVerticalAlignment( 3 , 0 ,
HasVerticalAlignment.ALIGN_TOP);
//为文本框设置垂直对齐属性,
// 以使它们合适地排列
grid.getCellFormatter().setVerticalAlignment( 0 , 1 ,
HasVerticalAlignment.ALIGN_BOTTOM);
grid.getCellFormatter().setVerticalAlignment( 1 , 1 ,
HasVerticalAlignment.ALIGN_BOTTOM);
grid.getCellFormatter().setVerticalAlignment( 2 , 1 ,
HasVerticalAlignment.ALIGN_BOTTOM);
// 将grid,实际上是一个HTML table,加到
// 浏览器HTML中id值为"gridholder"的div元素中。
RootPanel.get( " gridholder " ).add(grid);
}
/*简单地测试是否有为空的域,然后以单一字符串返回
提交的值 。
HasWidgets是Grid和其它panel类型的对象实现的一个接口。
因此,我们可以将grid传入该方法;遍历它包含的文本框,
并且验证文本框的内容。
*/
private String getPanelTextContent(HasWidgets panelType,
boolean validateContent) {
StringBuffer buf = new StringBuffer( "" );
String tmp = null ;
if (panelType != null ) {
//为了简介,省略
}
// 返回以空格分隔的文本框的内容
return buf.toString();
}
/* 过于简化的验证! */
private boolean validateText(String _content){
return _content.length() > 0 ;
}
private int getTextboxCount(HasWidgets pType){
// 未显示: 返回panel中TextBox控件的数量
}
/* 如果响应很长时间才到达
则显示给用户一个状态信息。 */
private void showRpcStatus( boolean _on){
// 利用GWT DOM API进行JavaScript DOM编程
Element el = DOM.getElementById(statusId);
if (el != null ) {
if (_on) {
DOM.setStyleAttribute(el, " font-size " , " 1.2em " );
DOM.setStyleAttribute(el, " color " , " green " );
DOM.setInnerHTML(el, " Fetching server info
"
);
} else {
DOM.setInnerHTML(el, "" );
}
}
}
}
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui. * ;
import java.util.Iterator;
public class MyForm implements EntryPoint {
// 提供状态信息的HTML div元素的id
private String statusId = " status " ;
// 一个Grid对象;实际上,是一个HTML table
private Grid grid = new Grid( 5 , 2 );
//其它用户界面对象
private Label nmLab = new Label();
private Label ageLab = new Label();
private Label homeLab = new Label();
private TextBox nmTxt = new TextBox();
private TextBox ageTxt = new TextBox();
private TextBox homeTxt = new TextBox();
private Button okBut = new Button();
private TextArea tarea = new TextArea();
/* 当浏览器载入应用程序时本方法被调用。
本方法设置3个标签和文本框,以及一个
按钮和用来显示服务器相应的文本区 */
public void onModuleLoad() {
//设置标签和文本域
nmLab.setText( " Full Name: " );
nmTxt.setMaxLength( 25 );
ageLab.setText( " Age: " );
ageTxt.setVisibleLength( 3 );
ageTxt.setMaxLength( 3 );
homeLab.setText( " Home country: " );
homeTxt.setMaxLength( 25 );
//将这些控件放入Grid中
grid.setWidget( 0 , 0 ,nmLab);
grid.setWidget( 0 , 1 ,nmTxt);
grid.setWidget( 1 , 0 ,ageLab);
grid.setWidget( 1 , 1 ,ageTxt);
grid.setWidget( 2 , 0 ,homeLab);
grid.setWidget( 2 , 1 ,homeTxt);
// 设置按钮和文本区
tarea.setCharacterWidth( 40 );
tarea.setVisibleLines( 25 );
okBut.setText( " OK, Submit " );
// 通过增加一个listener对象为按钮设置行为
// 详细地讲,一个带有onClick()事件处理器的
// ClickListener对象。
okBut.addClickListener( new ClickListener() {
public void onClick(Widget sender) {
//提示用户远程过程调用的状态;
//见下面的方法
showRpcStatus( true );
// 为服务器端服务创建一个客户端存根的实例
ShowRespServiceAsync respService =
(ShowRespServiceAsync) GWT
.create(ShowRespService. class );
ServiceDefTarget endpoint = (ServiceDefTarget) respService;
//我们的服务的实现是一个RemoteServiceServlet的实例,
//所以提供到该servlet的服务器路径
// 该路径为web.xml中设置的值
endpoint.setServiceEntryPoint( " /parkerriver/s/showresp " );
//该接口处理服务器响应。
// 它会在一个文本区中显示服务器的响应。
// 如果回复消息表示一个错误
// 则将用红色字体显示
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
//如有,则移除与错误消息外观相关的‘warning’CSS样式
//
if (tarea.getStyleName().
equalsIgnoreCase( " warning " )){
tarea.removeStyleName( " warning " );
}
//文本区显示服务器的返回值
tarea.setText((String)result);
}
public void onFailure(Throwable caught) {
//文本区显示任何异常消息
tarea.setStyleName( " warning " );
tarea.setText(
" Server request raised an error; Java exception : " +
caught == null ? " An unknown exception " :
caught.getMessage());
}
};
// 调用服务方法。
// 首先验证表单值。
try {
respService.displayResponse(
getPanelTextContent(grid, true ),
callback);
} catch (Exception e) {
tarea.setStyleName( " warning " );
tarea.setText( " Server request raised an error: " +
e.getMessage());
} finally {
// 当我们完成RPC调用时
// 移除状态信息
showRpcStatus( false );
}
}
});
//现在将这些控件加到Grid上
grid.setWidget( 3 , 0 ,okBut);
grid.setWidget( 3 , 1 ,tarea);
//为OK按钮的单元设定垂直对齐属性
grid.getCellFormatter().setVerticalAlignment( 3 , 0 ,
HasVerticalAlignment.ALIGN_TOP);
//为文本框设置垂直对齐属性,
// 以使它们合适地排列
grid.getCellFormatter().setVerticalAlignment( 0 , 1 ,
HasVerticalAlignment.ALIGN_BOTTOM);
grid.getCellFormatter().setVerticalAlignment( 1 , 1 ,
HasVerticalAlignment.ALIGN_BOTTOM);
grid.getCellFormatter().setVerticalAlignment( 2 , 1 ,
HasVerticalAlignment.ALIGN_BOTTOM);
// 将grid,实际上是一个HTML table,加到
// 浏览器HTML中id值为"gridholder"的div元素中。
RootPanel.get( " gridholder " ).add(grid);
}
/*简单地测试是否有为空的域,然后以单一字符串返回
提交的值 。
HasWidgets是Grid和其它panel类型的对象实现的一个接口。
因此,我们可以将grid传入该方法;遍历它包含的文本框,
并且验证文本框的内容。
*/
private String getPanelTextContent(HasWidgets panelType,
boolean validateContent) {
StringBuffer buf = new StringBuffer( "" );
String tmp = null ;
if (panelType != null ) {
//为了简介,省略

}
// 返回以空格分隔的文本框的内容
return buf.toString();
}
/* 过于简化的验证! */
private boolean validateText(String _content){
return _content.length() > 0 ;
}
private int getTextboxCount(HasWidgets pType){
// 未显示: 返回panel中TextBox控件的数量
}
/* 如果响应很长时间才到达
则显示给用户一个状态信息。 */
private void showRpcStatus( boolean _on){
// 利用GWT DOM API进行JavaScript DOM编程
Element el = DOM.getElementById(statusId);
if (el != null ) {
if (_on) {
DOM.setStyleAttribute(el, " font-size " , " 1.2em " );
DOM.setStyleAttribute(el, " color " , " green " );
DOM.setInnerHTML(el, " Fetching server info

} else {
DOM.setInnerHTML(el, "" );
}
}
}
}
代码中大多部分处理GWT API。值得一提的是如果你需要实现JavaScript DOM编程,如showRpcStatus()方法中那样,你可以通过使用com.google.gwt.user.client.DOM类实现这个任务。
构建文件
以下是Ant构建文件的重点;它:
- 编译Java文件,结果写入项目目录中的./classes目录。
- 执行GWT编译脚本(本例中名为MyForm-compile)。
- 将./www目录中生成的结果代码移到一个已部署到Tomcat上的更大的web应用。
- 复制编译后的Java servlet和相关接口(ShowRespService)到同一个web应用。
负责编译Java类和执行到JavaScript的转换的两个Ant任务定义为如果产生任何错误,则使整个构建失败。
Ant XML
这里是gwtproj.properties文件包含的部分内容:
web.deploy.location
=
/users/bruceperry/parkerriver/gwt
web.classes.location = /users/bruceperry/parkerriver/WEB-INF/classes
web.classes.location = /users/bruceperry/parkerriver/WEB-INF/classes
下面的XML表示了上述Ant文件的主要内容;完整文件的链接在本文的资源一节中。
<?
xml version="1.0" encoding="UTF-8"
?>
< project name ="gwtproj" default ="all" >
< property file ="gwtproj.properties" />
<!-- The top-level directory for the project and
where the ant file resides -->
< dirname property ="module.gwtproj.basedir" file ="${ant.file}" />
<!-- The ./classes directory inside the top-level directory -->
< property name ="gwtproj.output.dir" value =
"${module.gwtproj.basedir}/classes" />
<!-- This target calls MyForm-compile to create
all the content in the ./www directory -->
< target name ="gwt-compile" depends =
"compile.production.classes"
description ="use gwt's compiler" >
< delete >
< fileset dir ="${web.deploy.location}" includes ="**/*" />
</ delete >
< exec executable =
"${module.gwtproj.basedir}/MyForm-compile"
failonerror ="true" />
< copy todir ="${web.deploy.location}" >
< fileset dir =
"${module.gwtproj.basedir}/www" >
</ fileset >
</ copy >
</ target >
< target name ="compile.production.classes" description =
"Compile the gwtproj production classes" >
< mkdir dir ="${gwtproj.output.dir}" />
< javac destdir ="${gwtproj.output.dir}" debug =
"on" failonerror ="true" nowarn =
"off" memoryMaximumSize ="128m" fork =
"true" executable ="${module.jdk.home.gwtproj}/bin/javac" >
< classpath refid ="gwtproj.module.classpath" />
< src refid ="gwtproj.module.sourcepath" />
</ javac >
</ target >
<!-- copy the Java servlet classes to the web application -->
< target name ="deploy.classes" depends ="gwt-compile"
description ="copy classes to web directory" >
< copy todir ="${web.classes.location}" >
< fileset dir ="${gwtproj.output.dir}" >
</ fileset >
</ copy >
</ target >
< target name ="all" depends ="deploy.classes"
description ="build all" />
</ project >
< project name ="gwtproj" default ="all" >
< property file ="gwtproj.properties" />
<!-- The top-level directory for the project and
where the ant file resides -->
< dirname property ="module.gwtproj.basedir" file ="${ant.file}" />
<!-- The ./classes directory inside the top-level directory -->
< property name ="gwtproj.output.dir" value =
"${module.gwtproj.basedir}/classes" />
<!-- This target calls MyForm-compile to create
all the content in the ./www directory -->
< target name ="gwt-compile" depends =
"compile.production.classes"
description ="use gwt's compiler" >
< delete >
< fileset dir ="${web.deploy.location}" includes ="**/*" />
</ delete >
< exec executable =
"${module.gwtproj.basedir}/MyForm-compile"
failonerror ="true" />
< copy todir ="${web.deploy.location}" >
< fileset dir =
"${module.gwtproj.basedir}/www" >
</ fileset >
</ copy >
</ target >
< target name ="compile.production.classes" description =
"Compile the gwtproj production classes" >
< mkdir dir ="${gwtproj.output.dir}" />
< javac destdir ="${gwtproj.output.dir}" debug =
"on" failonerror ="true" nowarn =
"off" memoryMaximumSize ="128m" fork =
"true" executable ="${module.jdk.home.gwtproj}/bin/javac" >
< classpath refid ="gwtproj.module.classpath" />
< src refid ="gwtproj.module.sourcepath" />
</ javac >
</ target >
<!-- copy the Java servlet classes to the web application -->
< target name ="deploy.classes" depends ="gwt-compile"
description ="copy classes to web directory" >
< copy todir ="${web.classes.location}" >
< fileset dir ="${gwtproj.output.dir}" >
</ fileset >
</ copy >
</ target >
< target name ="all" depends ="deploy.classes"
description ="build all" />
</ project >
你可以在IDE中运行该Ant文件(比如在IntelliJ中)或者在包含构建文件的目录中运行下面的命令行:
ant -buildfile gwtproj.xml
大多数情况下,在更改应用程序并运行Ant后,你可以通过在浏览器中刷新页面看到所作的改变。
最后设置
在最后的设置上,你可能需要了解的是添加gwt-user.jar库到你的web应用的/WEB-INF/lib目录。
我创建了我自己的JAR文件,去掉了其中的javax包,命名为gwt-user-deploy.jar,并添加到/WEB-INF/lib。这是因为Tomcat将不会载入web应用程序中包含servlet API类的库。
web开发者的吹毛求疵
applicationCreator同时创建了你的Ajax应用程序的HTML前端,本例中名为MyForm.html。
如果你的应用程序的HTML需要遵从如XHTML transitional或Strict时怎么办呢?对于XHTML transitional的情况,我首先在MyForm.html顶端增加了所需的DOCTYPE,以及html标签的相关属性:
<!
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd" >
< html xmlns ="http://www.w3.org/1999/xhtml" xml:lang ="en" lang ="en" >
"http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd" >
< html xmlns ="http://www.w3.org/1999/xhtml" xml:lang ="en" lang ="en" >
然后我上传MyForm.html到http://validator.w3.org/的World Wide Web协会的HTML验证器。
运行验证器后,我对HTML做了一些简单的修改,如合适地关闭元标签以及增加type="text/javascript"到script标签。
Strict:啧啧
然而,如果你要遵从XHTML Strict标准,则可能需要做一些更复杂的更改。例如,W3C的验证器将iframe标签标为“undefined element”,iframe是GWT的历史支持所需要的(提供如浏览器的后退按钮同样的功能)。XHTML已经移除了iframe元素。
这对你可能不是一个问题(可能会同其它明显问题一起在GWT的未来版本中解决);但是,你可以自己实现替代策略,如扩展GWT的类和创建你自己的兼容的控件。
使控件各就各位
视觉外观设计是web开发中总是出现的问题。项目设计者希望页面看起来和他们在Adobe Illustrator中创建的一摸一样,是吗?
尽管在复杂的Ajax项目中你可能无法实现这一使程序养眼的理想,至少你可以使用Firefox的DOM Inspector来检视你的Java类最终生成的HTML。那么从这里开始。
打开Firefox的Tools=>DOM Inspector菜单项(如图5)

图5: 用DOM Inspector查看HTML
可见Java代码中的com.google.gwt.user.client.ui.Grid对象实现为一个HTML table标签。table中包含OK,Submit按钮的TD标签与一个样式属性“verticle-align:top”关联。
这使按钮与文本区顶端对齐。下面是MyForm.java类中设置对齐方式的代码:
//
set the vertical alignment for the OK button's cell
grid.getCellFormatter().setVerticalAlignment( 3 , 0 ,
HasVerticalAlignment.ALIGN_TOP);
grid.getCellFormatter().setVerticalAlignment( 3 , 0 ,
HasVerticalAlignment.ALIGN_TOP);
如果代码中没有此调用,按钮就会很业余地挂在文本区的中间区域。
现在要做的只是使按钮与它上面的文本标签左对齐。