使用 Oracle JDeveloper 構建您的第一個 GWT Web 應用程序
到目前為止,您已經了解了 GWT 的工作方式;現在,讓我們編碼示例 Web 應用程序。
示例應用程序是一個工作列表管理器。其特性十分簡單:創建、編輯、刪除工作列表并對其進行優先級排列。我們選擇了該示例是因為它很容易理解,然而其實施涵蓋了大量 GWT 的特性。
下面是最終應用程序的屏幕快照:
第 1 步:安裝 GWT
從 Google 的 Web 站點 http://code.google.com/webtoolkit/ 下載 GWT。在寫本文時,GWT 推出的是 Windows 和 Linux 版本。GWT 是特定于平臺的,因為其托管模式在 Firefox 的修改版本中工作,該版本本身依賴于平臺。(我們可以在 Apple 計算機上成功地使用 GWT 的 Linux 版本,但是托管模式不起作用。)
GWT 下載形式是一個歸檔文件,您必須使用 Linux 上的 tar -xvf 命令或者 Windows 上的解壓縮工具進行解壓縮。這就是您安裝該工具包需要做的所有工作。
第 2 步:運行 applicationCreator 腳本
打開命令行,轉至 GWT 的安裝目錄。該目錄包含 applicationCreator 腳本,我們將使用該腳本啟動我們的應用程序。由于我們希望應用程序存儲在 Oracle Technology Network 目錄中,因此我們將“-out otn”作為參數添加到腳本中。在 Linux 上,鍵入:
./applicationCreator -out otn otn.todo.client.TodoApp
在 Windows 上,使用:
applicationCreator -out otn otn.todo.client.TodoApp
該腳本生成基本的項目結構 — 請求的應用程序類中的示例“Hello word”代碼以及兩個腳本:TodoApp-shell(用于在托管模式下運行應用程序)和 TodoApp-compile(用于打包應用程序以便在 Web 模式下使用)。
第 3 步:在 JDeveloper 中打開項目
啟動 JDeveloper 并創建一個新的 Web 項目:

單擊 Next 按鈕。JDeveloper 將詢問新項目的位置。使用應用程序的名稱作為 Project Name,選擇應用程序根目錄(如步驟 2 的定義)作為 Directory Name:

單擊 Next 按鈕,并驗證您的應用程序是 J2EE 1.4 應用程序:

單擊
Next 按鈕,并選擇您的項目 Web 屬性:Document Root 是當前項目的 www 目錄,J2EE Web Application Name 和 J2EE Context Root 都是項目名稱:

這將創建 JDeveloper 項目,但是將出現某些編譯錯誤,因為 GWT 的庫未包含在項目類路徑中。在項目屬性中,選擇左側端樹的 Libraries 節點,并添加 gwt-user.jar 庫:
您的項目現在應該可以編譯,看起來與以下內容相似:

編寫客戶端代碼
上面的 applicationCreator 腳本創建了一個基本的“Hello world”應用程序,可在 otn.todo.client 程序包中使用。下面是其主要方法:
public void onModuleLoad() {
final Button button = new Button("Click me");
final Label label = new Label();
button.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if (label.getText().equals(""))
label.setText("Hello World!");
else
label.setText("");
}
});
RootPanel.get("slot1").add(button);
RootPanel.get("slot2").add(label);
}
}
該方法將創建一個按鈕“Click Me”。單擊該按鈕后,將顯示“Hello World”。
該方法分為三部分:
- 創建 Button 和 Label 小部件
- 創建 ClickListener 對象。該代碼與您用 Swing 編寫的內容很接近;如果您具有桌面 Java 背景則更容易理解。
- 在 HTML 頁上顯示小部件:slot1 和 slot2 都是該頁上的 HTML 元素
用作框架的 HTML 頁位于 src/otn/todo/public 目錄中。它將兩個 HTML 元素(slot1 和 slot2)定義為表單元格。
在托管模式下運行和調試
現在您已經創建了應用程序并且已經看到其生成的內容,結下來讓我們來執行它。
您可以通過從命令行使用 TodoApp-shell 腳本輕松地運行該項目。雖然這是啟動應用程序的很好途徑,但是您可能更喜歡直接從 JDeveloper 內啟動應用程序。為此,單擊 Run 菜單,選擇 Choose Active Run Configuration > Manage Run Configurations。編輯默認的運行配置并使用以下命令:
- 對于 Default Run Target:使用 com.google.gwt.dev.GWTShell,它在特定于平臺的 GWT jar 內。在 Linux 上,它類似以下內容:
path.to.your.gwt.installation.directory/gwt-devlinux.jar!/com/google/gwt/dev/GWTShell.class
在 Windows 上,它類似以下內容: path.to.your.gwt.installation.directory/gwt-dev-windows.jar!/com/google/gwt/dev/GWTShell.class
- 對于 Program Arguments,使用:
-out path.to.your.gwt.installation.directory/otn/www otn.todo.TodoApp/TodoApp.html
- 對于 Run Directory,使用
path.to.your.gwt.installation.directory/otn
最終結果類似以下內容:

