GWT 入门介绍
From: http://blog.csdn.net/struts2/article/details/1758122
GWT 入門介紹
GWT使用JSON格式的數據通訊?
?
GWT是 Google Web Toolkit的簡稱。
GWT是一個以Java語言為工具,以類似Swing的方式編寫UI組件,之后通過GWT Compiler編譯
為JavaScritp和HTML在客戶端瀏覽器中運行的一個開發工具和編程模型。
?
GWT應用程序有兩種方式運行:
Hosted Model
???? 在Hosted Model方式下,Java程序并不會被編譯成JavaScript,GWT只是創造了一個類似
???? 瀏覽器的環境,直接運行使用GWT開發的程序。這種模式最具生產力(這種模式下可以Debug所有的Java代碼),
???? 所以開發環境通常使用Hosted Model,但是真正的程序執行不使用這種模式。
Web Model
???? 就是將使用GWT編寫的Java程序整整編譯成JavaScript,在Web容器上運行,使用瀏覽器
???? 訪問的模式,這種方式只有在測試或者運行的時候才使用,每次對Java類的修改必須重新編譯
???? 這些Java類,之后重新部署應用。所以生產力比較低。
?
安裝GWT
從如下位置下載GWT的最新版本:
http://code.google.com/webtoolkit/download.html
當前最新的Release版本為1.4.60, 上一個穩定的版本為1.3.3
第一步,安裝JDK, 1.4 以上的版本都可以。
第二步,下載GWT壓縮文件,這里使用1.4.59 RC2(gwt-windows-1.4.59.zip)
第三布,將GWT壓縮文件解壓縮到制定的目錄,這里我使用的目錄為,C:/程序開發/Java/gwt-windows-1.4.59
第四步,安裝成功
?
安裝之后的目錄結構為:
C:/程序開發/Java/gwt-windows-1.4.59
??? doc(文檔目錄,開發文檔和Java API文檔)
??? samples(示例代碼目錄,非常有名的KitchenSink示例代碼即在此目錄中)
??? about.html
??? about.txt
??? applicationCreator.cmd
??? benchmarkViewer.cmd
??? COPYING
??? COPYING.html
??? gwt-benchmark-viewer.jar
??? gwt-dev-windows.jar
??? gwt-ll.dll
??? gwt-module.dtd
??? gwt-servlet.jar
??? gwt-user.jar
??? i18nCreator.cmd
??? index.html
??? junitCreator.cmd
??? projectCreator.cmd
??? release_notes.html
??? swt-win32-3235.dll
?
使用GWT建立開發
GWT的安裝目錄下有一個名叫Samples的目錄,里邊有很多的例子,其中KitchenSink比較全面的展現了GWT的Web組件。
?
applicationCreator創建一個可以以Hosted Mode形式運行的GWT應用程序,
以下命令將建立一個工程
?
C:/程序開發/Java/gwt-windows-1.4.59>applicationCreator.cmd -eclipse GWTLogon -out GWTLogon com.jpleausre.gwt.logon.client.GWTLogon
Created directory GWTLogon/src
Created directory GWTLogon/src/com/jpleausre/gwt/logon
Created directory GWTLogon/src/com/jpleausre/gwt/logon/client
Created directory GWTLogon/src/com/jpleausre/gwt/logon/public
Created file GWTLogon/src/com/jpleausre/gwt/logon/GWTLogon.gwt.xml
Created file GWTLogon/src/com/jpleausre/gwt/logon/public/GWTLogon.html
Created file GWTLogon/src/com/jpleausre/gwt/logon/client/GWTLogon.java
Created file GWTLogon/GWTLogon.launch
Created file GWTLogon/GWTLogon-shell.cmd
Created file GWTLogon/GWTLogon-compile.cmd
?
運行GWTLogon-shell.cmd可以看到GWT啟動的服務端的Google Web Toolkit Development Shell,如下圖:
和如下的Host Mode的瀏覽器:
點擊其中的 Click Me 按鈕,可以看到輸出的Hello World!。
projectCreator建立一個基于ant構建的,或者基于eclipse的GWT開發工程,
例如:
C:/程序開發/Java/gwt-windows-1.4.59>projectCreator.cmd -ant GWTLogon -eclipse GWTLogon -out GWTLogon
Created directory GWTLogon/src
Created directory GWTLogon/test
Created file GWTLogon/GWTLogon.ant.xml
Created file GWTLogon/.project
Created file GWTLogon/.classpath
?
其中的GWTLogon.ant.xml構建文件的內容為:
?<?xml version="1.0" encoding="utf-8" ?>
<project name="GWTLogon" default="compile" basedir=".">
? <description>
??? GWTLogon build file.? This is used to package up your project as a jar,
??? if you want to distribute it.? This isn't needed for normal operation.
? </description>
? <!-- set classpath -->
? <path id="project.class.path">
??? <pathelement path="${java.class.path}/"/>
??? <pathelement path="C:/程序開發/Java/gwt-windows-1.4.59/gwt-user.jar"/>
??? <!-- Additional dependencies (such as junit) go here -->
? </path>
? <target name="compile" description="Compile src to bin">
??? <mkdir dir="bin"/>
??? <javac srcdir="src:test" destdir="bin" includes="**" debug="on" debuglevel="lines,vars,source" source="1.4">
????? <classpath refid="project.class.path"/>
??? </javac>
? </target>
? <target name="package" depends="compile" description="Package up the project as a jar">
??? <jar destfile="GWTLogon.jar">
????? <fileset dir="bin">
??????? <include name="**/*.class"/>
????? </fileset>
????? <!-- Get everything; source, modules, html files -->
????? <fileset dir="src">
??????? <include name="**"/>
????? </fileset>
????? <fileset dir="test">
??????? <include name="**"/>
????? </fileset>
??? </jar>
? </target>
? <target name="clean">
??? <!-- Delete the bin directory tree -->
??? <delete file="GWTLogon.jar"/>
??? <delete>
????? <fileset dir="bin" includes="**/*.class"/>
??? </delete>
? </target>
? <target name="all" depends="package"/>
</project>
?
applicationCreator和projectCreator的區別是
applicationCreator創建了src目錄和Demo代碼,projectCreator不創建Demo代碼,但是創建src目錄和test目錄。
applicationCreator創建了啟動腳本GWTLogon-shell.cmd和GWT編譯腳本GWTLogon-compile.cmd,而projectCreator不創建。
applicationCreator創建了eclipse launch文件,而projectCreator創建.classpath和.project文件。
?
通常情況下,我們先使用projectCreator創建按project,projectCreator創建的工程可以輕松的import到eclipse中,
之后我們使用applicationCreator創建需要的Java示例代碼,eclipse launch文件,啟動腳本和編譯腳本。
?
使用Eclipse導入的GWT工程如下:
?
?
命令列表
projectCreator
生成基本項目框架,可以選擇使用eclipse或者使用ant構建文件
?
applicationCreator
生成代碼示例和一個可運行的應用程序
?
junitCreator
生成一個JUnit測試Case
?
i18nCreator
生成一個i18n屬性文件和對應的Javascript腳本。
benchmarkViewer
顯示benchmark結果
?
功能介紹(通用)
GWT體系結構
?
GWT Java-to-JavaScript Compiler
將Java程序翻譯為JavaScript,通過GWT Compiler可以讓GWT程序在Web 模式下運行
GWT Hosted Web Browser
GWT Hosted Web Brower讓你的程序可以在Hosted模式下運行,在Hosted模式下運行的是Java代碼而不是編譯出來的JavaScript代碼,在Hosted模式下可以輕松的做Debug。
JRE emulation library
可以稱為JRE簡化的類庫。在客戶端(用來編譯成JavaScript代碼的客戶端Java代碼)不是所有的java類庫都被gwt支持,只有部分被支持,這些類庫是幾乎所有的java.lang包,java.util包的一部分。
GWT Web UI class library
使用GWT Web UI類庫可以創建web 瀏覽器的組件,例如按鈕,文本框,圖片等。這是GWT的核心UI類庫。
?
?
功能介紹(Web控件)
Web控件是GWT表示層的核心,通過使用GWT提供的Web控件可以創建豐富的客戶端畫面。
GWT的web組件主要分為兩類:輸入控件和Layout控件。
?
輸入控件主要是指向服務器提交數據,處理操作,顯示服務器數據的空間,主要包括:
Button,RadionButton,PushButton,ToggleButton,CheckBox,TextBox,PasswordBox
TextArea, HyperLink,ListBox,MenuBar,Tree,Table, TabBar,DialogBox, PopupPanel
RichTextArea, DisclosurePanel, SuggestBox
Layout 空間主要用來有規律地放置輸入控件,主要包括:
Stackpanel,HorizontalPanel,VerticalPanel, FlowPanel, VerticalSplitPanel, HorizontalSplitPanel,
DockPanel, TabPanel,
?
事件模型(Event & Listener)
如何處理頁面的事件呢,例如點擊按鈕,Textbox失去焦點等?
如下的代碼建立了一個按鈕,按鈕的名字是“Click Me”,當你點擊這個按鈕的時候處罰一個Click事件,Click事件觸發onClick(Widget sender)方法。
sender表示觸發onClick方法的組件,這里只是簡單的在頁面上顯示Hello World信息(相當于javascript的alert方法)。
?Button b = new Button("Click Me");
?b.addClickListener(new ClickListener() {
? public void onClick(Widget sender) {
??? Window.alert("Hello World");
? }
});
常見的Listener如下:
ChangeListener
ClickListener
FocusListener
KeyboardListener
MouseListener
MouseWheelListener
PopupListener
ScrollListener
TableListener
TreeListener
?
?
功能介紹(遠過程調用RPC)
??? 體系結構
GWT應用中頁面一旦加載,就再也不會向服務器請求HTML內容,所有的畫面遷移,轉換都在客戶端進行,但是數據還是會向服務器提交,或者從服務器獲取。
服務器上負責處理數據的對象在GWT中叫做Service,每個Service有三個類組成:服務方法定義接口(Service),異步調用接口(ServiceAsync)和服務器方法實現類ServiceImpl。
以Login為例子說明:
?
// 服務方法定義接口
public interface LoginService extends RemoteService {
??? public boolean login(LoginSO login) throws ApplicationException;
}
// 異步調用接口
public interface LoginServiceAsync {
??? void login(LoginSO login, AsyncCallback async);
}
// 服務器方法實現類
?public class LoginServiceImpl extends RemoteServiceServlet implements LoginService {
??? public boolean login(LoginSO login) throws ApplicationException {
??????? ...
??????? return true;?
??? }
}
?
其中前兩個接口在client包內部,最后一個實現在server包內部。
?
客戶端調用一個服務類的方法的代碼如下:
?
LoginServiceAsync ourInstance = (LoginServiceAsync) GWT.create(LoginService.class); // ?
((ServiceDefTarget) ourInstance).setServiceEntryPoint(GWT.getModuleBaseURL() + "/LoginService"); // ?
ourInstance.login(loginSO, new AsyncCallback() {?? //?
??? public void onFailure(Throwable caught) {???????? //?
??????? if(caught instanceof InvocationException) {
??????????? // system exception
??????? } else {
??????????? Window.alert(" " + GWTShowConstants.Messages.constants.maxQueryCount());
??????????? // aplication exception
??????? }
??? }
??? public void onSuccess(Object result) {??????????? //?
??????? Window.alert("success");
??? }
});
?
// ‘
?
?遠程調用
? 獲得服務器方法的調用接口(skeleton)。
? 設置服務位置。
? 遠程調用服務器上的方法,注意這里是異步調用,在?和?調用之前‘代碼可能先被調用了。
? 調用出錯,或者調用方法拋出異常的時候調用的方法。
? 調用成功返回時候調用的方法。
?
?
??? 參數和返回值系列化類型
這里的參數指的是Service方法調用的參數和返回值。
由于GWT的客戶端代碼都是JavaScript,而服務器代碼都是使用Java編寫的,這就涉及到JavaScript調用Java方法的時候
如何傳遞參數,如何取得返回值的問題。
?
?可序列化的類型包括:
(1) 原始類型,例如:char, byte, short, int, long, boolean, float, double;
(2) String,java.util.Date,或者原始類型的包裝類型,例如:? Character, Byte, Short, Integer, Long, Boolean, Float, or Double;
(3) 可序列化類型數組(包含(4)和(5)定義的類型)
(4) 用戶定義的可序列化類型
(5) 該類型至少有一個可序列化的子類型
?
針對上述(4)中說明的,什么是用戶自定義的可序列化類型呢?必須滿足以下亮點:
第一,必須直接或者間接(例如,父類型實現了這個接口)的實現了IsSerializable接口
第二,所有非transient類型都是可序列化的(final類型的屬性在GWT中被視為transient類型)
?
是否支持容器類型呢?那么又如何聲明呢?
支持容器類型,GWT可以使用Type 參數來表示容器類型內部的元素的類型,例如:
注意GWT暫時不支持使用 JDK 5.0 的模板容器
?
//用戶自定義序列化類型
public class MyClass implements IsSerializable {
? /**
?? * 這個Set中的元素的類型必須都是String類型
?? *
?? * @gwt.typeArgs <java.lang.String>
?? */
? public Set setOfStrings;
? /**
?? * Map中的元素的Key和Value的類型都是String類型。
?? *
?? * @gwt.typeArgs <java.lang.String,java.lang.String>
?? */
? public Map mapOfStringToString;
}
// 服務器方法實現類
public interface MyService extends RemoteService {
? /**
?? * 第一個類型參數表示方法的參數c是一個List,并且其中只能放置Integer類型。
?? * 第二個類型參數表示返回值為List,并且其中的原書的類型為String類型。
?? *
?? * @gwt.typeArgs c <java.lang.Integer>
?? * @gwt.typeArgs <java.lang.String>
?? */
? List reverseListAndConvertToStrings(List c);
}
?
?
??? 異常
在調用方法的時候異常怎么處理呢?
調用方法的過程中的異??梢苑譃閮深?#xff1a;第一類,調用方法的過程中出現了異常,例如網絡故障,服務類不存在等。
第二類,服務器方法拋出了異常。
?
?在客戶端調用的過程中這兩種異常都在onFailure(Throwable caught)方法中處理,但是caught的類型有所區別,
第一種情況下,caught為InvocationException的子類,第二種情況下caught為用戶自定義的異常。
?
由于異常也需要在客戶端(JavaScript)和服務器端(Java)傳遞,所以Exception的定義也要滿足可序列化的要求。
但是在GWT中已經定義了一個基本的異常類型來提一個Exception類的基礎類,這個類是SerializableException,例如:
public class ApplicationException extends SerializableException {
??? public ApplicationException() {
??????? super();
??? }
??? public ApplicationException(String msg) {
??????? super(msg);
??? }
??? public Throwable getCause() {
??????? return super.getCause();
??? }
??? public String getMessage() {
??????? return super.getMessage();
??? }
??? public Throwable initCause(Throwable cause) {
??????? return super.initCause(cause);
??? }
}
?
異常消息內容建議,服務器端的錯誤消息內容在服務器保存,客戶端的錯誤消息內容在客戶端保存(參看后續的國際化部分),兩個地方
都需要的,出于GWT技術建議使用兩份,分別放在客戶端和服務器端。
為什么不能重用呢?
客戶端的消息會被編譯為JavaScript,所以服務器端通常無法使用。(還有另外的原因,參看后續的國際化部分)。
?
功能介紹(集成JUnit)
@TODO
?
功能介紹(國際化)
???? 項目開發過程中經常需要一些可配置的常量,例如查詢最大條數,目錄位置等。在傳統的Java應用程序中這些內容通常會放在
屬性文件中(Properties文件),但是使用屬性文件有些弊端,第一,不支持類型,所有的內容都是String,第二是,只有在具體使用
的時候才能發現有些屬性沒有定義,而不能在編譯的時候發現。
???? 那么GWT如何處理這個問題呢?GWT中有一個特殊的接口com.google.gwt.i18n.client.Constants可以使用這個接口達到
定義常量的效果,并且這些常量在編譯的時候被綁定,而且可以支持類型。
??? 使用GWT主要有以下幾步:
第一步,建立一個集成于Constants的接口,例如:
public interface NumberFormatConstants extends Constants {
? /**
?? * @return the localized decimal separator
?? */
? String decimalSeparator();
? /**
?? * @return the localized thousands separator
?? */
? String thousandsSeparator();
}
第二步,根據接口中定義的方法定義一個跟接口同名的屬性文件,例如:
#NumberFormatConstants.properties
decimalSeparator = ,
thousandsSeparator = .
?
第三步,獲取文件中定義的內容,例如:
public void useNumberFormatConstants() {
? NumberFormatConstants constants = (NumberFormatConstants) GWT.create(NumberFormatConstants.class);
? String decimalSep = constants.decimalSeparator();
? String thousandsSep = constants.thousandsSeparator();
? String msg = "Decimals are separated using '" + decimalSep + "'";
? msg += ", and thousands are separated using '" + thousandsSep + "'";
? showMessage(msg);
}
上述三步中在第二步和第三步中間隱含了伊特特殊的步驟,就是GWT編譯器結合接口文件和屬性文件編譯出了一個
類,這個類實現了這個接口,每一個方法返回屬性文件中的值。
其中GWT.create()方法可以獲得生成的中間類的引用。
?
通常情況下,接口方法明和屬性文件中的名字相同,例如:
String decimalSeparator(); 和 thousandsSeparator = .
但是也可以自定義接口方法和屬性文件中內容的映射,例如:
?
?public interface NumberFormatConstantsWithAltKey extends Constants {
? /**
?? * @gwt.key fmt.sep.decimal
?? * @return the localized decimal separator
?? */
? String decimalSeparator();
? /**
?? * @gwt.key fmt.sep.decimal
?? * @return the localized thousands separator
?? */
? String thousandsSeparator();
}
@gwt.key fmt.sep.decimal 定義了屬性文件中key的內容,所以屬性文件應該為:
#NumberFormatConstants.properties
fmt.sep.decimal = .
fmt.sep.thousands = ,
Constants子接口中定義的方法必須滿足如下形式:
?
T methodName()
?
這里T是一個返回值,T可以使用如下表中的所有類型:
T類型??????????????????????? 屬性文件定義
String?????????????????????? 簡單的字符串
String[]???????????????????? 使用逗號分割的字符串,如果某個字符串中包含逗號需要使用//作為轉移字符,例如:'//,'?
int??????????????????????????? int值,在編譯的時候做類型檢查
float???????????????????????? float值,在編譯的時候做類型檢查
double????????????????????? double值,在編譯的時候做類型檢查
boolean???????????????????? boolean值"true" 或者 "false"), 在編譯的時候做類型檢查
Map????????????????????????? 使用逗號分隔的字符產,每一個字符產在屬性文件中有一條定義,定義了一個Key-Value值
?
Map示例:
a = X
b = Y
c = Z
someMap = a, b, c
?
Map someMap();方法得到的內容為:{a:X, b:Y, c:Z}
?
ConstantsWithLookup
ConstantsWithLookup是Constants的子接口,用法一樣,只不過ConstantsWithLookup有一組通過屬性名字獲取屬性值的方法:
getBoolean(String)??????? 通過名字找到boolean型內容
getDouble(String)???????? 通過名字找到double型內容
getFloat(String)??????????? 通過名字找到float型內容
getInt(String)????????????? 通過名字找到int型內容
getMap(String)??????????? 通過名字找到Map型內容
getString(String)????????? 通過名字找到String型內容
getStringArray(String)?? 通過名字找到String[]型內容
?
效率問題:Constants效率比ConstantsWithLookup高,為什么呢?Constants在編譯的時候會生成對應的JavaScript代碼,
GWT Compiler會根據程序中是否使用了某些屬性來決定這些內容是否會被編譯為JavaScript,所以及時在Constants中聲明
了某些方法,如果在代碼中不使用的話,不會被編譯為JavaScript代碼的。
但是ConstantsWithLookup有根據屬性名字查找屬性內容的方法,所以,GWT Compiler不能根據上述方法確定屬性是否被使用,
所以所有的屬性內容都回被編譯為JavaScript代碼。
這是ConstantsWithLookup的優點,也是缺點!
?
Message類
在使用Constants(或者ConstantsWithLookup)的時候,我們只能使用預定義的消息,有些時候我們需要可變的消息。
例如:
??? 我們需要一個通用的消息再加上一個功能名字的參數怎么實現呢?
?
Message類相當于Java中的Properties,ResourceBundle和MessageFormat的聯合體,例如:
?
消息文件類:
public interface GameStatusMessages extends Messages {
? /**
?? * @param username the name of a player
?? * @param numTurns the number of turns remaining
?? * @return a message specifying the remaining turns for a player
?? */
? String turnsLeft(String username, int numTurns);
? /**
?? * @param numPoints the number of points
?? * @return a message describing the current score for the current player
?? */
? String currentScore(int numPoints);
}
?
屬性文件定義:
turnsLeft = Turns left for player ''{0}'': {1}
currentScore = Current score: {0}
使用:
public void beginNewGameRound(String username) {
? GameStatusMessages messages = (GameStatusMessages) GWT.create(GameStatusMessages.class);
? // Tell the new player how many turns he or she has left.
? int turnsLeft = computeTurnsLeftForPlayer(username);
? showMessage(messages.turnsLeft(username, turnsLeft));
? // Tell the current player his or her score.
? int currentScore = computeScore(username);
? setCurrentPlayer(username);
? showMessage(messages.currentScore(currentScore));
}
?
我們可以看到在使用的時候基本一致,但是,可以使用參數配置原有的消息。
另外Message的方法的格式為:
??? String methodName(optional-params)
從中我們也可以看出區別,Message只能使用String類型的參數。
?
Constants(或者ConstantsWithLookup)和Message的區別是:
Constants用來定義系統的常量,支持多種類型。
Message用來定義系統的消息,可以支持參數化消息,但是只支持String類型的內容。
?
在使用Constants和Message的時候,可以將屬性文件的編碼設置為UTF-8這樣,就不用
使用Native2ascii將正常的文件轉移為utf-8的替換文件了。
當然如果你覺得不麻煩也可以使用傳統的Java屬性文件(使用native2ascii處理過得文件)。
?
?
?
功能介紹(JavaScript Native Interface)
JavaScript Native Interface = JSNI
JSNI定義了在GWT環境下,Java與JavaScript交互的一種方法。
雖然GWT的一些核心的方法是用JavaScript編寫的,但是這里還是不推薦使用JNI,應為這樣做與GWT的初衷相悖,
并且,有一定的難度,開發調試也相對困難。
Java調用JavaScript方法:
JSNI方法定義需要使用native關鍵字,并且需要在參數列表之后,結尾的分號之前定義。JSNI方法的開始使用/*-{
結尾使用}-*/,例如:
?
public static native void alert(String msg) /*-{
? $wnd.alert(msg);
}-*/;
?
?
當上述方法在Java中調用的時候,實際上將會調用Window的alert方法,將傳入的內容打印出來。
在Hosted Mode下,斷點可以設置在上述方法中,可以方便的查看傳入的參數。
?
JavaScript調用Java方法:
方法調用方式:
??? [instance-expr.]@class-name::method-name(param-signature)(arguments)
屬性訪問方式:
??? [instance-expr.]@class-name::field-name
?
[instance-expr.]
??? 用來區分實例方法調用還是靜態方法調用。在調用實例方法的時候必須出現,在調用靜態方法的時候不能出現。
class-name
??? 類的名字。
method-name
??? 方法的名字
param-signature
??? 方法的參數列表,這里使用的是內部形式(參考Java虛擬機Class格式),但是不需要寫返回值類型。
arguments
??? 調用方法的實際參數。
例如:
public class JSNIExample {
? String myInstanceField;
? static int myStaticField;
? void instanceFoo(String s) {
??? // use s
? }
? static void staticFoo(String s) {
??? // use s
? }
?
? // 該方法被調用的時候將在JavaScript中執行,并且
? // 可以使用JavaScript中的內容。
? public native void bar(JSNIExample x, String s) /*-{
??? // 調用這個實例本身的instanceFoo方法
??? this.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);
??? // 調用x實例(輸入參數)上的instanceFoo實例方法
??? x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);
??? // 調用靜態方法 staticFoo()
??? @com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s);
??? // 讀取這個實例的變量
??? var val = this.@com.google.gwt.examples.JSNIExample::myInstanceField;
??? // 設置x上的實例變量
??? x.@com.google.gwt.examples.JSNIExample::myInstanceField = val + " and stuff";
??? // Read static field (no qualifier)
??? @com.google.gwt.examples.JSNIExample::myStaticField = val + " and stuff";
? }-*/;
}
?
Java和JavaScript之間參數的傳遞:
Java -> JavaScript
Java type????????????????????????????? JavaScript Type
numeric primitive???????????????????? a JavaScript numeric value, as in var x = 42;
String?????????????????????????????????? a JavaScript string, as in var s = "my string";
boolean???????????????????????????????? a JavaScript boolean value, as in var b = true;
JavaScriptObject (see notes)??? a JavaScriptObject that must have originated from JavaScript code, typically as the return value of some other JSNI method
Java array??????????? an opaque value that can only be passed back into Java code
any other Java Object??????? an opaque value accessible through special syntax
?
異常
調用JSNI方法的時候會拋出一個JavaScriptException的異常,但是由于JavaScript不是一個強類型的語言,所以
無法想Java一樣處理JavaScript異常。一個好的方式是在Java中處理Java異常,在JavaScript中處理JavaScript異常。
另外在JSNI方法,Java普通方法混掉的過程中,異??梢詮淖畹讓右浦矑伒阶钕氲恼{用層,例如:
1. Java method foo() calls JSNI method bar()
2. JavaScript method bar() calls Java method baz()
3. Java method baz() throws an exception
baz()中拋出的異常可以蔓延到bar方法,可以在foo方法中捕獲。
?
?
?
從Host Model到 Web Model
在Host Model方式下,GWT并不將Java代碼編譯為JavaScript,而是在GWT環境中直接運行Java bytecode,
但是項目正式部署之后使用的是Web Model,那么如何從Host Model遷移到Web Model呢?
?
首先需要將Java代碼編譯為JavaScript代碼。
使用如下命令可以將Java代碼編譯為JavaScript代碼:
java -cp "%~dp0/src;%~dp0/bin;%~dp0/../../gwt-user.jar;%~dp0/../../gwt-dev-windows.jar" com.google.gwt.dev.GWTCompiler -out "%~dp0/www" %* com.google.gwt.sample.hello.Hello
?
-cp? 指定源代碼目錄,Class目錄,和GWT的jar文件的路徑
-out 指定JavaScript代碼的輸出路徑
com.google.gwt.sample.hello.Hello 指定編譯的Module,一般是gwt.xml文件中entry-point類去掉client之后的內容。
?
當代碼量比較大的時候,需要指定Java使用內存的大小,否則會內存溢出。
java -Xmx512m -Xms128m -cp "%~dp0/src;%~dp0/bin;%~dp0/../../gwt-user.jar;%~dp0/../../gwt-dev-windows.jar" com.google.gwt.dev.GWTCompiler -out "%~dp0/www" %* com.google.gwt.sample.hello.Hello
?
之后將編譯成的JavaScript代碼拷貝到Web項目的根目錄中,與WEB-INF相同層次的目錄。
?
最后需要將gwt.xml文件中定義的service編程對應的Servlet。
<servlet path='/calendar' class='com.google.gwt.sample.dynatable.server.SchoolCalendarServiceImpl'/>
?
=>
?
<servlet>
???? <servlet-name>Calendar</servlet-name>
???? <servlet-class>com.google.gwt.sample.dynatable.server.SchoolCalendarServiceImpl</servlet-class>
</servlet>
?
<servlet-mapping>
???? <servlet-name>Calendar</servlet-name>
???? <url-pattern>/calendar</url-pattern>
</servlet-mapping>
?
使用數據源
Hosted Mode 雖然開發起來很方便,但是也有缺點,例如,數據源的配置就有問題。
在GWT Hosted Mode下無法配置數據源,一種可選的方式是使用一個假的數據庫鏈接
管理類,這個類的接口返回Connection,內部以DriverManager的方式實現,等待
后續部署之后再切換到數據源模式。
?
?
日志處理(Log4J)
回想GWT應用程序,client包內部的代碼將會被編譯為客戶端JavaScript代碼,所以這里
不需要記錄日志,也不可能使用Log4j。
但是Server包內的內容在服務器上運行,需要合理的使用日志。
?
?
一個簡單的Login示例
代碼結構如下:
?
└─src
??? └─com
??????? └─jpleasure
??????????? └─gwt
??????????????? └─logon
??????????????????? │? LogonDemo.gwt.xml????????????????????? GWT配置模塊文件
??????????????????? │?
??????????????????? ├─client???????????????????????????????????????? 客戶端代碼包
??????????????????? │? │? LogonDemo.java?????????????????????? GWT代碼的入口點
??????????????????? │? │? LogonDemoController.java????????? 畫面遷移控制類
??????????????????? │? │?
??????????????????? │? ├─exception??????????????????????????????? 異常定義包
??????????????????? │? │????? ApplicationException.java?????? 應用程序異常
??????????????????? │? │?????
??????????????????? │? ├─panel????????????????????????????????????? 頁面Panel包
??????????????????? │? │????? BasePanel.java???????????????????? 基類Panel
??????????????????? │? │????? LogonPanel.java?????????????????? Logon Panel
??????????????????? │? │????? WelcomePanel.java?????????????? Welcome Panel
??????????????????? │? │?????
??????????????????? │? ├─service?????????????????????????????????? 客戶端服務定義包
??????????????????? │? │????? LogonService.java??????????????? 服務接口
??????????????????? │? │????? LogonServiceAsync.java??????? 服務異步調用接口
??????????????????? │? │?????
??????????????????? │? └─so????????????????????????????????????????? Serializable Object 包
??????????????????? │????????? LogonSO.java????????????????????? Logon SO
??????????????????? │?????????
??????????????????? ├─public??????????????????????????????????????? GWT HTML包
??????????????????? │????? LogonDemo.css??????????????????????? CSS定義
??????????????????? │????? LogonDemo.html????????????????????? 主HTML頁面
??????????????????? │?????
??????????????????? └─server???????????????????????????????????????? 服務端Service包
??????????????????????? └─service
??????????????????????????????? LogonServiceImpl.java????????? Logon Service
?
// ApplicationException
package com.jpleasure.gwt.logon.client.exception;
import com.google.gwt.user.client.rpc.SerializableException;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 22:16:17
?* To change this template use File | Settings | File Templates.
?*/
public class ApplicationException extends SerializableException {
??? public ApplicationException() {
??????? super();
??? }
??? public ApplicationException(String msg) {
??????? super(msg);
??? }
}
//BasePanel
package com.jpleasure.gwt.logon.client.panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.jpleasure.gwt.logon.client.LogonDemoController;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 22:01:14
?* To change this template use File | Settings | File Templates.
?*/
public class BasePanel extends VerticalPanel {
??? protected LogonDemoController ldc;
??? public LogonDemoController getLdc() {
??????? return ldc;
??? }
??? public void setLdc(LogonDemoController ldc) {
??????? this.ldc = ldc;
??? }
}
// LogonPanel
package com.jpleasure.gwt.logon.client.panel;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.jpleasure.gwt.logon.client.service.LogonService;
import com.jpleasure.gwt.logon.client.service.LogonServiceAsync;
import com.jpleasure.gwt.logon.client.so.LogonSO;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 21:35:13
?* To change this template use File | Settings | File Templates.
?*/
public class LogonPanel extends BasePanel {
??? private TextBox name;
??? private PasswordTextBox password;
??? private Button logonButton;
??? public LogonPanel() {
??????? HorizontalPanel msgPanel = new HorizontalPanel();
??????? msgPanel.add(new Label("Please input logon information here!"));
??????? HorizontalPanel namePanel = new HorizontalPanel();
??????? Label nameLabel = new Label("Name:");
??????? nameLabel.setPixelSize(100, 20);
??????? namePanel.add(nameLabel);
??????? name = new TextBox();
??????? namePanel.add(name);
??????? HorizontalPanel pwdPanel = new HorizontalPanel();
??????? Label passwordLabel = new Label("Password:");
??????? passwordLabel.setPixelSize(100, 20);
??????? pwdPanel.add(passwordLabel);
??????? password = new PasswordTextBox();
??????? pwdPanel.add(password);
??????? HorizontalPanel btnPanel = new HorizontalPanel();
??????? logonButton = new Button("Logon");
??????? logonButton.addClickListener(new ClickListener() {
??????????? public void onClick(Widget sender) {
??????????????? LogonServiceAsync logonService = LogonService.App.getInstance();
??????????????? LogonSO logonSO = new LogonSO();
??????????????? logonSO.setName(getName());
??????????????? logonSO.setPassword(getPassword());
??????????????? logonService.logon(logonSO, new AsyncCallback() {
??????????????????? public void onFailure(Throwable caught) {
??????????????????????? Window.alert(caught.getMessage());
??????????????????? }
??????????????????? public void onSuccess(Object result) {
??????????????????????? boolean isLogon = ((Boolean)result).booleanValue();
??????????????????????? if(isLogon) {
??????????????????????????? ldc.gotoWelcome();
??????????????????????? }???????????????????? else {
??????????????????????????? Window.alert("logon failed!");
??????????????????????? }
??????????????????? }
??????????????? })? ;
??????????? }
??????? });
??????? btnPanel.add(logonButton);
??????? this.add(msgPanel);
??????? this.add(namePanel);
??????? this.add(pwdPanel);
??????? this.add(btnPanel);
??? }
??? public String getName() {
??????? return this.name.getText();
??? }
??? public String getPassword() {
??????? return this.password.getText();
??? }
}
// WelcomePanel
package com.jpleasure.gwt.logon.client.panel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 21:49:53
?* To change this template use File | Settings | File Templates.
?*/
public class WelcomePanel extends BasePanel {
??? public WelcomePanel() {
??????? Label welcomeLabel = new Label("Welcome to LogonDemo!");
??????? this.add(welcomeLabel);
??????? Button logoutButton = new Button("Logout");
??????? logoutButton.addClickListener(new ClickListener() {
??????????? public void onClick(Widget sender) {
??????????????? ldc.gotoLogon();
??????????? }
??????? });
??????? this.add(logoutButton);
??? }
}
//LogonService
package com.jpleasure.gwt.logon.client.service;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.jpleasure.gwt.logon.client.exception.ApplicationException;
import com.jpleasure.gwt.logon.client.so.LogonSO;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 22:13:09
?* To change this template use File | Settings | File Templates.
?*/
public interface LogonService extends RemoteService {
??? /**
???? * Utility/Convenience class.
???? * Use LogonService.App.getInstance() to access static instance of LogonServiceAsync
???? */
??? public static class App {
??????? private static LogonServiceAsync ourInstance = null;
??????? public static synchronized LogonServiceAsync getInstance() {
??????????? if (ourInstance == null) {
??????????????? ourInstance = (LogonServiceAsync) GWT.create(LogonService.class);
??????????????? ((ServiceDefTarget) ourInstance).setServiceEntryPoint(GWT.getModuleBaseURL() + "com.jpleasure.gwt.logon.LogonDemo/LogonService");
??????????? }
??????????? return ourInstance;
??????? }
??? }
??? public boolean logon(LogonSO logonSO) throws ApplicationException;
}
//LogonServiceAsync
package com.jpleasure.gwt.logon.client.service;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.jpleasure.gwt.logon.client.so.LogonSO;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 22:13:09
?* To change this template use File | Settings | File Templates.
?*/
public interface LogonServiceAsync {
??? void logon(LogonSO logonSO, AsyncCallback async);
}
//?? LogonSO??
package com.jpleasure.gwt.logon.client.so;
import com.google.gwt.user.client.rpc.IsSerializable;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 22:13:52
?* To change this template use File | Settings | File Templates.
?*/
public class LogonSO implements IsSerializable {
??? private String name;
??? private String password;
??? public String getName() {
??????? return name;
??? }
??? public void setName(String name) {
??????? this.name = name;
??? }
??? public String getPassword() {
??????? return password;
??? }
??? public void setPassword(String password) {
??????? this.password = password;
??? }
???
}
??
//? LogonDemo??????????????????????
package com.jpleasure.gwt.logon.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.jpleasure.gwt.logon.client.panel.LogonPanel;
import com.jpleasure.gwt.logon.client.panel.WelcomePanel;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 21:34:12
?* To change this template use File | Settings | File Templates.
?*/
public class LogonDemo implements EntryPoint {
??? public void onModuleLoad() {
??????? DeckPanel mainPanel = new DeckPanel();
??????? LogonDemoController ldc = new LogonDemoController(mainPanel);
??????? LogonPanel logonPanel = new LogonPanel();
??????? logonPanel.setLdc(ldc);
??????? mainPanel.add(logonPanel);
??????? WelcomePanel welcomePanel = new WelcomePanel();
??????? welcomePanel.setLdc(ldc);
??????? mainPanel.add( welcomePanel );
??????? mainPanel.showWidget(0);
??????? RootPanel.get().add(mainPanel);
??? }
}
// LogonDemoController
package com.jpleasure.gwt.logon.client;
import com.google.gwt.user.client.ui.DeckPanel;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 21:58:02
?* To change this template use File | Settings | File Templates.
?*/
public class LogonDemoController {
??? private DeckPanel mainPanel;
??? public LogonDemoController(DeckPanel panel) {
??????? this.mainPanel = panel;
??? }
??? public void gotoWelcome() {
??????? if (mainPanel != null) {
??????????? mainPanel.showWidget(1);
??????? }
??? }
??? public void gotoLogon() {
??????? if (mainPanel != null) {
??????????? mainPanel.showWidget(0);
??????? }
??? }
}
?
// LogonServiceImpl
package com.jpleasure.gwt.logon.server.service;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.jpleasure.gwt.logon.client.exception.ApplicationException;
import com.jpleasure.gwt.logon.client.service.LogonService;
import com.jpleasure.gwt.logon.client.so.LogonSO;
/**
?* Created by IntelliJ IDEA.
?* User: ma.zhao@dl.cn
?* Date: 2007-8-27
?* Time: 22:13:10
?* To change this template use File | Settings | File Templates.
?*/
public class LogonServiceImpl extends RemoteServiceServlet implements LogonService {
??? public boolean logon(LogonSO logonSO) throws ApplicationException {
??????? if (logonSO.getName() != null && logonSO.getName().length() > 0? &&
??????????????? logonSO.getPassword() != null && logonSO.getPassword().length() > 0 &&
??????????????? logonSO.getName().equals(logonSO.getPassword())) {
??????????? return true;
??????? } else if ("ex".equals(logonSO.getName())) {
??????????? throw new ApplicationException("Logon Exception!");
??????? } else {
??????????? return false;
??????? }
??? }
}
?
//LogonDemo.gwt.xml
<module>
??? <inherits name='com.google.gwt.user.User'/>
??? <entry-point class='com.jpleasure.gwt.logon.client.LogonDemo'/>
??? <servlet path="/com.jpleasure.gwt.logon.LogonDemo/LogonService"
???????????? class="com.jpleasure.gwt.logon.server.service.LogonServiceImpl"/>
</module>
?
安全相關
參看:http://groups.google.com/group/Google-Web-Toolkit/web/security-for-gwt-applications
在GWT中所有的畫面都是由Panel實現的,而所有的Panel都會被編譯為JavaScript和Html代碼,這些代碼在程序運行的開始就會
下載到客戶的瀏覽器中,雖然這些JavaScript代碼很難閱讀,但是畢竟以影下載到了客戶的環境中,所以在本質上GWT是不安全的。(請大家討論)
?另外由于JavaScript的靈活性,可以動態的在畫面上創建鏈接,image等,所以數據也有可能別提交到其他的服務器,而非下載的服務器。
?
另外,由于Panel,Action等最終都編譯為了JavaScript,所以關于用戶權限等信息最好不要放在Panel,Action內部。最好能放在server包內部,
在客戶端對Service的每次調用的開始確認用戶的權限。
?
?
GWT開發使用的工具
免費
??? Netbeans + gwt4nb
??? 參看:http://www.javapassion.com/handsonlabs/ajaxgwtintro/
??? 優點:直接屏蔽了GWT Shell(Hosted Mode)開發的方式,可以方便的以普通Java Web 應用程序開發的凡是進行GWT開發。
??? 缺點:不支持界面的拖拽編輯,同時繼承了NB 5.5 的缺點,對JSP,HTML,JavaScript編輯器支持不足。
?
?
??? Eclipse + Cypal Studio for GWT
??? 參看:http://www.ibm.com/developerworks/library/os-eclipse-ajaxcypal/index.html
??? 優點:可以方便的以普通Java Web 應用程序開發的凡是進行GWT開發。支持兩種運行方式:GWT Shell運行和Web方式運行。
?
收費:
??? Ingellij IDEA 6
??? 參看:http://www.jetbrains.com/idea/training/demos/GWT.html
?
??? Eclipse + GWT Builder
??? http://www.instantiations.com/gwtdesigner/
?
?
?
?
?
?
?一些技巧/注意事項
PasswordTextBox 不像TextBox那樣有長度限制,所以在需要限制PasswordTextBox輸入長度的時候,可以有兩種實現方式,第一是,添加一個KeyboradListener,在KeyPress(KeyUp,KeyDown等)事件的時候判斷長度,然后substring,另一種方式是,在提交的時候判斷,讓客戶自己修改。確實有些不太方便,呵呵。
?
?
?(未完待續)
總結
- 上一篇: 数学思维游戏两则:Gabriel喇叭、世
- 下一篇: protues仿真8086常见问题