- 浏览: 32608 次
- 性别:
- 来自: 南京
文章分类
最新评论
-
ccx410:
安装gwt报错,unable to retrieve osgi ...
http://code.google.com/webtoolkit/doc/latest/DevGuideLogging.html
使用 GWT 和 RESTful Web 服务构建动态的组织树
最近几年,Web 应用程序开发的潮流是创建富 Internet 应用程序,其中大多数是使用 Asynchronous JavaScript + XML (Ajax) 实现的。但是,由于编写 JavaScript 代码比较复杂,这种方法并不容易,尤其是很难构建大型 Web 应用程序。因此出现了 GWT:它让我们能够使用 Java 编程而不是 Ajax 构建功能丰富的响应性的 Web 界面。GWT 还提供 Java 开发的所有优势,比如出色的 IDE 支持和高级调试功能。GWT 可以显著提高生产力并丰富用户的体验。在本文中,我要解释如何在 Eclipse 中构建 GWT 应用程序,以及如何使用 GWT Tree
和 TreeItem
部件为 University Corporation of Atmosphere Research (UCAR) 创建一个示例组织结构。我要解释如何实现惰性装载、如何与 RESTful Web 服务集成以及如何实现 GWT 回调和定制的异常。使用 JSON 作为 RESTful Web 服务的数据格式。
首先,需要下载以下软件包,按照相应网站上的安装指南安装它们(见 参考资料 中的链接)。
- Eclipse IDE for Java EE Developers Galileo (Eclipse 3.5)
- GWT 2.0
- GWT plug-in for Eclipse
- MySQL 5.1 或 DB2® Express-C
- Tomcat 6.x
RESTful Web 服务为 GWT 客户机提供组织数据。在本文中,我不讨论实现 RESTful Web 服务的步骤;您只需设置数据库并把 WAR 文件部署到 Tomcat 服务器上。可能还需要在配置文件中修改几个数据库属性,比如数据库主机、登录名和密码。RESTful Web 服务是使用多层架构实现的,我在两篇文章 “用于构建 RESTful Web 服务的多层架构” 和 “使用多层体系结构构建 REST 风格的 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 的主机的名称,port
是用于访问数据库的端口号,database_name
是数据库实例的名称。 - 把 DB2 Express-C 安装目录中的 db2jcc.jar 和 db2jcc_license_cu.jar 文件复制到 WEB-INF/lib 目录。
- 可能需要修改下载的 setup.sql 脚本,改用 DB2 语法。
从 下载 部分下载 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 服务。第一个提供特定职员的信息。访问这个 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. } |
本文使用具有 GWT 支持的 Eclipse 作为开发 GWT 应用程序的环境。在 Eclipse 中:
- 选择 File > New > Web Application Project。
- 在 New Web Application Project 窗口中的 Project Name 框中输入
gwtRESTTutorialView
,在 Package 框中输入edu.ucar.cisl.gwtRESTTutorialView
(见图 1)。 - 选择 Use Default SDK 并在 Google SDKs 中选择 GWT-2.0.0 或更新的版本。
Eclipse 中的 GWT 插件自动地创建一个示例远程服务。可以通过删除 edu.ucar.cisl.gwtRESTTutorialView.client 包中的 GreetingService.java 和 GreetingServiceAsync.java 文件以及 edu.ucar.cisl.gwtRESTTutorialView.server 包中的 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 代理的服务器端实现的类。
在本文中,我使用 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
包含职员的详细信息,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. } |
有几种集成 GWT 和 RESTful Web 服务的策略。如果 RESTful Web 服务在相同的域和端口上运行,明显的方法是使用 GWT RequestBuilder
类。但是,RequestBuilder
类无法克服 Same Original Policy (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
。前者是标识 RESTful Web 服务服务器上要请求的资源的 URI。后者表示应该返回的结果的内容类型。内容类型是标准 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
中的 processResponse
方法(清单 12 中的第 8 - 25 行)处理 RESTful Web 服务服务器返回的字符串。这个字符串包含 JSON 格式的职员数据。这个方法使用 GWT JSON 实用程序类解析 JSON 字符串,把详细的职员数据存储在应用程序对象 EmployeeItemData
中(这个对象包含在类成员 treeItem
中)。然后,它把 dataReady
标志设置为 true,表示在用户下一次单击这个节点时不需要向 RESTful 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
中的 processResponse
方法(清单 13 中的第 10 – 31 行)处理 RESTful Web 服务服务器返回的组织数据。与职员数据一样,返回的组织数据也是 JSON 字符串。组织数据包含组织单元的详细信息,以及这个组织单元中的职员和直接子组织的部分信息。这个方法使用 GWT JSON 实用程序类解析 JSON 字符串,把详细的组织数据存储在应用程序对象 OrganizationItemData
中(这个对象包含在类成员 treeItem
中)。然后,它把 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。必须像其他 servlet 一样在 web.xml 文件中配置这个 servlet(清单 15)。
清单 15. 声明 RESTful Web 服务代理远程服务的 web.xml 片段
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> |
清单 16 列出模块的入口点类。这个类必须实现 EntryPoint
接口。onModuleLoad
方法是装载模块之后执行的第一个方法。这个类还实现 SelectionHandler<TreeItem>
和 OpenHandler<TreeItem>
接口,从而处理树节点选择和打开事件。在以前的版本中,GWT 提供许多事件监听器接口。但是,从 1.6 版开始,它们已经被事件处理器替代了。
onModuleLoad
方法实例化一个 Tree
部件和一个 TreeItem
部件,后者作为树部件的根,表示组织的最高层。创建应用程序对象 OrganizationItemData
并与根 TreeItem
相关联。这个对象的 id
设置为 1,可以设置为作为起点的任何组织层。因为根节点代表组织而不是职员,所以它的表现和外观应该像树节点一样,是可以打开的。当前,GWT TreeItem
部件没有提供这种功能。为了解决这个问题,我创建了一个假的 TreeItem
作为根的子节点并把它设置为不可见。现在,当根 TreeItem
的状态被设置为 open 时(第 35 行),启动 Open
事件并调用 onOpen
方法,这会创建组织结构的第一层,包括职员和子组织的列表。把 Tree
部件添加到 RootPanel
(GWT 应用程序中所有部件的最高层容器)中。
当用户选择职员 TreeItem
或组织 TreeItem
部件时,调用 Tree
部件事件处理器方法 onSelection
(第 38-51 行)。它从部件获取应用程序条目数据,如果数据已经装载了,它会打开一个弹出窗口以显示详细数据。否则,它调用 invokeRESTfulWebService
向代理服务器发送请求。下一节讨论后一个方法。
当用户打开组织 TreeItem
部件时,调用另一个 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. } |
向 RPC 代理服务器发送 RESTful Web 服务请求
invokeRESTfulWebService
方法(第 61–73 行)使用 RPC 服务向代理服务器发送 RESTful Web 服务请求。它先从 TreeItem
部件获取应用程序条目数据,然后根据应用程序条目数据的性质,实例化 EmployeeRpcCallback
或 OrganizationItemData
的回调实例。然后,把 TreeItem
部件和事件类型与这个回调实例关联起来,让它知道在 RESTful Web 服务返回数据之后如何处理数据。
按照 GWT 的要求,在调用远程服务之前,必须创建异步远程接口的实例并使用它调用远程服务,要使用远程服务中声明的所有参数和回调类的实例。因为远程服务调用是异步的、非阻塞的,所以 GWT 客户机并不等待服务的响应。它继续执行,直到从远程服务器收到异步回调。回调告诉 GWT 应用程序远程服务调用是否成功地执行了。如果远程服务调用成功,就调用 onSuccess
方法。否则,用 Throwable
的实例调用 onFailure
方法,Throwable
的实例包含从服务器传递来的定制异常。回调类处理服务器返回的数据。
定制 GWT Tree
部件的树图像非常容易。只需创建一个扩展 Tree.Resource
接口的定制接口,重新声明 treeOpen
、treeClosed
和 treeLeaf
方法(清单 17)。然后,在创建 Tree
部件时,使用 GWT.create
实例化这个新接口的实例并把它传递给 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)。
在 Project Explore 中右键单击项目名,选择 Run As > Web Application or Debug As > Web Application 运行它。复制 Developer Mode 窗口中的 URL 并把它粘贴到您喜欢的浏览器中。这个应用程序看起来应该像图 3 这样。
GWT 可以帮助 Java 开发人员构建功能丰富、响应性的桌面风格应用程序,尤其是大型 Web 应用程序。在本文中,我演示了如何使用 GWT Tree 部件显示公司的组织结构。我使用一个 RPC 代理与 RESTful Web 服务集成。RESTful Web 服务使用 JSON 作为数据格式。只在需要时装载组织数据和职员数据,动态地创建树节点(组织)和树叶(职员)。将回调实现为真正的类,从而促进代码重用并允许在运行时与客户机数据关联。使用定制的树图像显示组织和职员,使用弹出窗口显示组织和职员的详细信息。
本文基于由 National Science Foundation 支持的部分研究工作,这些工作基于它与 University Corporation for Atmospheric Research 之间的合作协议。National Center for Atmospheric Research 是由 National Science Foundation 发起成立的。
- 学习资料.rar (401.8 KB)
- 下载次数: 6
相关推荐
本书《Pro Web 2.0 Application Development with GWT》由Jeff Dwyer撰写,旨在通过详细的案例分析和技术指南,帮助读者掌握如何使用GWT构建高质量的Web 2.0应用。 #### 二、GWT简介 Google Web Toolkit(简称GWT...
你将学习如何使用GWT的布局管理器,如CellWidget、FlexTable和DockLayoutPanel,来更好地组织和控制用户界面元素。此外,会探讨异步通信的进一步应用,如使用RequestBuilder进行RESTful通信,以及GWT的本地存储和...
3. **GWT RAPID-UI**:GWT的Rapid-UI技术,如Cell Widgets,使开发者能快速构建动态表格和列表,处理大量数据。 4. **GWT PlaceManager**:用于实现页面导航和状态管理,类似于传统Web应用中的URL路由。 5. **GWT ...
4. **AJAX支持**:GWT提供了丰富的UI组件和异步通信机制,使得创建动态、交互性强的Web界面变得简单。 5. **强大的调试工具**:GWT提供了强大的开发和调试环境,开发者可以在IDE中直接调试Java代码,而不需要关注...
根据提供的文件信息,我们可以提炼出以下关键知识点,主要聚焦于Google Web Toolkit (GWT)以及如何使用它构建高性能的企业级Web应用。 ### GWT简介 Google Web Toolkit (GWT) 是一个开发框架,用于创建高性能的...
在IT行业中,GXT(Ext GWT)和GWT(Google Web Toolkit)是两种流行的JavaScript库,用于构建富互联网应用程序(Rich Internet Applications, RIA)。它们都是基于Java语言的,可以提供丰富的用户界面组件和高效的...
在GWT客户端,我们需要创建服务接口和服务异步接口。服务接口定义了要调用的服务器端方法,而服务异步接口包含了回调方法,用于处理服务器响应。 示例: ```java public interface MyService extends Remote...
3. **GWT RPC**:理解RPC的工作原理,包括序列化和反序列化,以及如何定义服务接口和服务端点。 4. **Java后端开发**:熟悉Servlet、JPA或Hibernate进行数据持久化,以及如何配置Spring来支持这些。 5. **客户端-...
标题中的“基于Java的Spring4GWT.zip”表明这是一个关于使用Java编程语言,结合Spring框架和GWT(Google Web Toolkit)技术的项目压缩包。这个项目可能是一个Web应用程序,利用了Spring的强大功能来管理和协调应用的...
- **数据绑定与服务端通信**:教授如何在客户端与服务器之间传输数据,包括RESTful服务调用和JSON数据解析。 #### 第三部分:高级主题 - **GWT与MVC模式**:讨论GWT框架下如何应用MVC(Model-View-Controller)...
《基于Google App Engine(GAE)的Java和GWT应用开发》这本书深入探讨了如何使用 Java 和 GWT 在 GAE 上构建强大且可扩展的 Web 应用程序。通过对 GAE 的介绍、Java 和 GWT 的应用技巧以及构建交互式 Web 应用程序的...
通过这个实例,开发者可以学习到如何在Spring框架下构建RESTful服务,如何使用GWT创建交互丰富的客户端,以及如何通过RPC进行高效的数据交换。这对于想要提升Java Web开发技能的开发者来说,是一个宝贵的实践资源。
4. **Web Services和RESTful API**:书中可能详细介绍了如何设计和实现Web服务,特别是使用REST(Representational State Transfer)架构风格,用于构建可扩展和灵活的API。 5. **富互联网应用(RIA)**:可能涉及...
《Packt.Google.Web.Toolkit.2.Application.Development.Cookbook.Source.Code》这个压缩包文件主要包含的是关于使用Google Web Toolkit(GWT)进行Web应用程序开发的源代码。GWT是一款强大的开源工具,它允许开发者...
这些技术在现代Web开发中占据重要地位,它们共同帮助开发者构建高质量、可维护的Web应用和服务。对于希望深入学习GWT、Java Bean验证、Scala以及RESTful API设计的开发者来说,这些库提供了很好的实践和学习资源。
GWT提供了一系列强大的工具和API,使开发者能够轻松构建复杂的Web应用。 #### 四、本书主要内容 - **第1章:GWT入门** - GWT的历史和发展背景。 - 安装配置环境,包括Eclipse集成开发环境和GWT插件。 - 第一个...
【标题】"webService-cxf-demo" 是一个基于Apache CXF框架实现的Web服务示例项目,主要用于展示如何创建和使用Web服务。CXF是一个开源的Java框架,它提供了多种方式来开发符合WS-*标准的Web服务,包括SOAP、RESTful ...
通过这些步骤,开发者可以快速地构建基于 Axis2 的 Web 服务系统,实现服务提供和服务消费。注意,随着技术的发展,Eclipse 和 Axis2 的版本可能会更新,因此在实际操作时,应根据最新的文档进行相应的调整。