GWT笔记(3)
Remote Procedure Calls
Rich Internet Applications (RIA)
JavaScript Object Notation (JSON)
在任何时候,只要你需要把代码运行在两个不同的地方,你就需要一种方法让它们之间相互通讯。最简单的方法是通过远程过程调用。
远程过程调用是一种简单的方法,客户端可以执行服务器上的一些逻辑并得到一个返回结果。
RMI,.NET Remoting,SOAP,REST和XML-RPC都是远程过程调用协议,但GWT不使用它们中任意一个。
1、GWT采用新协议的原因
(1)浏览器的调用是异步的;
(2)GWT的RPC需要简单化;
在首次使用时,浏览器下载所有的代码来开始应用程序,如果代码量大于100K,或许人们会感觉到一个停顿。而且,在浏览器上运行JavaScript往往是相当慢的。因此,执行一个复杂的协议如SOAP需要太多的代码下载,实际上太慢了。
(3)JavaScript不支持Java风格的序列化(串行化)类或动态类加载,因此GWT的RPC不能依靠它。
2、GWT RPC基础
GWT是以Java为中心的:客户端的Java被转化成JavaScript,服务器端的Java作为Java Servlet来运行。
(1)建立远程过程调用的第一步是定义一个模板来描述调用。
你需要做三次:客户端一次、服务器端一次、两者之间的共享接口一次。
例:假定你的客户端需要查看股票的价格。要预防非正式的通信,你应该允许它(客户端)一次请求几只股票的价格。因此,你需要一个方法来传递一个标记名数组,并返回double数组。让我们在接口中留住这个方法,调用StackService。你需要定义三个接口或类。
-------------------------------------------------------------------
名字 位置 目标
StockService接口 客户端和服务器端 描述服务
StockServiceImpl类 服务器端 实际的代码
StockServiceAsync接口 客户端 让你调用服务
-------------------------------------------------------------------
在Eclipse中建立RpcExample项目,使用脚手架工具,加入com.xyz.server包。
RpcProject/src/com/xyz/server/StockServiceImpl.java
-------------------------------
public class StockServiceImpl extends RemoteServiceServlet
implements StockService{
public double[] getPrices(String[] symbols){
double[] result=new double[symbols.length];
for(int i=0;i<symbols.length;i++){
result[i]=getPrice(symbols[i]);
}
return result;
}
}
-------------------------------
让我们来看所有的股价在400美元并且每次上涨1美元的股票。
RpcProject/src/com/xyz/server/StockServiceImpl.java
-------------------------------
pricate double price=400.0;
pricate synchronized double getPrice(String symbol){
return price++;
}
-------------------------------
现在返回到客户端,这里是入口点(EntryPoint)类。
RpcProject/src/com/xyz/client/RpcExample.java
-------------------------------
public class RpcExample implements EntryPoint, ClickListener{
private Button button=new Button("Click me");
private HTML label=new HTML();
private void onModuleLoad(){
button.addClickListener(this);
RootPanel.get("slot1").add(button);
RootPanel.get("slot2").add(label);
}
//.......
}
-------------------------------
当点击按钮时,onClick()方法被调用。你实际上是向服务器发出请求。
RpcProject/src/com/xyz/client/RpcExample.java
-------------------------------
private String[] symbols={"GooG","MSFT","SUNW"};
public void onClick(Widget sender){
//建立客户端代理。异步方式
StockServiceAsync service=(StockServiceAsync)GWT.create(StockService.class);
//指定URL
ServiceDefTarget endpoint=(ServiceDefTarget)service;
endpoint.setServiceEntryPoint(GWT.getModuleBaseURL()+"prices");
//创建异步返回处理结果
AsyncCallback callback=new AsyncCallback(){
public void onSuccess(Object result){
double[] prices=(double[]) result;
updatePrices(symbols, prices);
}
public void onFailure(Throwable cauht){
//生成一些UI东东来显示错误
}
//请求调用
service.getPrices(symbols,callback);
}
}
-------------------------------
StockServiceAsync和StockService接口可以手动的取得,通过一些工具(如Ruby脚本)从StockServiceImpl类得到。如下:
RpcProject/src/com/xyz/client/StockServiceAsync.java
-------------------------------
public interface StockServiceAsync{
void getPrices(String[] symbols, AsyncCallback callback);
}
-------------------------------
RpcProject/src/com/xyz/client/StockService.java
-------------------------------
public interface StockService extends RemoteService{
double[] getPrices(String[] symbols);
}
-------------------------------
现在来看第三步的onClick()方法。当getPrice()方法完成后,那么在AsyncCallback类的onSuccess()方法将被执行。这转变并调用一个名为updatePrices()的方法,它改变用户界面,显示出结果。如下定义:
RpcProject/src/com/xyz/client/RpcExample.java
-------------------------------
private void updatePrices(String[] symbols, double[] prices){
String html="";
for(int i=0;i<symbols.length;i++){
html+=symbols[i]+": "+prices[i]+"<br/>";
}
label.setHTML(html);
}
-------------------------------
当你运行程序并点击按钮时,并不发生任何事。主机shell将显示一条错误信息:
-------------------------------
[TRACE] The development shell servlet received a request for
'prices' in module 'com.xyz.RpcExample'
[WARN] Resource not found: prices
-------------------------------
这是因为我们还没有部署servlet代码。在Web模式下,你需要部署StockServiceImpl servlet到容器中,如Tomcat。查看Servlet容器的文档弄清怎样做。
在主机模式下它是很容易的。你只需加它到组件定义(RpcExample.gwt.xml)中即可。
RpcProject/src/com/xyz/RpcExample.gwt.xml
-------------------------------
<!-- Specify the servlet class. -->
<servlet path="/prices" class="com.xyz.server.StockServiceImpl"/>
-------------------------------
现在重启应用程序,一切OK。
(2)Server-based State
每次你点击按钮时,价格将递增,即使你在不同的浏览器进行点击。这是因为一个servlet实例被所有的客户端所共享。要想不同的用户有不同的状态,你需要执行多种会话ID。
(3)Serialization 串行化
在早先的例子中,你递交了一系列字符串,返回了一系列double值。
就像RMI和.NET Remoting,你不用限制初始类型。任何类型均能作为一个参数串行化传递,并从远程调用返回结果。
要记住:GWT的串行化概念不同于Java的串行化概念。
GWT的串行化类型有:
(1)is primitive, such as char, byte, short, int, long, boolean, float, or double;
(2)is a primitive wrapper (Character, Byte, etc.);
(3)is String or Date
(4)is an array of serializable types (including arrays of arrays);
(5)is a user defined class that contains only serializable fields, or implements the IsSerializable marker interface.
你使用的大多数简单类型和类都可以自动地被串行化,例如:
RpcProject/src/com/xyz/client/Rect.java
-------------------------------
public class Rect implements IsSerializable{
private int height;
private int width;
//Must have a zero-rg constructor, or no constructor
public Rect(){
}
public void setHeight(int height){
this.height=height;
}
//...
}
-------------------------------
对于集合类如Set、List、Map和HashMap,你必须在JavaDoc中使用特殊的annotation类来告诉GWT编译器是哪种集合。例如:
RpcProject/src/com/xyz/client/MyClass.java
-------------------------------
public class MyClass implements IsSerializable{
/**
* This field is a Set that must always contain Strings.
*
* @gwt.typeArgs<java.lang.String>
*/
public Set setOfStrings;
}
类似,注解参数和返回值:
RpcProject/src/com/xyz/server/MyService.java
-------------------------------
public interface MyService extends RemoteService{
/**
* The first annotation indicates that the parameter named 'c' is
* a List that will only contain Integer objects. The second
* annotation indicates that the returned List wil only contain
* String objects (notice there is no need for a name, since it
* is a return value).
*
* @gwt.typeArgs c <java.lang.Integer>
* @gwt.typeArgs <java.lang.String>
*/
List reverseListAndConvertToStrings(List c);
}