gwt 同步和异步
过去几年中,Web应用程序开发的趋势是创建富Internet应用程序,其中大多数是使用异步JavaScript + XML(Ajax)实现的。 但是,由于JavaScript编码的本质,这并不容易。 构建大型Web应用程序特别困难。 这就是GWT发挥作用的地方:它使您可以使用Java编程而不是Ajax构建丰富而响应Swift的Web界面。 GWT还提供了Java开发的所有好处,例如具有高级调试功能的出色IDE支持。 GWT可以极大地提高您的生产力并丰富您的用户体验。 在本文中,我将解释如何在Eclipse中创建GWT应用程序以及如何使用GWT Tree
和TreeItem
小部件在大学大气研究公司(UCAR)创建示例组织结构。 我将说明如何实现延迟加载,如何与RESTful Web服务集成以及如何实现GWT回调和自定义异常。 JSON已用作RESTful Web服务的数据格式。
软件需求
首先,您需要下载以下软件包并根据各自网站上的安装指南进行安装。 (请参阅相关主题的链接。)
- Java EE开发人员的Eclipse IDE Galileo(Eclipse 3.5)
- GWT 2.0
- Eclipse的GWT插件
- MySQL 5.1或DB2®Express-C
- Tomcat 6.x
RESTful Web服务
RESTful Web服务为GWT客户端提供组织数据。 在本文中,我不会讨论演示如何实现RESTful Web服务的步骤。 您需要做的就是设置数据库并将WAR文件部署到Tomcat服务器。 您可能还需要在配置文件中更改一些数据库属性,例如数据库主机,登录名和密码。 RESTful Web服务是使用我在两篇文章“ 用于构建RESTful Web服务的多层体系结构 ”和“使用多层体系结构 构建RESTful Web服务和动态Web应用程序”中讨论的多层体系结构实现的 。
设置数据库
我使用MySQL Community Server 5.1作为本文的数据库。 但是,您也可以使用DB2 Express-C或其他。 使用MySQL,你会发现下载链接相关主题 。 如果尚未下载并安装到您选择的主机上。 然后创建一个名为gwtresttutorial的数据库和一个名为gwtresttutorial的用户,密码为gwtresttutorial。 连接到gwtresttutorial数据库并以gwtresttutorial登录。 从下载中下载sql脚本,然后运行该脚本以创建表并将数据插入表中。
要将RESTful Web服务服务器连接到DB2 Express-C或另一个DB2变量数据库,该配置与清单1中针对MySQL所述的配置非常相似,但有以下更改:
- 使用com.ibm.db2.jcc.DB2Driver作为
driverClassName
。 - 使用jdbc:db2:// <host>:<port> / <database_name>作为URL,其中
host
是安装DB2 Express-C的host
的名称,port
是访问数据库的端口号,而database_name
是数据库实例的名称。 - 将db2jcc.jar和db2jcc_license_cu.jar文件从DB2 Express-C安装目录复制到WEB-INF / lib目录。
- 您可能需要从下载中修改setup.sql脚本以使用DB2语法。
将WAR文件部署到Tomcat服务器
从下载部分下载WAR文件,并将其保存到您的Tomcat文件夹:<TOMCAT_HOME> / webapps,其中TOMCAT_HOME是您的Tomcat服务器的安装位置。 如果您尚未安装Tomcat,则可以从参考资料中下载。
将WAR文件部署到<TOMCAT_HOME> / webapps目录后,如果Tomcat服务器正在运行,它将把WAR文件解压缩到gwtRESTTutorial文件夹中。 检查<TOMCAT_HOME> /webapps/gwtRESTTutorial/WEB-INF/classes/applicationContext.xml(清单1),以确保用于配置dataSource
bean的值与您用于MySQL数据库的值匹配。 请注意,如果进行了任何更改,则可能需要重新启动Tomcat服务器。
清单1.在applicationContext.xml中配置dataSource bean
1. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
2. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
3. <property name="url" value="jdbc:mysql://localhost:3306/gwtresttutorial"/>
4. <property name="username" value="gwtresttutorial"/>
5. <property name="password" value="gwtresttutorial"/>
6. </bean>
访问RESTful Web服务
本文已实现了两个RESTful Web服务。 第一个是提供有关雇员的信息。 访问此Web服务的URI是:http:// localhost:8080 / gwtRESTTutorial / rrh / employees / <EMP_ID>,其中EMP_ID
是员工的ID。 返回包含详细员工数据的JSON字符串。 清单2给出了返回的JSON字符串的示例。
清单2.从员工RESTful Web服务返回的示例JSON数据
1. {
2. "id":20,
3. "firstName":"Robert",
4. "nickName":"Bob",
5. "lastName":"Sunny",
6. "title":"SE",
7. "phone":"303-123-1234",
8. "email":bobs@ucar.edu
9. }
第二个Web服务是提供有关组织单位的信息。 它的URI是http:// localhost:8080 / gwtRESTTutorial / rrh / organizations / <ORG_ID>,其中ORG_ID
是组织单位的ID。 像员工数据一样,将返回一个包含详细组织数据的JSON字符串(清单3)。 详细数据包含组织,各个级别的子组织的ID,首字母缩写词,姓名,销售线索名称,销售线索标题和雇员总数。 它还包含用于组织单位工作的员工的数据数组和用于直接子组织单位的单独的数据数组。 员工数据和子组织数据仅包含ID和显示名称。
清单3.从组织RESTful Web服务返回的示例JSON数据
1. {
2. "id":1,
3. "acronym":"NCAR",
4. "name":"National Center for Atmospheric Research",
5. "leadName":"Dan Bush -Director",
6. "leadTitle":"Director",
7. "totalEmployees":15,
8. "employees":
9. [{
10. "id":2,
11. "displayName":"Dan Bush - Director"
12. },
13. {
14. "id":3,
15. "displayName":"Lori Stanley - Deputy Director"
16. }],
17. "subOrgs":
18. [{
19. "id":3,
20. "displayName":"CISL"
21. },
22. {
23. "id":5,
24. "displayName":"EOL"
25. },
26. {
27. "id":6,
28. " displayName ":"RAL"
29. },
30. {
31. "id":4,
32. "displayName":"ESSL"
33. }]
34. }
在Eclipse中创建GWT应用程序
具有GWT支持的Eclipse是本文中用于开发GWT应用程序的环境。 在Eclipse中:
- 选择文件>新建> Web应用程序项目 。
- 输入
gwtRESTTutorialView
在Project Name字段和edu.ucar.cisl.gwtRESTTutorialView
在新的Web应用程序项目窗口Package字段中(见图1)。 - 选择使用默认SDK,然后在Google SDK中选择GWT-2.0.0或更高版本。
图1.创建新的Web应用程序项目
Eclipse中的GWT插件会自动创建一个示例远程服务。 您可以有选择地删除它,方法是删除edu.ucar.cisl.gwtRESTTutorialView.client程序包中的文件GreetingService.java和GreetingServiceAsync.java以及edu.ucar.cisl.gwtRESTTutorial程序包中的GeetingServiceImpl.java。 您还需要删除远程服务的web.xml文件中的servlet配置,并删除WAR文件夹下GwtRESTTutorialView.html文件中<body>和</ body>之间的所有内容。
以下各节涵盖了有关特定主题的详细信息,例如创建数据Bean,实现RPC代理以访问RESTful Web服务和回调以及构建GWT Web界面。 这些组件位于以下四个程序包中。 (如果它们不存在,请在Eclipse中创建它们。)源代码可从“ 下载”部分下载 。
- edu.ucar.cisl.gwtRESTTutorialView.client.bean —包含客户端的应用程序Java Bean。
- edu.ucar.cisl.gwtRESTTutorialView.client.callback-包含回调类的实现。
- edu.ucar.cisl.gwtRESTTutorialView.client —包含模块条目类
GwtRESTTutorialView
。 它还包含用于创建GWT Web界面的其他几个界面,类和图像文件。 RPC代理的客户端站点类也位于此程序包中。 - edu.ucar.cisl.gwtRESTTutorialView.server —包含RPC代理的服务器端实现的类。
实施应用程序数据Bean
在本文中,我使用Tree
小部件来显示组织结构。 在GWT中, Tree
小部件包含TreeItem
小部件,通常将它们用作树节点。 在这种情况下, TreeItem
小部件用作树节点或树叶来分别代表组织单位和雇员。 我实现了一个抽象基类ItemData
(清单4),它具有三个属性: id
, displayName
和dataReady
。 id
是数据项的ID,用于构建RESTful Web服务请求。 它标识RESTful Web服务服务器中的资源。 属性displayName
是要显示的名称。 属性dataReady
是一个标志,用于指示是否已从RESTful Web服务服务器检索详细数据。 它用于帮助实现延迟加载。 创建TreeItem
小部件时, ItemData
bean与该小部件关联。 它只有资源ID和显示名称。 仅当用户选择树叶或打开树节点时,才加载在子类中声明的详细数据。 抽象方法buildURI
用于构建RESTful Web服务请求的URI,并将由其子类EmployeeItemData
(清单5)和OrganizationItemData
(清单6)实现。 EmployeeItemData
包含EmployeeItemData
的详细信息,而OrganizationItemData
包含组织单位的详细信息。
清单4. edu.ucar.cisl.gwtRESTTutorialView.client.bean.ItemData
1. package edu.ucar.cisl.gwtRESTTutorialView.client.bean;
2. public abstract class ItemData {
3. protected int id = -1;
4. protected String displayName;
5. protected boolean dataReady = false;
6. ...//setters and getters
7. abstract public String buildUri();
8. }
清单5. edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData
1. package edu.ucar.cisl.gwtRESTTutorialView.client.bean;
2. public class EmployeeItemData extends ItemData {
3. protected String firstName;
4. protected String lastName;
5. protected String nickName;
6. protected String phone;
7. protected String email;
8. protected String title;
9. ...//setters and getters
10. public String buildUri(){
11. return "http://localhost:8080/gwtRESTTutorial/rrh/employees/" + id;
12. }
13. }
清单6. edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData
1. package edu.ucar.cisl.gwtRESTTutorialView.client.bean;
2. public class OrganizationItemData extends ItemData {
3. protected String name;
4. protected String leadName;
5. protected String leadTitle;
6. protected int totalEmployees;
7. ...//getters and setters
8. public String buildUri() {
9. return "http://localhost:8080/gwtRESTTutorial/rrh/organizations/" + id;
10. }
11. }
实现RPC代理以请求RESTful Web服务
有几种策略可以将GWT与RESTful Web服务集成。 如果RESTful Web服务服务器在相同的域和端口上运行,那么显而易见的选择是使用GWT RequestBuilder
类。 但是, RequestBuilder
类RequestBuilder
“相同原始策略(SOP)”限制,该限制禁止从其他域向Web服务服务器发出请求。 为了避免SOP限制,我使用RPC代理策略。 使用此策略,GWT客户端将RESTful Web服务请求发送到RPC远程服务,然后RPC远程服务将请求传递到RESTful Web服务服务器。
创建一个自定义异常类
需要特殊的自定义异常,以便服务器可以将异常传递给客户端。 GWT提供了一种非常简单的实现方法。 您需要做的就是让自定义异常类扩展Exception类并实现IsSerializable
接口。 清单7显示了定制异常。
清单7. edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceException
1. package edu.ucar.cisl.gwtRESTTutorialView.client;
2. import com.google.gwt.user.client.rpc.IsSerializable;
3. public class RESTfulWebServiceException extends Exception implements IsSerializable {
4. private static final long serialVersionUID = 1L;
5. private String message;
6. public RESTfulWebServiceException() {
7. }
8. public RESTfulWebServiceException(String message) {
9. super(message);
10. this.message = message;
11. }
12. public RESTfulWebServiceException(Throwable cause) {
13. super(cause);
14. }
15. public RESTfulWebServiceException(String message, Throwable cause) {
16. super(message, cause);
17. this.message = message;
18. }
19. public String getMessage() {
20. return message;
21. }
22. }
创建一个远程服务接口
对于每个远程服务,GWT在客户端需要两个接口:一个远程服务接口和一个远程服务异步接口。 远程服务接口必须扩展GWT RemoteService
接口,并定义将公开给客户端的服务方法的签名。 方法参数和返回类型必须可序列化。
本文的远程服务接口非常简单(清单8)。 它只声明一个方法invokeGetRESTfulWebService
。 该方法具有两个参数uri
和contentType
。 前者是URI,用于标识要在RESTful Web服务服务器上请求的资源。 后者指示返回结果应预期的内容类型。 内容类型将是标准的HTTP内容类型,例如application / json,application / xml,application / text等。 该方法从HTTP响应返回内容字符串,并在失败的情况下引发自定义异常。
需要添加注释RemoteServiceRelativePath
来指定服务的URL路径(清单8中的第5行)。 创建了一个简单的实用程序类,以轻松获取异步远程接口的实例(清单8中的第7-13行)。
清单8. edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceProxy
1. package edu.ucar.cisl.gwtRESTTutorialView.client;
2. import com.google.gwt.core.client.GWT;
3. import com.google.gwt.user.client.rpc.RemoteService;
4. import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
5. @RemoteServiceRelativePath("RESTfulWebServiceProxy")
6. public interface RESTfulWebServiceProxy extends RemoteService {
7. public static class Util {
8. public static RESTfulWebServiceProxyAsync getInstance() {
9. RESTfulWebServiceProxyAsync
10. rs=(RESTfulWebServiceProxyAsync)GWT.create(RESTfulWebServiceProxy.class);
11. return rs;
12. }
13. }
14.
15. public String invokeGetRESTfulWebService(String uri, String contentType)
16. throws RESTfulWebServiceException;
17. }
创建一个远程服务异步接口
远程服务异步接口基于远程服务接口。 服务的异步接口必须在相同的程序包中,并且具有相同的名称,但后缀为“ Async”。 每个远程服务方法都有一个对应的异步方法。 但是异步方法不能具有返回类型,它们必须始终返回void。 异步方法不仅必须以相同顺序声明相同的参数,而且还必须声明泛型类型AsyncCallback<T>
的额外参数,其中T将是远程服务方法的返回类型。 异步方法不会引发异常。 清单9是示例应用程序的远程服务异步接口。
清单9. edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceProxyAsync
1. package edu.ucar.cisl.gwtRESTTutorialView.client;
2. import com.google.gwt.user.client.rpc.AsyncCallback;
3. public interface RESTfulWebServiceProxyAsync {
4. public void invokeGetRESTfulWebService(String uri, String contentType, AsyncCallback<String> callback);
5. }
在服务器上实现代理服务
远程服务在扩展GWT的RemoteServiceServlet
类的服务器端类中实现。 在RESTful Web服务代理(清单10)中,该类实现了远程服务invokeGetRESTfulWebService
。 基于URI和内容类型,此方法构建HTTP请求并将其发送到RESTful Web服务服务器。 如果响应代码为200,它将缓冲HTTP响应中的内容,并将其用作该方法的返回值。 否则,它将引发一个自定义异常。 该方法将捕获其他异常,例如MalformedURLException
和IOException
并抛出自定义异常,以便GWT客户端可以捕获该异常。
清单10. edu.ucar.cisl.gwtRESTTutorialView.server.RESTfulWebServiceProxyImpl
1. package edu.ucar.cisl.gwtRESTTutorialView.server;
2. import java.io.BufferedReader;
3. import java.io.IOException;
4. import java.io.InputStream;
5. import java.io.InputStreamReader;
6. import java.net.HttpURLConnection;
7. import java.net.MalformedURLException;
8. import java.net.URL;
9. import com.google.gwt.user.server.rpc.RemoteServiceServlet;
10. import edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceProxy;
11. import edu.ucar.cisl.gwtRESTTutorialView.client.RESTfulWebServiceException;
12. public class RESTfulWebServiceProxyImpl extends RemoteServiceServlet
13. implements RESTfulWebServiceProxy {
14. private static final long serialVersionUID = 1L;
15. public RESTfulWebServiceProxyImpl() { // must have
16. }
17. public String invokeGetRESTfulWebService(String uri, String contentType)
18. throws RESTfulWebServiceException {
19. try {
20. URL u = new URL(uri);
21. HttpURLConnection uc = (HttpURLConnection) u.openConnection();
22. uc.setRequestProperty("Content-Type", contentType);
23. uc.setRequestMethod("GET");
24. uc.setDoOutput(false);
25. int status = uc.getResponseCode();
26. if (status != 200)
27. throw (new RESTfulWebServiceException("Invalid HTTP response status
28. code " + status + " from web service server."));
29. InputStream in = uc.getInputStream();
30. BufferedReader d = new BufferedReader(new InputStreamReader(in));
31. String buffer = d.readLine();
32. return buffer;
33. }
34. catch (MalformedURLException e) {
35. throw new RESTfulWebServiceException(e.getMessage(), e);
36. }
37. catch (IOException e) {
38. throw new RESTfulWebServiceException(e.getMessage(), e);
39. }
40. }
41. }
实现回调
大多数GWT书籍和在线教程中的回调示例都是作为匿名内部类实现的。 在本文中,我将回调创建为真实类。 这种方法有几个优点。 它使代码更整洁。 它允许客户端数据在运行时与回调类关联。 回调类提供了更大的灵活性,可扩展性和代码重用性。 例如,可以在回调基类中实现错误处理方法,以便所有回调都可以使用该方法来确保所有一致的远程服务异常得到处理。 您可以轻松调试回调类中的代码,因为并非所有的IDE都支持内部类中的跟踪。
在本文中,我创建了一个抽象基类RestServiceRpcCallback
(清单11)以及两个子类EmployeeRpcCallback
(清单12)和OrganizationRpcCallback
(清单13)。 在清单11中,抽象基类实现了一个接口AsyncCallback
。 如果对服务器的请求成功,则将调用onSuccess
方法。 否则,将调用onFailure
方法。 onFailure
方法显示从服务器传递过来的错误消息。 onSuccess
方法将调用processResponse
方法来处理从RESTful Web服务服务器返回的字符串。 抽象方法processResponse
将由子类实现。 抽象基类具有成员treeItem
,该成员将成为GWT中TreeItem
小部件的实例,并且将成为使用回调类时与回调相关联的客户端数据。 该类成员将包含根据TreeItem
小部件表示的内容存储员工数据或组织数据的应用程序对象。 TreeItem
小部件还将用于帮助创建子树和定位弹出窗口。
我创建了一个枚举类型EventType
和类成员eventType
。 该类成员用于跟踪哪个事件触发了对RESTful Web服务服务器的请求,以便在结果从RESTful Web服务服务器返回后,回调将需要它来决定如何继续。
清单11. edu.ucar.cisl.gwtRESTTutorialView.client.callback.RestServiceRpcCallback
1. package edu.ucar.cisl.gwtRESTTutorialView.client.callback;
2. import com.google.gwt.user.client.rpc.AsyncCallback;
3. import com.google.gwt.user.client.ui.TreeItem;
4. import com.google.gwt.user.client.Window;
5. public abstract class RestServiceRpcCallback implements AsyncCallback <String> {
6. TreeItem treeItem;
7. public enum EventType {SELECT_EVENT, STATE_CHANGE_EVENT};
8. protected EventType eventType;
9. public EventType getEventType() {
10. return eventType;
11. }
12. public void setEventType(EventType eventType) {
13. this.eventType = eventType;
14. }
15. public TreeItem getTreeItem() {
16. return treeItem;}
17. public void setTreeItem(TreeItem treeItem) {
18. this.treeItem = treeItem;
19. }
20. public void onSuccess(String result) {
21. if (result == null)
22. return;
23. processResponse(result);
24. }
25. public void onFailure(Throwable caught) {
26. String msg=caught.getMessage();
27. if (msg != null)
28. Window.alert(msg);
29. }
30. protected abstract void processResponse(String response);
31. }
EmployeeRpcCallback
(清单12中的第8至25行)中的processResponse
方法处理从请求返回到RESTful Web服务的字符串。 该字符串包含JSON格式的员工数据。 此方法使用GWT JSON实用程序类解析JSON字符串,并将详细的员工数据存储在应用程序对象EmployeeItemData
,该对象包含在类成员treeItem
。 然后,它将dataReady
标志设置为true,以指示下次用户单击该节点时,无需从RESTfull Web服务请求员工数据。 最后,该方法将打开一个弹出窗口,以显示该员工的详细信息。
清单12. edu.ucar.cisl.gwtRESTTutorialView.client.callback.EmployeeRpcCallback
1. package edu.ucar.cisl.gwtRESTTutorialView.client.callback;
2. import com.google.gwt.json.client.JSONObject;
3. import com.google.gwt.json.client.JSONParser;
4. import com.google.gwt.json.client.JSONValue;
5. import edu.ucar.cisl.gwtRESTTutorialView.client.EmployeePopup;
6. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData;
7. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.ItemData;
8. public class EmployeeRpcCallback extends RestServiceRpcCallback {
9. protected void processResponse(String response) {
10. JSONValue jsonValue = JSONParser.parse(response);
11. ItemData iData = (ItemData) treeItem.getUserObject();
12. JSONObject jobj = jsonValue.isObject();
13. EmployeeItemData eItemData = (EmployeeItemData) iData;
14. eItemData.setId((int) jobj.get("id").isNumber().doubleValue());
15. eItemData.setFirstName(jobj.get("firstName").isString().stringValue());
16. eItemData.setNickName(jobj.get("nickName").isString().stringValue());
17. eItemData.setLastName(jobj.get("lastName").isString().stringValue());
18. eItemData.setPhone(jobj.get("phone").isString().stringValue());
19. eItemData.setEmail(jobj.get("email").isString().stringValue());
20. eItemData.setTitle(jobj.get("title").isString().stringValue());
21. iData.setDataReady(true);
22. int left = treeItem.getAbsoluteLeft() + 50;
23. int top = treeItem.getAbsoluteTop() + 30;
24. EmployeePopup.show(left, top, (EmployeeItemData) eItemData);
25. }
26. }
OrganizationRpcCallback
(清单13中的第10 – 31行)中的processResponse
方法处理从RESTful Web服务服务器返回的组织数据。 像员工数据一样,组织数据也作为JSON字符串返回。 组织数据包含有关组织单位的详细信息,以及有关组织单位和直接子组织中员工的一些信息。 该方法使用GWT JSON实用程序类解析JSON字符串,并将详细的组织数据存储在类成员treeItem
包含的应用程序对象OrganizationItemData
中。 然后,它将dataReady
标志设置为true,这表示详细的组织数据已在内存中。 该方法将调用processEmployees
方法为组织单位内的雇员处理数据,并调用processSubOrgs
为其子组织处理数据。 最后,如果事件为Select
,它将打开一个弹出窗口以显示详细的组织信息,例如全名,领导者姓名和职务以及雇员总数,包括在其所有子组织中工作的雇员。
processEmployees
方法(第44 – 54行)处理员工数据的JSON数组。 它为每个雇员提取id
和displayName
,创建一个应用程序对象EmployeeItemData
,创建一个TreeItem
小部件,并将该应用程序对象与该小部件绑定。
processSubOrgs
方法(第32 – 43行)处理JSON数组中的每个子组织。 它提取id
和displayName
,并将它们存储在应用程序对象OrganizationItemData
。 然后,它创建一个TreeItem
小部件,并将应用程序对象与该小部件绑定。 众所周知,在桌面文件管理器应用程序中,无论是否为空,都可以拥有一个文件夹。 但是在GWT中,不支持此操作。 作为惰性加载策略的一部分,创建组织TreeItem
小部件时,您没有创建其所有子小部件的数据。 但是,您需要使小部件看起来像组织(树节点),而不像雇员(树叶)。 为了解决此限制,我创建了一个虚拟子TreeItem
小部件并将其设置为不可见(第39、40行)。
清单13. edu.ucar.cisl.gwtRESTTutorialView.client.callback.OrganizationRpcCallback
1. package edu.ucar.cisl.gwtRESTTutorialView.client.callback;
2. import com.google.gwt.json.client.JSONArray;
3. import com.google.gwt.json.client.JSONObject;
4. import com.google.gwt.json.client.JSONParser;
5. import com.google.gwt.json.client.JSONValue;
6. import com.google.gwt.user.client.ui.TreeItem;
7. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData;
8. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData;
9. import edu.ucar.cisl.gwtRESTTutorialView.client.OrganizationPopup;
10. public class OrganizationRpcCallback extends RestServiceRpcCallback {
11. protected void processResponse(String response) {
12. JSONValue jsonValue = JSONParser.parse(response);
13. OrganizationItemData oItemData = (OrganizationItemData) treeItem.getUserObject();
14. JSONObject jobj = jsonValue.isObject();
15. oItemData.setId((int) jobj.get("id").isNumber().doubleValue());
16. oItemData.setDisplayName(jobj.get("acronym").isString().stringValue());
17. oItemData.setName(jobj.get("name").isString().stringValue());
18. oItemData.setLeadName(jobj.get("leadName").isString().stringValue());
19. oItemData.setLeadTitle(jobj.get("leadTitle").isString().stringValue());
20. oItemData.setTotalEmployees((int)
21. obj.get("totalEmployees").isNumber().doubleValue());
22. oItemData.setDataReady(true);
23. treeItem.setText(oItemData.getDisplayName());
24. processEmployees(jobj.get("employees").isArray());
25. processSubOrgs(jobj.get("subOrgs").isArray());
26. if (getEventType() == EventType.SELECT_EVENT) {
27. int left = treeItem.getAbsoluteLeft() + 50;
28. int top = treeItem.getAbsoluteTop() + 30;
29. OrganizationPopup.show(left, top, (OrganizationItemData) oItemData);
30. }
31. }
32. protected void processSubOrgs(JSONArray jsonArray) {
33. for (int i = 0; i < jsonArray.size(); ++i) {
34. JSONObject jo = jsonArray.get(i).isObject();
35. OrganizationItemData iData = new OrganizationItemData();
36. iData.setId((int) jo.get("id").isNumber().doubleValue());
37. iData.setDisplayName(jo.get("acronym").isString().stringValue());
38. TreeItem child = treeItem.addItem(iData.getDisplayName());
39. TreeItem dummy = child.addItem("");
40. dummy.setVisible(false);
41. child.setUserObject(iData);
42. }
43. }
44. protected void processEmployees(JSONArray jsonArray) {
45. for (int i = 0; i < jsonArray.size(); ++i) {
46. JSONObject jo = jsonArray.get(i).isObject();
47. EmployeeItemData eData = new EmployeeItemData();
48. eData.setId((int) jo.get("id").isNumber().doubleValue());
49. eData.setDisplayName(jo.get("name").isString().stringValue());
50. eData.setDataReady(false);
51. TreeItem child = treeItem.addItem(eData.getDisplayName());
52. child.setUserObject(eData);
53. }
54. }
55. }
因为GWT JSON库用于解析JSON字符串,所以您需要将其包括在GWT模块配置文件中(清单14)。 该文件还声明了模块的入口点类(第6行)。 该文件位于edu.ucar.cisl.gwtRESTTutorialView包中。
清单14. GwtRESTTutorialView.gwt.xml
1. <?xml version="1.0" encoding="UTF-8"?>
2. <module rename-to='gwtresttutorialview'>
3. <inherits name='com.google.gwt.user.User'/>
4. <inherits name="com.google.gwt.json.JSON"/>
5. <inherits name='com.google.gwt.user.theme.standard.Standard'/>
6. <entry-point class='edu.ucar.cisl.gwtRESTTutorialView.client.GwtRESTTutorialView'/>
7. <source path='client'/>
8. </module>
在web.xml文件中声明RESTful Web服务代理
从技术上讲,RPC远程服务是一个servlet。 您需要做的就是在web.xml文件中配置servlet,就像配置其他servlet一样(清单15)。
清单15. Web.xml的一部分,用于声明RESTful Web服务代理远程服务
1. <servlet>
2. <servlet-name>RESTfulWebServiceServlet</servlet-name>
3. <servlet-class>
4. edu.ucar.cisl.gwtRESTTutorialView.server.RESTfulWebServiceProxyImpl
5. </servlet-class>
6. </servlet>
7. <servlet-mapping>
8. <servlet-name>RESTfulWebServiceServlet</servlet-name>
9. <url-pattern>/gwtresttutorialview/RESTfulWebServiceProxy</url-pattern>
10. </servlet-mapping>
实施GWT客户端界面
创建一个主窗口
清单16列出了模块的入口点类。 此类必须实现EntryPoint
接口。 onModuleLoad
方法是模块加载后要执行的第一个方法。 该类还实现SelectionHandler<TreeItem>
和OpenHandler<TreeItem>
接口,以处理树节点选择和打开事件。 在早期版本中,GWT提供了许多事件侦听器接口。 但是,自1.6版本以来,它们已被事件处理程序取代。
方法onModuleLoad
实例化一个Tree
小部件和一个TreeItem
小部件作为Tree
小部件的根,以表示组织的最高级别。 创建了一个应用程序对象OrganizationItemData
,并将其与根TreeItem
关联。 对象的id
设置为1,并且可以设置为组织的任何级别作为起点。 因为根节点是用来代表组织而不是员工,所以它需要表现得很像树形节点,并且可以打开。 当前,GWT TreeItem
小部件不提供此功能。 解决方法是,创建一个虚拟TreeItem
作为根的子级,并将该虚拟TreeItem
设置为不可见。 现在,当将根TreeItem
的状态设置为open(第35行)时,将启动Open
事件,并onOpen
方法以创建第一层组织结构,包括雇员和子组织的列表。 Tree
Widget被添加到RootPanel
, RootPanel
是GWT应用程序中所有Widget的顶部容器。
当用户选择雇员TreeItem
或组织TreeItem
小部件时,将调用Tree
小部件事件处理程序方法onSelection
(第38-51行)。 它从小部件检索应用程序项目数据,并打开一个弹出窗口以显示详细数据(如果已加载数据)。 否则,它将调用invokeRESTfulWebService
将请求发送到代理服务器。 下一部分将讨论后一种方法。
当用户打开组织TreeItem
小部件时,将onOpen
另一个Tree
小部件事件处理程序方法onOpen
(第53-60行)。 如果组织的详细数据(包括员工数据和直接子组织数据)不可用,则此方法(如onSelection
)将调用invokeRESTfulWebService
来向代理服务器发送请求。
清单16. edu.ucar.cisl.gwtRESTTutorialView.client。 GwtRESTTutorialView
1. package edu.ucar.cisl.gwtRESTTutorialView.client;
2. import com.google.gwt.core.client.EntryPoint;
3. import com.google.gwt.core.client.GWT;
4. import com.google.gwt.event.logical.shared.OpenEvent;
5. import com.google.gwt.event.logical.shared.OpenHandler;
6. import com.google.gwt.event.logical.shared.SelectionEvent;
7. import com.google.gwt.event.logical.shared.SelectionHandler;
8. import com.google.gwt.user.client.ui.RootPanel;
9. import com.google.gwt.user.client.ui.Tree;
10. import com.google.gwt.user.client.ui.TreeItem;
11. import com.google.gwt.user.client.ui.Tree.Resources;
12. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData;
13. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.ItemData;
14. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData;
15. import edu.ucar.cisl.gwtRESTTutorialView.client.callback.EmployeeRpcCallback;
16. import edu.ucar.cisl.gwtRESTTutorialView.client.callback.OrganizationRpcCallback;
17. import edu.ucar.cisl.gwtRESTTutorialView.client.callback.RestServiceRpcCallback;
18. /**Entry point classes define <code>onModuleLoad()</code>.
19. */
20. public class GwtRESTTutorialView implements EntryPoint,
21. SelectionHandler<TreeItem>, OpenHandler<TreeItem> {
22. final static String contentType="application/json";
23. public void onModuleLoad() {
24. TreeItem root = new TreeItem("Root");
25. ItemData iData = new OrganizationItemData();
26. iData.setId(1);
27. root.setUserObject(iData);
28. TreeItem dummyItem = root.addItem("");
29. dummyItem.setVisible(false);
30. Tree tree = new Tree((Resources) GWT.create(OrgTreeResource.class), true);
31. tree.addItem(root);
32. tree.addSelectionHandler(this);
33. tree.addOpenHandler(this);
34. RootPanel.get().add(tree);
35. root.setState(true, true);
36. }
37. @Override
38. public void onSelection(SelectionEvent<TreeItem> event) {
39. TreeItem item=event.getSelectedItem();
40. ItemData iData = (ItemData) item.getUserObject();
41. if (iData.isDataReady()) {
42. int left = item.getAbsoluteLeft() + 50;
43. int top = item.getAbsoluteTop() + 30;
44. if (iData instanceof EmployeeItemData)
45. EmployeePopup.show(left, top, (EmployeeItemData) iData);
46. else
47. OrganizationPopup.show(left, top, (OrganizationItemData) iData);
48. } else
49. invokeRESTfulWebService(item,
50. RestServiceRpcCallback.EventType.SELECT_EVENT);
51. }
52. @Override
53. public void onOpen(OpenEvent<TreeItem> event) {
54. TreeItem item = event.getTarget();
55. ItemData iData = (ItemData) item.getUserObject();
56. if (!iData.isDataReady()) {
57. invokeRESTfulWebService(item,
58. RestServiceRpcCallback.EventType.STATE_CHANGE_EVENT);
59. }
60. }
61. protected void invokeRESTfulWebService(TreeItem item,
62. RestServiceRpcCallback.EventType eventType) {
63. ItemData iData = (ItemData) item.getUserObject();
64. RestServiceRpcCallback callback = null;
65. if (iData instanceof EmployeeItemData)
66. callback = new EmployeeRpcCallback();
67. if (iData instanceof OrganizationItemData)
68. callback = new OrganizationRpcCallback();
69. callback.setEventType(eventType);
70. callback.setTreeItem(item);
71. RESTfulWebServiceProxyAsync ls = RESTfulWebServiceProxy.Util.getInstance();
72. ls.invokeGetRESTfulWebService(iData.buildUri(), contentType, callback);
73. }
74. }
将RESTful Web服务请求发送到RPC代理服务器
方法invokeRESTfulWebService
(第61–73行)使用RPC服务将RESTful Web服务请求发送到代理服务器。 它首先从TreeItem
小部件检索应用程序项目数据,并根据应用程序项目数据的性质实例化EmployeeRpcCallback
或OrganizationItemData
的回调实例。 然后,它将TreeItem
小部件和事件类型与回调实例相关联,以便知道返回RESTful Web服务的数据后如何继续。
根据GWT的要求,在调用远程服务之前,必须创建异步远程接口的实例并将其用于调用远程服务,并在远程服务中声明所有参数以及回调类的实例。 因为远程服务调用是异步的并且是非阻塞的,所以GWT客户端不会等待服务的响应。 它继续执行,直到收到来自远程服务器的异步回调为止。 回调通知GWT应用程序远程服务调用是否已成功执行。 如果远程服务成功,则将调用onSuccess
方法。 否则,将使用Throwable
实例调用onFailure
方法,该实例包含从服务器传递的自定义异常。 回调类将处理从服务器返回的数据。
创建自定义树图像
为GWT Tree
小部件自定义树图像非常容易。 您只需要创建一个扩展Tree.Resource
接口的自定义接口,然后重新声明treeOpen
, treeClosed
和treeLeaf
方法(清单17)。 然后使用GWT.create
来实例化新的接口的实例,并将其传递给Tree
当微件构造Tree
创建插件(清单16,第30行)。 需要将三个分别以treeOpen,treeClosed和treeLeaf开头的图像文件放置在同一文件夹中。
清单17. edu.ucar.cisl.gwtRESTTutorialView.client.OrgTreeResource
1. package edu.ucar.cisl.gwtRESTTutorialView.client;
2. import com.google.gwt.resources.client.ImageResource;
3. import com.google.gwt.user.client.ui.Tree.Resources;
4. public interface OrgTreeResource extends Resources {
5. ImageResource treeOpen();
6. ImageResource treeClosed();
7. ImageResource treeLeaf();
8. }
实施弹出窗口
创建了两个弹出窗口,以显示有关雇员和组织单位的详细信息。 清单18列出了雇员弹出窗口的实现。 该类扩展了GWT PopupPanel
小部件。 它作为单例类实现。 它使用六对“ Label
小部件来显示名字,昵称,姓氏,标题,电话和电子邮件的标签和值。 Grid
小部件用于处理Label
小部件的布局。 要显示详细的员工数据,您需要做的就是调用静态方法show
并根据其所指向的小部件的左右偏移量传递位置。 在这种情况下,参考小部件是用户选择的TreeItem
小部件。 组织弹出窗口的实现与此类似(清单19)。
清单18. edu.ucar.cisl.gwtRESTTutorialView.client.EmployeePopup
1. package edu.ucar.cisl.gwtRESTTutorialView.client;
2. import com.google.gwt.user.client.ui.Grid;
3. import com.google.gwt.user.client.ui.Label;
4. import com.google.gwt.user.client.ui.PopupPanel;
5. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.EmployeeItemData;
6. public class EmployeePopup extends PopupPanel {
7. static protected EmployeePopup instance=null;
8. protected Grid grid = new Grid(6, 2);
9. protected Label firstNameLabel = new Label("First Name");
10. protected Label firstNameValueLabel = new Label("First Name");
11. protected Label nickNameLabel = new Label("Nickname");
12. protected Label nickNameValueLabel = new Label("Nick Name");
13. protected Label lastNameLabel = new Label("Last Name");
14. protected Label lastNameValueLabel = new Label("Last Name");
15. protected Label titleLabel = new Label("Title");
16. protected Label titleValueLabel = new Label("Title");
17. protected Label phoneLabel = new Label("Phone Number");
18. protected Label phoneValueLabel = new Label("Phone Number");
19. protected Label emailNameLabel = new Label("Email");
20. protected Label emailValueLabel = new Label("Email");
21. protected EmployeePopup() {
22. super(true);
23. grid.setWidget(0, 0, firstNameLabel);
24. grid.setWidget(0, 1, firstNameValueLabel);
25. grid.setWidget(1, 0, nickNameLabel);
26. grid.setWidget(1, 1, nickNameValueLabel);
27. grid.setWidget(2, 0, lastNameLabel);
28. grid.setWidget(2, 1, lastNameValueLabel);
29. grid.setWidget(3, 0, titleLabel);
30. grid.setWidget(3, 1, titleValueLabel);
31. grid.setWidget(4, 0, phoneLabel);
32. grid.setWidget(4, 1, phoneValueLabel);
33. grid.setWidget(5, 0, emailNameLabel);
34. grid.setWidget(5, 1, emailValueLabel);
35. grid.setWidth("300px");
36. // grid.setHeight("400px");
37. setWidget(grid);
38. }
39. public void setEmployeeData(EmployeeItemData iData) {
40. String firstName = iData.getFirstName();
41. String lastName = iData.getLastName();
42. String nickName = iData.getNickName();
43. String phone = iData.getPhone();
44. String email = iData.getEmail();
45. String title = iData.getTitle();
46. firstNameValueLabel.setText(firstName);
47. if (nickName != null && nickName.length() > 0) {
48. nickNameValueLabel.setVisible(true);
49. nickNameLabel.setVisible(true);
50. nickNameValueLabel.setText(nickName);
51. }
52. else {
53. nickNameValueLabel.setVisible(false);
54. nickNameLabel.setVisible(false);
55. }
56. lastNameValueLabel.setText(lastName);
57. phoneValueLabel.setText(phone);
58. emailValueLabel.setText(email);
59. titleValueLabel.setText(title);
60. }
61. protected static EmployeePopup getInstance() {
62. if (instance == null)
63. instance = new EmployeePopup();
64. return instance;
65. }
66. public static void show(int leftOffset, int topOffset, EmployeeItemData eData) {
67. EmployeePopup popup = getInstance();
68. popup.setEmployeeData(eData);
69. popup.setPopupPosition(leftOffset, topOffset);
70. popup.show();
71. }
72. }
清单19. edu.ucar.cisl.gwtRESTTutorialView.client.OrganizationPopup
1. package edu.ucar.cisl.gwtRESTTutorialView.client;
2. import com.google.gwt.user.client.ui.Grid;
3. import com.google.gwt.user.client.ui.Label;
4. import com.google.gwt.user.client.ui.PopupPanel;
5. import edu.ucar.cisl.gwtRESTTutorialView.client.bean.OrganizationItemData;
6. public class OrganizationPopup extends PopupPanel {
7. static protected OrganizationPopup instance=null;
8. protected Grid grid = new Grid(3, 2);
9. protected Label nameLabel = new Label("Full Name");
10. protected Label nameValueLabel = new Label("Full Name");
11. protected Label leadNameLabel = new Label("Lead");
12. protected Label leadNameValueLabel = new Label("Lead Name");
13. protected Label totalEmployeesLabel = new Label("Total Employees");
14. protected Label totalEmployeesValueLabel = new Label("Total Employees");
15. public OrganizationPopup() {
16. super(true);
17. grid.setWidget(0, 0, nameLabel);
18. grid.setWidget(0, 1, nameValueLabel);
19. grid.setWidget(1, 0, leadNameLabel);
20. grid.setWidget(1, 1, leadNameValueLabel);
21. grid.setWidget(2, 0, totalEmployeesLabel);
22. grid.setWidget(2, 1, totalEmployeesValueLabel);
23. grid.setWidth("700px");
24. setWidget(grid);
25. }
26. public void setOrganizationData(OrganizationItemData iData) {
27. nameValueLabel.setText(iData.getName());
28. leadNameValueLabel.setText(iData.getLeadName());
29. totalEmployeesValueLabel.setText(new
30. Integer(iData.getTotalEmployees()).toString());
31. }
32. protected static OrganizationPopup getInstance() {
33. if (instance == null)
34. instance = new OrganizationPopup();
35. return instance;
36. }
37. public static void show(int leftOffset, int topOffset,OrganizationItemData oData) {
38. OrganizationPopup popup = getInstance();
39. popup.setOrganizationData(oData);
40. popup.setPopupPosition(leftOffset, topOffset);
41. popup.show();
42. }
43. }
放在一起
在实现所有类之后,您应该在Eclipse中的项目的src文件夹下具有以下文件夹和文件(图2)。
图2. Eclipse中的Project文件夹
要运行它,请在Project Explorer中右键单击项目名称,选择Run As> Web Application或Debug As> Web Application 。 从“ 开发人员模式”窗口复制URL,然后将其粘贴到您喜欢的浏览器中。 该应用程序应如图3所示。
图3.浏览器中的组织树应用程序
结论
GWT可以帮助Java开发人员构建功能丰富且响应Swift的类似于桌面的应用程序,尤其是大型Web应用程序。 在本文中,我演示了如何使用GWT树小部件来显示公司的组织结构。 我使用RPC代理与RESTful Web服务集成。 JSON是RESTful Web服务使用的数据格式。 仅在需要时才加载组织数据和员工数据,并且动态创建树节点(组织)和叶子(员工)。 回调被实现为真实类,以帮助促进代码重用并在运行时与客户端数据相关联。 树形图像经过自定义以显示组织和员工,弹出窗口用于显示组织和员工的详细信息。
根据国家科学基金会与大学大气研究公司的合作协议,该研究的实现是由国家科学基金会提供了部分支持。 国家大气研究中心由国家科学基金会赞助。
翻译自: https://www.ibm.com/developerworks/web/library/wa-aj-gmt/index.html
gwt 同步和异步