gwt 同步和异步

过去几年中,Web应用程序开发的趋势是创建富Internet应用程序,其中大多数是使用异步JavaScript + XML(Ajax)实现的。 但是,由于JavaScript编码的本质,这并不容易。 构建大型Web应用程序特别困难。 这就是GWT发挥作用的地方:它使您可以使用Java编程而不是Ajax构建丰富而响应Swift的Web界面。 GWT还提供了Java开发的所有好处,例如具有高级调试功能的出色IDE支持。 GWT可以极大地提高您的生产力并丰富您的用户体验。 在本文中,我将解释如何在Eclipse中创建GWT应用程序以及如何使用GWT TreeTreeItem小部件在大学大气研究公司(UCAR)创建示例组织结构。 我将说明如何实现延迟加载,如何与RESTful Web服务集成以及如何实现GWT回调和自定义异常。 JSON已用作RESTful Web服务的数据格式。

软件需求

首先,您需要下载以下软件包并根据各自网站上的安装指南进行安装。 (请参阅相关主题的链接。)

  1. Java EE开发人员的Eclipse IDE Galileo(Eclipse 3.5)
  2. GWT 2.0
  3. Eclipse的GWT插件
  4. MySQL 5.1或DB2®Express-C
  5. 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中:

  1. 选择文件>新建> Web应用程序项目
  2. 输入gwtRESTTutorialViewProject Name字段和edu.ucar.cisl.gwtRESTTutorialView新的Web应用程序项目窗口Package字段中(见图1)。
  3. 选择使用默认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),它具有三个属性: iddisplayNamedataReadyid是数据项的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 getters7.      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 getters10.     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 setters8.     public String buildUri() {
9.         return "http://localhost:8080/gwtRESTTutorial/rrh/organizations/" + id;
10.    }
11. }

实现RPC代理以请求RESTful Web服务