要運行您的應用程序,您必須向其類路徑中再添加兩個庫:GWT 特定于平臺的 jar 和應用程序的 src 目錄:

您現在應能夠從 JDeveloper 運行應用程序了。
這是一個很復雜的設置,但是令人欣慰的是,您可以重新使用它對應用程序進行調試。使用 Debug 按鈕而不是 Run 按鈕。然后,您可以象平常一樣使用調試器 — 設置斷點、逐步執行代碼等:

關于該特性給人印象很深的是,您可以通過標準的 JDeveloper 調試器調試用 Java 編寫的客戶端代碼。
擴展您的 GWT Web 應用程序
現在您已經創建了一個簡單的 GWT Web 應用程序,讓我們通過兩個最常用的 GWT 特性對其進行擴展:RPC 機制(該機制允許應用程序調用服務器端代碼)和 History 對象(通過該對象,用戶可精確處理瀏覽器的 Back 按鈕)。
使用 RPC 進行客戶端和服務器之間的數據交換
到目前為止,您只創建了應用程序的客戶端代碼:使用 GWT 編譯器,您已經生成了大量 HTML 和 JavaScript 文件,它們將在最終用戶的瀏覽器中運行。但是,如果該應用程序不能與服務器通信就沒有什么用處了。
使用 GWT,客戶端/服務器通信就是對 servlet 進行編碼并使其與應用程序通信。下面是您要做的工作。
創建一個定義您的服務的接口。該接口必須擴展 Google 的 com.google.gwt.user.client.rpc.RemoteService 接口,并可以放到客戶端程序包(本例為 otn.todo.client)中。
然后,對接口進行編碼以便允許您在服務器上讀取和寫入工作列表:
package otn.todo.client;
import java.util.List;
import com.google.gwt.user.client.rpc.RemoteService;
public interface TodoListBackupService extends RemoteService {
/**
* Save the to-do list on the server.
*/
void saveTodoList(List todoList);
/**
* Get the to-do list on the server.
*/
List getTodoList();
}
對 Servlet 進行編碼。在服務器端,您必須編碼出具有以下特征的類:
- 擴展 Google 的 com.google.gwt.user.server.rpc.RemoteServiceServlet 類(該類反過來會擴展 Java 的 javax.servlet.http.HttpServlet,有效使其成為 servlet)
- 實施步驟 1 中編寫的接口
- 位于服務器程序包(本例為 otn.todo.server)中
package otn.todo.server;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import otn.todo.client.Todo;
import otn.todo.client.TodoListBackupService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class TodoListBackupServiceImpl extends RemoteServiceServlet implements
TodoListBackupService {
private static final String TODOLIST_KEY = "TODOLIST_KEY";
public void saveTodoList(List todoList) {
HttpServletRequest request = this.getThreadLocalRequest();
HttpSession session = request.getSession();
session.setAttribute(TODOLIST_KEY, todoList);
}
public List getTodoList() {
HttpServletRequest request = this.getThreadLocalRequest();
HttpSession session = request.getSession();
if (session.getAttribute(TODOLIST_KEY) == null) {
List todoList = new ArrayList();
Todo todo = new Todo("Hello from the server");
todoList.add(todo);
return todoList;
} else {
return (List) session.getAttribute(TODOLIST_KEY);
}
}
}
該 servlet 在用戶的 HttpSession 中只存儲工作列表;這當然是保存數據的基本方法。在一般的應用程序中,您可以使用 JNDI 訪問 EJB,或者使用任何經典模式從 servlet 訪問業務服務。
最后,您必須在 servlet 容器內配置該 servlet。如果您使用的是 GWT shell,您可以在 *.gwt.xml 配置文件中進行配置,本例中該配置文件為 TodoApp.gwt.xml:
<module>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<!-- Specify the app entry point class. -->
<entry-point class='otn.todo.client.TodoApp'/>
<servlet path="/todoListBackupService" class="otn.todo.server.TodoListBackupServiceImpl"/>
</module>
如果您希望在其他應用服務器(如 OC4J)中對其進行配置,只需將平常的 XML 配置添加到 WEB-INF/web.xml 文件中即可:
<servlet>
<servlet-name>TodoListBackupService</servlet-name>
<servlet-class>otn.todo.server.TodoListBackupServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TodoListBackupService</servlet-name>
<url-pattern>/todoListBackupService</url-pattern>
</servlet-mapping>
添加一些粘合劑。我們需要的粘合劑是 Async 類,它必須遵循幾個規則:
- 位于客戶端程序包(otn.todo.client)中。
- 其名稱與步驟 1 中描述的接口的名稱相同,最后面添加 Async。
- 其方法與步驟 1 中描述的接口的方法相同,但是它們都回調一個附加參數 com.google.gwt.user.client.rpc.AsyncCallback。
package otn.todo.client;
import java.util.List;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface TodoListBackupServiceAsync {
/**
* Save the to-do list on the server.
*/
void saveTodoList(List todoList, AsyncCallback callback);
/**
* Get the to-do list on the server.
*/
void getTodoList(AsyncCallback callback);
}
在應用程序內使用該類。要從客戶端應用程序內訪問服務器端代碼,使用 com.google.gwt.core.client.GWT 類,該類可以創建一個很特殊的對象:
TodoListBackupServiceAsync todoListBackupService = (TodoListBackupServiceAsync) GWT.create(TodoListBackupService.class);
這將在運行時創建一個實施兩個接口的類:
- 我們剛剛在步驟 3 中進行編碼的 Async 接口
- Google 的 com.google.gwt.user.client.rpc.ServiceDefTarget 接口
第二個接口用于配置類以便它可以指向步驟 2 中定義的 servlet:
ServiceDefTarget endpoint = (ServiceDefTarget) todoListBackupService; endpoint.setServiceEntryPoint("/todoListBackupService");
現在您已經將該對象配置為可訪問服務器端服務,讓我們來訪問服務。如您在步驟 3 中所見,Async 接口允許您通過添加 AsyncCallback 回調參數訪問在服務中定義的所有方法。該參數用于定義應用程序的行為,具體取決于服務器端調用的成功或失敗:
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
printTodoList();
}
public void onFailure(Throwable caught) {
Window.alert("Warning : the to-do list could not be saved on the server. Maybe the server is down.");
}
};
讓我們把它們全都放在一起。下面是訪問 TodoListBackupService 業務服務的兩個客戶端方法的完整代碼:一個用于在服務器端保存工作列表,另一個用于讀取該列表:
/**
* Update the to-do list with data from the server.
*/
private void updateTodoListFromServer() {
TodoListBackupServiceAsync todoListBackupService =
(TodoListBackupServiceAsync)GWT.create(TodoListBackupService.class);
ServiceDefTarget endpoint = (ServiceDefTarget)todoListBackupService;
endpoint.setServiceEntryPoint("/todoListBackupService");
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
todoList = (List)result;
saveTodoListInHistory();
}
public void onFailure(Throwable caught) {
Todo todo =
new Todo("ERROR!! Server could not be reached.");
todoList.add(todo);
saveTodoListInHistory();
}
};
todoListBackupService.getTodoList(callback);
}
/**
* Save the to-do list on the server.
*/
private void saveTodoListOnServer() {
saveTodoListInHistory();
TodoListBackupServiceAsync todoListBackupService =
(TodoListBackupServiceAsync)GWT.create(TodoListBackupService.class);
ServiceDefTarget endpoint = (ServiceDefTarget)todoListBackupService;
endpoint.setServiceEntryPoint("/todoListBackupService");
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
printTodoList();
}
public void onFailure(Throwable caught) {
Window.alert("Warning : the to-do list could not be saved on the server. Maybe the server is down.");
}
};
todoListBackupService.saveTodoList(todoList, callback);
}
示例應用程序在啟動時進行服務器端調用。該調用將返回用戶的 HttpSession 中保存的最新工作列表,或者包含“Hello from the server”工作的新工作列表:
管理 Back 按鈕
在高端 Web 應用程序中,瀏覽器的 Back 按鈕經常斷開。經典的 Ajax 應用程序不支持返回前一 Web 頁的標準 Web 行為。
另一方面,GWT 允許對 Back 按鈕進行編程處理。這是一個功能強大卻又很難處理的特性,我們將在示例應用程序中對其進行探究。提議是將 Back 按鈕用作 Undo 按鈕:單擊該按鈕將顯示最新事件之前的工作列表。同樣地,Forward 按鈕將用作 Redo 按鈕。
實施 HistoryListener 接口。要以編程方式管理 Back 按鈕,GWT 應用程序必須實施 com.google.gwt.user.client.HistoryListener 接口。這將強制編寫 onHistoryChanged(String _historyToken) 方法:
public class TodoApp implements EntryPoint, HistoryListener {
/**
* This method is called whenever the application's history changes.
*/
public void onHistoryChanged(String _historyToken) {
if (Integer.parseInt(_historyToken) + 1 != historyToken) {
if (historyMap.get(_historyToken) != null) {
historyToken = Integer.parseInt(_historyToken);
todoList = (List) historyMap.get(_historyToken);
}
}
printTodoList();
}
該方法意味著當瀏覽器的歷史記錄更改時接收事件。您必須將其作為監聽器添加到 GWT 的 History 對象中。該操作通常在 onModuleLoad() 方法中完成,以便 History 對象在啟動時正確初始化:
/**
* This is the entry point method.
*/
public void onModuleLoad() {
History.addHistoryListener(this);
}
現在,每次瀏覽器的歷史記錄更改時都會調用 onHistoryChanged(String _historyToken) 方法。
該方法可以根據作為參數傳遞的令牌重新創建應用程序的狀態。本例中,您將使用該令牌作為密鑰來查找存儲在歷史地圖中的工作列表。
向歷史記錄中添加條目。要使 onHistoryChanged(String _historyToken) 方法起作用,您必須預先在歷史記錄中存儲一些條目。
利用 History 對象很容易實現,可以使用其靜態 newItem(String historyToken) 方法:
private void saveTodoListInHistory() {
List todoListClone = new ArrayList();
Iterator it = todoList.iterator();
while (it.hasNext()) {
Todo todo = (Todo) it.next();
todoListClone.add(todo.clone());
}
historyMap.put(String.valueOf(historyToken), todoListClone);
History.newItem(String.valueOf(historyToken));
historyToken++;
}
在本例中,您將應用程序狀態存儲在了地圖中,因此使用歷史記錄令牌可以找到它。注意,您使用了一個數字作為歷史記錄令牌,也可以改用任何字符串。
部署您的 Web 應用程序
要部署通過 GWT 構建的 Web 應用程序,您需要編譯客戶端代碼,在 Web 應用程序的 .war 文件中打包結果,然后將 .war 文件部署到相應的應用服務器 OC4J 上。
編譯客戶端代碼
編譯客戶端代碼的方法有多種。使用 applicationCreator 腳本后,GWT 將創建一個名為 TodoApp-compile 的 shell 腳本。您可以從命令行啟動該腳本。與 TodoApp-shell 一樣,這是編譯應用程序的很好方法;但是,您可能更喜歡直接從 JDeveloper 內部啟動該腳本。
另一種編譯代碼的方法使以托管模式執行應用程序,以便直接從 JDeveloper 編譯代碼。您的應用程序的窗口的工具欄包含編譯/瀏覽按鈕,與下圖類似:

在編譯過程結束時,您的默認 Web 瀏覽器將打開以便您可以測試結果。GWT 開發 shell 的窗口將顯示編譯是否成功:
無論您使用何種方法編譯代碼,您都可以在項目的 www/otn.todo.TodoApp 中找到生成的文件。
最后一種編譯代碼的方法是使用 Ant。GWT 不提供特定的 Ant 任務,但是您可以通過標準的 Java Ant 任務啟動任何 Java 類(例如 GWTCompiler)。首先,定義包含 GWT jar 的路徑:
<path id="project.class.path">
<pathelement path="${java.class.path}/"/>
<pathelement location="src"/>
<pathelement path="/your/path/to/gwt-user.jar"/>
<pathelement path="/your/path/to/gwt-dev-linux.jar"/>
<!-- ... -->
</path>
現在,定義專用于編譯客戶端代碼的任務:
<target name="GWTcompile">
<java classpat
classname="com.google.gwt.dev.GWTCompiler"
fork="true">
<arg value="-out"/>
<arg value="${gwt.output.dir}"/>
<arg value="${entry.point.class}"/>
</java>
</target>
在屬性文件中設置 gwt.output.dir 和 entry.point.class 變量,如下所示:
gwt.output.dir=www
entry.point.class=otn.todo.TodoApp
最后,在 Ant 腳本中聲明屬性文件(此處為 build.properties),如下所示:
<property file="build.properties"/>
您可以通過在任務的 Context 菜單中選擇 Run Target GWTCompile 直接啟動該新的目標:
GWTcompile:
[java] Output will be written into www\otn.todo.TodoApp
[java] Compilation succeeded
BUILD SUCCESSFUL
在 OC4J 中部署
編譯完應用程序之后,在 OC4J 下部署它只需創建一個適當的部署配置文件。如果您按照前面描述的步驟進行操作,您應該已經具有一個默認的部署配置文件。如果沒有,只需選擇 File > New...> Deployment Profiles > WAR File,創建一個新的配置文件。
使用您的配置,一切都應該正常工作。但是,如果您遇到任何問題,請查看以下常見的錯誤:
- 在 Project Properties 的 Project Content > Web Application 中,HTML Root Directory 應該是應用程序的 www 目錄(在該目錄中,GWT 將對應用程序進行編譯以便在托管模式下運行)。
- 在部署配置文件的 File Groups > WEB-INF/lib > Contributors 中,應該添加 gwt-user.jar。該 jar 文件包括 J2EE 規范中的 javax.servlet 程序包。這在本例中沒有引起任何問題;但是,您通常不應將這些類部署在 Web 應用程序中,因為它們會引起麻煩。如果發生了這種情況,GWT 還提供有一個 gwt-servlet.jar 文件,它是沒有 javax.servlet 程序包的 gwt-user.jar。
- Web 應用程序的上下文根影響 GWT 的 RPC 機制。如果 GWT 客戶端應用程序要與服務器通信(如“使用 RPC 進行客戶端和服務器之間的數據交換”所述),它必須可以找到服務器。這就是我們已經討論過的 endpoint.setServiceEntryPoint("") 方法的目的。在本例中,我們將應用程序部署到了服務器的根上,這就是 GWT shell 默認的工作方式。但是,如果您將應用程序部署到 TodoApp Web 上下文(在部署配置文件的一般屬性中),請將端點設置為 /TodoApp/todoListBackupService 而不要設置為 /todoListBackupService。不用忘記該 URL 還應正確映射到應用程序的 web.xml 文件中(如前所述)。
- 假設 OC4J 已在系統上正確安裝,將應用程序部署到服務器是很簡單的:只需右鍵單擊部署配置文件,并選擇 Deploy to OC4J。
部署的應用程序有兩部分:
- 客戶端應用程序是一組先前編譯的 HTML 和 JavaScript 文件。(請參見“編譯客戶端代碼”部分。)OC4J 充當經典的 Web 服務器,將這些文件傳遞給用戶。
- 服務器端應用程序基本上是一個處理 RPC 通信的 servlet。在 OC4J 中部署后,該 servlet 可以訪問諸如 EJB 或 JMS 提供商等企業資源。
性能智能化為靜態資源提供了高效服務;應用程序的主要性能瓶頸來源于客戶端/服務器通信。但是由于有 OC4J,我們可以訪問一些有趣的服務器端性能圖形:
如該圖形所顯示的,在正常負載下(每秒幾個請求),應用程序的服務器端部分在平均不到 4 ms 的時間內進行響應,這是很難得的結果。
posted on 2006-11-24 10:31
壞男孩 閱讀(2273)
評論(2) 編輯 收藏 所屬分類:
新知識學習