级别: 初级 邹 林志 (zoulz@cn.ibm.com), 软件工程师, IBM 中国软件开发中心
2007 年 8 月 24 日 本文介绍如何使用 Google Web Toolkit(GWT) 和 JSON 开发一个示例 Ajax 应用程序。作者将分别介绍如何在客户端使用 GWT 的 JSON API 来解析和生成 JSON 编码的数据,以及如何在服务器端使用 GWT 的 RemoteServiceServlet 来接受和回复来自客户端的请求,并使用 SOJO 来解析处理 JSON 数据。
Google Web Toolkit 和 JSON 简介
 | |
请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
|
|
Google Web Toolkit(GWT)是一套用来开发 Ajax 程序的工具,它支持开发者使用 Java 代码来创建 Ajax 的应用程序。
JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。它是一种完全和语言无关的文本格式。同时由于 JSON 是 JavaScript 的对象文字符号的子集,所以在 Ajax 开发中,经常用来作为客户端和服务器端数据交换的标准格式。
关于 GWT 和 JSON,developerWorks 已经有了一系列文章进行介绍,本文就不再详细介绍,相关资源请参阅 参考资源。本文将关注如何使用 GWT 这个强大的开发工具包开发使用 JSON 作为数据交换格式的 Ajax 应用程序。
示例程序
本文将介绍如何使用 GWT 和其它开源工具来实现一个可以在页面上动态实时地展示当前服务器端单元测试执行结果的 Ajax 应用程序。其中,将讨论如何使用 GWT 和其它开源工具转换和处理 JSON 数据。
背景介绍
当开发人员提交自己修改的代码到代码服务器时,通常需要做回归测试,用来测试新提交的代码是否能够保证产品质量。测试人员在服务器端事先部署好测试工具、版本控制的代码以及可运行的 TestCases。当代码更新的时候,测试人员启动代码的回归单元测试,希望能够实时地看到服务器端 TestCases 的执行情况。然而,以往的 Web 技术很难达到以上目标,通常只有在全部结果执行完毕,才能一次性的看到全部结果,或者让用户手动地频繁刷新页面来实时的察看服务器端执行结果。
使用 Ajax 技术,可以实现页面实时刷新的动态效果。而以 GWT 作为 Ajax 开发工具,可以快捷方便的协助开发。使用 JSON 作为服务器端和客户端通讯的数据交换格式,可以更清晰地定义和传递相对复杂的结构化数据。
架构
图 1. 系统构架图
客户端通过 GWT 的 UI Component 来构建用户界面。客户端收集用户输入的参数 testCaseNum 和 testSuites ,编码成为 JSON 格式数据,并通过 RequestBuilder 发送到服务器端。得到返回信息以后,解析 JSON 数据,并将得到的测试结果通过 resultsGrid 实时显示出来。
服务器端 RemoteServiceServlet 接收到 JSON 格式数据以后,解析并转换为 RequestForm 对象。服务器获取请求参数,并启动测试程序。当客户端发送获取当前已完成测试结果请求的时候,服务器端读取所有生成的 TestResult 对象,并转换成为 JSON 格式数据返回给客户端。
创建 Unit Test Ajax Application 的 Eclipse 项目
首先下载 Google Web Toolkit(参见 参考资源),目前最新版本为 1.4 RC,本示例代码是基于 GWT 1.4.10 开发的。如果您还没有安装 Eclipse,也请下载最新的 Eclipse(参见 参考资源)。
本文示例将把 gwt-windows-1.4.10.zip 文件解压到 c:\gwt-windows-1.4.10,在该目录下将会有 projectCreator.cmd 和 applicationCreator.cmd 两个文件。然后建立项目目录 c:\workspace\unitTestProject。打开命令窗口,进入到该目录,并运行以下命令来创建 Eclipse 项目所需的项目文件:
projectCreator -eclipse unitTestProject
applicationCreator -eclipse unitTestProject unitTest.client.MainApplication
|
之后就可以在 Eclipse 中导入 GWT 创建的项目 unitTestProject。
为了让 GWT 客户端支持 GWT 提供的 JSON 和 http 模块,需要在项目的 src/unitTest/MainApplication.gwt.xml 文件中加入 JSON 和 http 的模块声明。这样才能在 GWT 中正常使用 com.google.gwt.json.* 以及 com.google.gwt.http.* 包中的 Java 类。清单 1 列出了需要加入的 inherits。
清单 1. MainApplication.gwt.xml 加入的 JSON, HTTP 模块声明
<inherits name='com.google.gwt.json.JSON'/>
<inherits name='com.google.gwt.http.HTTP'/>
|
定义用户界面
现在,我们将开始创建这个 Ajax 示例应用程序,首先将创建用户界面部分,我们将建立一些用于显示的 HTML 页面和样式表,下面将介绍创建过程。
打开 src/unitTest/public/MainApplication.html。在 <style>…</style> 中定义样式。示例程序定义了按钮、状态文字以及测试结果所在单元格的样式,如 清单 2 所示。
清单 2. 按钮、状态条以及测试结果的样式定义
<style>
…
.gwt-Button{font-size: 180%;}
.status{font-size:12px;font-weight:bold;font-family:sans-serif,arial}
.green{background-color:#1FFF1F}
.red{background-color:#FF9999}
</style>
|
测试程序使用一个页面来展示所有元素,包括测试参数的输入界面,运行按钮,当前测试运行状态条和测试结果输出界面。为此,我们定义了如下表格(见 清单 3),并用 id 来标志各个部分界面将会出现的位置。
清单 3. Html 文件里界面元素的定位
<table align=center>
<tr>
<td align=center id="inputSlot"></td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td align=center id="runSlot"></td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td align=center id="statusSlot"></td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td align=center id="outputSlot"></td>
</tr>
</table>
|
在 MainApplication 类中使用 GWT 界面组件创建交互界面。测试参数输入界面,包括 TestCase 的数目以及 TestSuite 列表,使用了 Grid、TextBox 以及 ListBox 来构建页面。
-
运行测试按钮。同时定义测试启动按钮和按钮触发 Listener 类,在 Listener 的 onClick 方法中发送运行测试请求到服务器端。
清单 4. 运行按钮的监听类主要逻辑
formButton.addClickListener( new ClickListener() {
public void onClick(Widget sender) {
// disable button
…
// collect inputs
…
// send request
…
// start timer to get results in interval
…
}
});
//add button to rootPanel
RootPanel.get("runSlot").add(formButton);
|
-
运行状态信息条。实时的展现目前测试运行状态。
-
测试结果实时输出界面。实时展现服务器端的运行测试结果。使用 Grid 来显示为一个可以动态增长的表格,在单元格内部显示测试结果。
客户端 JSON
客户端主要逻辑是收集用户的输入参数,转换为 JSON 格式的数据,最后发送 JSON 数据到服务器端。并同时间歇性的发送请求来获取当前服务器端测试的运行结果,解析服务器端传回来的 JSON 数据,并通过页面展现出来。
本示例程序采用了 GWT 自带的 JSON API。该 API 支持 JSON 数据的解析和转换,可以将客户端用户的输入参数转换为 JSON 格式,并且能够解析从服务器端传来的 JSON 数据。
它所包含的主要 JSON 类有:
- JSONArray:数组
- JSONBoolean:布尔值
- JSONException:JSON 的异常类
- JSONNull:代表 JSON 的 null 值
- JSONNumber:数字
- JSONObject:JSON 对象
- JSONParser:JSON 格式解析器,它可以解析 JSON 编码的字符串,并转换为想对应 GWT 的 JSON Java 对象。
- JSONString:字符串
- JSONValue:所有 JSON Java 对象的基类。其中
toString() 方法可以将该 JSON 对象转换为它对应的 JSON 格式的字符串。
在开发过程中,主要使用 JSONValue 的 toString() 方法来将 JSON Java 对象编码成 JSON 格式的字符串,传到服务器端;使用 JSONParser.parse(String input) 来将服务器端返回的 JSON 格式字符串解析成为 JSON Java 对象。
开发过程如下:
-
在示例程序中收集界面输入参数,并创建 JSONObject inputs。
清单 5. 收集界面中用户输入参数,并转换为 GWT JSON 类
// collect inputs
JSONObject inputs = new JSONObject();
int caseNum = Integer.parseInt(testCaseNumText.getText());
inputs.put("caseNum", new JSONString(testCaseNumText.getText()));
JSONArray suitesArray = new JSONArray();
int j = 0;
for (int i = 0; i < suiteNameListBox.getItemCount(); i++) {
if (suiteNameListBox.isItemSelected(i)) {
suitesArray.set(j, new JSONString(suiteNameListBox
.getValue(i)));
j++;
}
}
inputs.put("testSuites", suitesArray);
|
-
使用 RequestBuilder 来发送 HTTP 请求,将上一步中生成的 JSON 数据 inputs 发送的服务器端。
-
首先建立 RequestBuilder。RequestBuilder 用来建立 HTTP GET 和 POST 请求。创建 RequestBuilder 的时候,可以选择 GET 或 POST 发送方法,同时需要指定要发送到 URL。通常情况下,如果 Server 端代码也是在同一个 GWT 项目中开发,那么 URL 的前缀是 GWT.getModuleBaseURL,再加上 servlet 的 URL 就可以组成完整的 URL。 在本示例程序中,GWT.getModuleBaseURL 的值为 http://localhost:8888/unitTest.MainApplication/。
清单 6 . 建立使用 POST 请求发送 URL 为 http://localhost:8888/unitTest.MainApplication/runTest 的 builder
RequestBuilder builder =
new RequestBuilder(RequestBuilder.POST,
GWT.getModuleBaseURL()+ "/runTest");
|
-
发送客户端请求到服务器端。客户端会发送两种请求信息。
-
请求开始服务器端的测试。请求信息为编码的 JSON 字符串。调用 inputs.toString() 方法,GWT 将把 inputs 对象进行 JSON 编码, 并输出编码后的字符串。
清单 7. 发送 JSON 格式参数并指定使用 StartUnitTestRequestCallback 处理返回信息
builder.sendRequest(inputs.toString(),new StartUnitTestRequestCallback());
|
-
请求获取当前服务器端已经测试完毕的 TestCase 执行结果。请求信息为字符串“getRuntimeResults”。
清单 8. 发送字符串并指定 RetrieveRuntimeResultsRequestCallback 处理返回信息
refreshRequest=
builder.sendRequest("getRuntimeResults",
new RetrieveRuntimeResultsRequestCallback(caseNum, suiteNum, this));
|
客户端发送字符串“getRuntimeResults”到服务器端,服务器端将会返回当前已经测试完毕的 TestCase 的结果。RetrieveRuntimeResultsRequestCallback 会处理返回信息。
客户端会按一定时间间隔,反复发送请求 getRuntimeResults 到服务器。服务器端将实时返回当前测试结果。客户端接收到后实时刷新页面。当客户端得到所有单元测试结果时,停止发送请求,并更新页面到初始状态。
定时器实现继承了 GWT 提供的 Timer。见 清单 9,如何启动 Timer。
清单 9. 使用 UIRefreshTimer 来重复调用函数,时间间隔为 1 秒
Timer t = new UIRefreshTimer(caseNum, suiteNum);
t.scheduleRepeating(1000);
|
运行时,发送的 JSON 编码的请求信息如下:{"caseNum":"3", "testSuites":["TestSuite2","TestSuite3","TestSuite5"]}。
-
在 RequestCallback 的 onResponseReceived() 方法中处理 HTTP 请求返回的信息。示例程序中,定义了 2 种 callback,分别对应客户端 2 种 HTTP 请求。StartUnitTestRequestCallback 根据返回信息来更新界面状态条的文字。RetrieveRuntimeResultsRequestCallback 会处理当前服务器端已经完成的测试程序的结果信息。返回的测试结果信息已经被服务器端编码为 JSON 格式的字符串。RetrieveRuntimeResultsRequestCallback 将在 onResponseReceived() 方法里使用 GWT 的 JSON 工具解析 JSON 字符串并将结果数据显示在页面上。
客户端使用 GWT 的 JSONParser 来解析返回的 JSON 数据。清单 10 给出了例子,如何实现 Callback 的 onResponseReceived 方法,以及如何转换 JSON 格式字符串为 GWT 的 JSON Java 对象并在界面上显示出来。
清单 10. RetrieveRuntimeResultsRequestCallback 的 onResponseReceived 实现
public void onResponseReceived(Request request, Response response) {
if (response.getStatusCode() == 200) {
String respText = response.getText();
//parse JSON encoded string
JSONValue respValue = JSONParser.parse(respText);
JSONArray trArray = respValue.isArray();
if (trArray != null) {
// refresh components
if (trArray.size() == caseNum * suiteNum) {
timer.cancel();
formButton.setEnabled(true);
statusLabel.setText("Test complete !");
formButton.setText("Run Test");
}
// refresh test results ui
resultsGrid.resize(trArray.size(), 4);
for (int i = 0; i < trArray.size(); i++) {
JSONObject tr = trArray.get(i).isObject();
if (tr != null) {
resultsGrid.setWidget(i, 0, new Label(tr
.get("name").isString().stringValue()));
resultsGrid.setWidget(i, 1, new Label(tr.get(
"start").isString().stringValue()));
resultsGrid.setWidget(i, 2, new Label(tr.get("end")
.isString().stringValue()));
resultsGrid.setWidget(i, 3, new Label(tr.get(
"result").isString().stringValue()));
resultsGrid.getCellFormatter().setStyleName(
i,3,z
tr.get("result").isString().stringValue()
.equals("PASS") ? "green" : "red");
}
}
}
} else {
Window.alert("Error occurred - statusCode:"
+ response.getStatusCode());
}
}
|

 |

|
服务器端 JSON
示例程序以 GWT 的 RemoteServiceServlet 类作为服务器端请求响应的 servlet 的基类。并且使用了 SOJO 作为服务器端 JavaBean 和 JSON 格式数据相互转换的工具,来处理 JSON 格式的信息。
模拟测试过程
因为本文提供的程序为示例程序,所以测试过程使用 TestRunner 类来模拟测试运行的过程。当 RunTestServlet 接收到客户端运行测试请求的时候,会创建一个 TestRunner 对象,并调用 TestRunner.start() 来启动测试线程。TestRunner 的方法包括:
- init():初始化模拟测试类。
- run(): 当起动测试工具时,启动 TestRunner 线程,run() 方法被调用。作为模拟测试,在 run() 方法里面会实时的更新测试结果,并记录下测试起止时间,测试结果等数值。
- getTestResults():得到当前测试运行的结果。
清单 12. TestRunner 的模拟测试实现
public class TestRunner extends Thread{
private List testResults;
private int caseNum = 0;
private String[] suiteNames = null;
public void init(int caseNum, String[] suiteNames) {
testResults= new ArrayList();
this.suiteNames = suiteNames;
this.caseNum = caseNum;
}
/**
* invoke the server-side test tool
*/
public void run() {
// simulate the running test tool
for (int j = 0; j < suiteNames.length; j++) {
for (int i = 0; i < caseNum; i++) {
TestResult tr = new TestResult();
tr.setName(suiteNames[j] + "-case-" + i);
tr.setStart(new Date().toString());
try { //simulate the test
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
tr.setEnd(new Date().toString());
tr.setResult(genRandomResult());
testResults.add(tr);
}
}
}
/**
* simulate test results
* @return
*/
private String genRandomResult(){
int result = new Random().nextInt(3);
return result==1?"FAIL":"PASS";
}
/**
* read the dynamic test results (such as result log file)
*/
public List getTestResults(){
return this.testResults;
}
}
|
服务器端 JSON 数据的解析和转换
已经有文章介绍了 JSON 数据到 JSON Java 类的解析转换。在通常开发中,在系统中通常使用各种 JavaBean 来代表系统中的业务对象,如本示例程序中的 JavaBean TestResult 和 RequestForm。如果直接采用以前使用的方法,则需要多做 JSON Java 类和 JavaBean 相互转换的一步。SOJO 则可以解决这个问题,实现 JSON 数据和 JavaBean 的直接转换。
服务器端的业务类如清单13,14。
清单 13. RequestForm 类
public class RequestForm {
private int caseNum =0;
private String[] testSuites = null;
//getter and setter
…
}
|
清单 14. TestResult 类
public class TestResult {
private String name;
private String start;
private String end;
private String result;
//getter and setter
…
}
|
SOJO 作为一个轻量级的数据转换工具,支持 JavaBean 和各种编码的数据格式,如 JSON,进行相互转换。使用 SOJO 的 net.sf.sojo.interchange.json.JsonSerializer 可以轻松的序列/反序列化 JSON 数据和 JavaBean。
其它 JavaBean 和 JSON 数据转换工具:json-lib。同样支持 JavaBean 和 JSON数据之间的解析转换。在这里就不详细介绍了。相对 SOJO 来说,它还支持从 Map 到 JSON 格式的转换,这种方式也非常的方便。
GWT RemoteServiceServlet
-
介绍
GWT 提供的为实现服务器端远程调用服务而提供的 servlet 基类。RemoteServiceServlet 能够自动的反序列化客户端发送的请求信息,也可以序列化返回信息。示例程序中,我们使用 RunTestServlet 来继承 RemoteServiceServlet。
-
Servlet 配置在 GWT 项目中的配置。
在 GWT 项目中,如果需要配置 servlet 映射,在 Module 的配置文件中添加该 servlet 的声明。以示例程序为例,在 src/unitTest/MainApplication.gwt.xml 中添加 <servlet class='unitTest.server.RunTestServlet' path='/runTest'/>。
-
重载 String processCall(String payLoad)实现业务逻辑
用 RunTestServlet 继承 RemoteServiceServlet,并重载 processCall 方法。在 processCall 方法里可以得到当前客户端的请求信息。读取参数 payLoad 里可以得到请求信息文本。当一系列业务处理以后,服务器端返回一个字符串作为此次请求的返回信息。以示例程序为例,服务器端接收到的 payLoad 是:{"caseNum":"3", "testSuites":["TestSuite2","TestSuite3","TestSuite5"]} 。返回的信息为: [{"start":"Fri Jul 13 11:09:55 CST 2007","name":"TestSuite2-case-0","~unique-id~":"0","result":"FAIL","end":"Fri Jul 13 11:09:56 CST 2007","class":"unitTest.entity.TestResult"}] 。清单 11 给出了服务器端 processCall 方法实现代码。
清单 11. RunTestServlet 的 processCall 方法
public String processCall(String payLoad) {
System.out.println("Received payLoad:" + payLoad);
Object result = "";
if (!payLoad.startsWith("getRuntimeResults")) {
//parse the request JSON string into javabean requestForm
RequestForm requestForm = (RequestForm) serializer.deserialize(
payLoad, RequestForm.class);
//start a test runner to simulate the unit test
testRunner = new TestRunner();
testRunner.init(requestForm.getCaseNum(), requestForm
.getTestSuites());
testRunner.start();
}
//get runtime results
if (testRunner != null) {
List results = testRunner.getTestResults();
//transform list of javabeans into JSON string
result = serializer.serialize(results.toArray());
}
System.out.println("Return Result:" + result.toString());
return result.toString();
}
|

 |

|
示例运行结果
选择要运行的 TestSuite,并输入每个 TestSuite 所需要运行的 TestCase 的数目。填写 TestCase Num 为 3,运行的 TestSuite 选择 TestSuite2, TestSuite3, TestSuite5。如 图 2 所示。
图 2. 初始界面
让服务器端运行测试。点击”Run Test”。
浏览器页面将动态显示服务器端测试的结果。当服务器端有 TestCase 被执行完毕,页面会实时展现目前已经执行完毕的 TestCase 的测试结果。如 图 3 所示。
图 3. 最新完成的测试结果实时显示在页面上
当测试结束时,启动按钮和状态恢复到初始状态。如 图 4。
图 4. 完成测试
结束语
本文介绍了如何使用 GWT 和 JSON 来开发 Ajax 应用程序。并分别介绍了如何在客户端使用 GWT 的 JSON API 来解析和生成 JSON 编码的数据。在服务器端,如何使用 GWT 的 RemoteServiceServlet 来接受和回复来自客户端的请求,以及如何使用 SOJO 来解析处理 JSON 数据。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 本文示例应用程序源代码 | unitTestProject.zip | 150 KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | 邹林志是 IBM 中国软件开发中心工程师,在 BTO Asset Lab 工作。目前主要从事国外大中型企业基于 CSDP 的行业解决方案的设计和开发。您可以通过 zoulz@cn.ibm.com 和他联系。 |
对本文的评价
|