有几种策略可以将GWT与RESTful Web服务集成。 如果RESTful Web服务服务器在相同的域和端口上运行,那么显而易见的选择是使用GWT RequestBuilder类。 但是, RequestBuilderRequestBuilder “相同原始策略(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 。 该方法具有两个参数uricontentType 。 前者是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响应中的内容,并将其用作该方法的返回值。 否则,它将引发一个自定义异常。 该方法将捕获其他异常,例如MalformedURLExceptionIOException并抛出自定义异常,以便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数组。 它为每个雇员提取iddisplayName ,创建一个应用程序对象EmployeeItemData ,创建一个TreeItem小部件,并将该应用程序对象与该小部件绑定。

processSubOrgs方法(第32 – 43行)处理JSON数组中的每个子组织。 它提取iddisplayName ,并将它们存储在应用程序对象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被添加到RootPanelRootPanel是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小部件检索应用程序项目数据,并根据应用程序项目数据的性质实例化EmployeeRpcCallbackOrganizationItemData的回调实例。 然后,它将TreeItem小部件和事件类型与回调实例相关联,以便知道返回RESTful Web服务的数据后如何继续。

根据GWT的要求,在调用远程服务之前,必须创建异步远程接口的实例并将其用于调用远程服务,并在远程服务中声明所有参数以及回调类的实例。 因为远程服务调用是异步的并且是非阻塞的,所以GWT客户端不会等待服务的响应。 它继续执行,直到收到来自远程服务器的异步回调为止。 回调通知GWT应用程序远程服务调用是否已成功执行。 如果远程服务成功,则将调用onSuccess方法。 否则,将使用Throwable实例调用onFailure方法,该实例包含从服务器传递的自定义异常。 回调类将处理从服务器返回的数据。

创建自定义树图像

为GWT Tree小部件自定义树图像非常容易。 您只需要创建一个扩展Tree.Resource接口的自定义接口,然后重新声明treeOpentreeClosedtreeLeaf方法(清单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 同步和异步

gwt 同步和异步_使用GWT和RESTful Web服务构建动态的组织树相关推荐

  1. gwt 同步和异步_GWT Spring和Hibernate进入数据网格世界

    gwt 同步和异步 利用Infinispan Data Grid的功能最大化Hibernate性能. 一个GWT , Spring , JPA , Hibernate , Infinispan集成教程 ...

  2. setstate是同步还是异步_【vert.x准备篇1】同步和异步,阻塞和非阻塞概念澄清

    为了能更好的理解vert.x的线程模型,我们必须要先明确几个概念:同步(Synchronous)和异步(Asynchronous),阻塞(Blocking)和非阻塞(Non-Blocking).关于这 ...

  3. kafka 同步提交 异步_极限MQ (5) Kafka 消费者

    要想知道如何从 Kafka 读取消息,需要先了解消费者和消费者群组的概念. 假设我们有一个应用程序需要从 Kafka 主题读取消息井验证这些消息,然后再把它们保存起来.应用程序需要创建一个消费者对象, ...

  4. setstate是同步还是异步_谈谈 IO模型:同步、异步、阻塞、非阻塞

    同步/异步.阻塞/非阻塞 说的是一回事儿吗? 同步/异步.阻塞/非阻塞 你能通俗易懂的讲清楚吗? Java 中的 BIO.NIO.AIO 你了解吗? Socket 编程你还会吗? Linux 操作系统 ...

  5. java 同步和异步_知道什么叫同步和异步吗?

    评论 # re: 知道什么叫同步和异步吗? 2006-11-06 15:34 chicken 你翻译的很垃圾阿 看了英文才懂...  回复  更多评论 # re: 知道什么叫同步和异步吗? 2006- ...

  6. kafka 同步提交 异步_腾讯游戏工程师分享:简单理解 Kafka 的消息可靠性策略

    作者:hymanzhang,腾讯 IEG 运营开发工程师 背景 部门的开发同学最近在开发一个活动的过程中,需要关注大量的应用后台逻辑,捕捉各种事件的触发.在设计时打算采用 kafka 消息队列进行业务 ...

  7. kafka 同步提交 异步_详解Kafka设计架构核心——Kafka副本机制详解

    所谓的副本机制(Replication),也可以称之为备份机制,通常是指分布式系统在多台网络互联的机器上保存有相同的数据拷贝.副本机制有什么好处呢? 1. 提供数据冗余.即使系统部分组件失效,系统依然 ...

  8. springcloud 熔断不生效_深入理解SpringCloud与微服务构建

    目录 一.SpringCloud微服务技术简介 二.开发框架SpringBoot 三.服务注册和发现Ereka 四.负载均衡 五.申明式调用 六.熔断器 七.路由网关 八.配置中心 九.服务链路追踪 ...

  9. rest web服务_在WildFly的REST Web服务中与Jackson的双向关系

    rest web服务 这是使用Jackson的REST Web服务中Java实体之间的双向关系的示例. 假设我们在两个实体Parent和Child之间存在双向关系. 使用MySQL工作台为这两个表生成 ...

  10. apache-cxf 使用_使用Apache CXF进行Web服务学习

    apache-cxf 使用 在我的最后几个项目中,我使用了Web服务,在某些地方创建它们并在其他地方使用它们. 我认为标准任务(例如创建客户端,创建Web服务等)非常简单,如果遇到问题,有足够的资源. ...

最新文章

  1. linux目录档案权限详解,五、Linux的档案权限与目录配置
  2. C语言写一个简单的数学程序,用C语言计算简单的数学式子
  3. Ubuntu 11.04 beta 2发布!
  4. git拉取请求_24个“拉取请求”挑战鼓励卓有成效的贡献
  5. C# 本质论 第二章 数据类型
  6. 剑指offer——面试题56:链表中环的入口
  7. python 文件复制中出现 Python3之由通用字符名称“\u202A”表示的字符不能在当前代码页中表示出来
  8. adb调试工具下载使用
  9. win10系统字体颜色变淡
  10. 惠普打印机换硒鼓图解_hp硒鼓怎么安装 hp硒鼓安装方法这图文教程】
  11. 热度php代码,爬取知乎热度搜索标题并数据分析及可视化(示例代码)
  12. 数据挖掘实例(航空公司客户价值分析)
  13. python剔除st股_通达信剔除st的指标,通达信条件预警中如何剔除st股票
  14. python美化excel_简单介绍python在CMD界面读取excel所有数据
  15. injectcheck php_PHP安全最大化
  16. 深入理解Solaris X64系统调用
  17. 高速—HDMI布线规则
  18. 搬家了,新的网站地址
  19. linux给网卡添加一个ip地址,linux网络配置中如何给一块网卡添加多个IP地址
  20. vue2关于引入字体的步骤

热门文章

  1. JSP ssh房地产项目管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计
  2. 发送邮件,javax.mail 与 geronimo-javamail_1.4_spec 的jar包冲突
  3. 减脂增肌运动和饮食结合
  4. 【计算机网络】计网笔记知识点整理篇(1-3章,后续章节持续更新)
  5. java中实现注册时Email邮件激活验证
  6. 如何建立个人网站:从搭建到运营再到盈利
  7. 封闭解(Closed-form solution)、解析解(Analytical solution)、数值解(Numerical solution) 释义
  8. 飞信机器人 ld-linux.so.2,飞信机器人安装
  9. java 所得税计算_java个人所得税计算器
  10. 图书销售系统 php,php文学小说销售系统