使用 Oracle JDeveloper 構(gòu)建您的第一個 GWT Web 應(yīng)用程序
到目前為止,您已經(jīng)了解了 GWT 的工作方式;現(xiàn)在,讓我們編碼示例 Web 應(yīng)用程序。
示例應(yīng)用程序是一個工作列表管理器。其特性十分簡單:創(chuàng)建、編輯、刪除工作列表并對其進行優(yōu)先級排列。我們選擇了該示例是因為它很容易理解,然而其實施涵蓋了大量 GWT 的特性。
下面是最終應(yīng)用程序的屏幕快照:
第 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 腳本
打開命令行,轉(zhuǎn)至 GWT 的安裝目錄。該目錄包含 applicationCreator 腳本,我們將使用該腳本啟動我們的應(yīng)用程序。由于我們希望應(yīng)用程序存儲在 Oracle Technology Network 目錄中,因此我們將“-out otn”作為參數(shù)添加到腳本中。在 Linux 上,鍵入:
./applicationCreator -out otn otn.todo.client.TodoApp
在 Windows 上,使用:
applicationCreator -out otn otn.todo.client.TodoApp
該腳本生成基本的項目結(jié)構(gòu) — 請求的應(yīng)用程序類中的示例“Hello word”代碼以及兩個腳本:TodoApp-shell(用于在托管模式下運行應(yīng)用程序)和 TodoApp-compile(用于打包應(yīng)用程序以便在 Web 模式下使用)。
第 3 步:在 JDeveloper 中打開項目
啟動 JDeveloper 并創(chuàng)建一個新的 Web 項目:

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

單擊 Next 按鈕,并驗證您的應(yīng)用程序是 J2EE 1.4 應(yīng)用程序:

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

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

編寫客戶端代碼
上面的 applicationCreator 腳本創(chuàng)建了一個基本的“Hello world”應(yīng)用程序,可在 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);
}
}
該方法將創(chuàng)建一個按鈕“Click Me”。單擊該按鈕后,將顯示“Hello World”。
該方法分為三部分:
- 創(chuàng)建 Button 和 Label 小部件
- 創(chuàng)建 ClickListener 對象。該代碼與您用 Swing 編寫的內(nèi)容很接近;如果您具有桌面 Java 背景則更容易理解。
- 在 HTML 頁上顯示小部件:slot1 和 slot2 都是該頁上的 HTML 元素
用作框架的 HTML 頁位于 src/otn/todo/public 目錄中。它將兩個 HTML 元素(slot1 和 slot2)定義為表單元格。
在托管模式下運行和調(diào)試
現(xiàn)在您已經(jīng)創(chuàng)建了應(yīng)用程序并且已經(jīng)看到其生成的內(nèi)容,結(jié)下來讓我們來執(zhí)行它。
您可以通過從命令行使用 TodoApp-shell 腳本輕松地運行該項目。雖然這是啟動應(yīng)用程序的很好途徑,但是您可能更喜歡直接從 JDeveloper 內(nèi)啟動應(yīng)用程序。為此,單擊 Run 菜單,選擇 Choose Active Run Configuration > Manage Run Configurations。編輯默認(rèn)的運行配置并使用以下命令:
- 對于 Default Run Target:使用 com.google.gwt.dev.GWTShell,它在特定于平臺的 GWT jar 內(nèi)。在 Linux 上,它類似以下內(nèi)容:
path.to.your.gwt.installation.directory/gwt-devlinux.jar!/com/google/gwt/dev/GWTShell.class
在 Windows 上,它類似以下內(nèi)容: 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
最終結(jié)果類似以下內(nèi)容:

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

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

關(guān)于該特性給人印象很深的是,您可以通過標(biāo)準(zhǔn)的 JDeveloper 調(diào)試器調(diào)試用 Java 編寫的客戶端代碼。
擴展您的 GWT Web 應(yīng)用程序
現(xiàn)在您已經(jīng)創(chuàng)建了一個簡單的 GWT Web 應(yīng)用程序,讓我們通過兩個最常用的 GWT 特性對其進行擴展:RPC 機制(該機制允許應(yīng)用程序調(diào)用服務(wù)器端代碼)和 History 對象(通過該對象,用戶可精確處理瀏覽器的 Back 按鈕)。
使用 RPC 進行客戶端和服務(wù)器之間的數(shù)據(jù)交換
到目前為止,您只創(chuàng)建了應(yīng)用程序的客戶端代碼:使用 GWT 編譯器,您已經(jīng)生成了大量 HTML 和 JavaScript 文件,它們將在最終用戶的瀏覽器中運行。但是,如果該應(yīng)用程序不能與服務(wù)器通信就沒有什么用處了。
使用 GWT,客戶端/服務(wù)器通信就是對 servlet 進行編碼并使其與應(yīng)用程序通信。下面是您要做的工作。
創(chuàng)建一個定義您的服務(wù)的接口。該接口必須擴展 Google 的 com.google.gwt.user.client.rpc.RemoteService 接口,并可以放到客戶端程序包(本例為 otn.todo.client)中。
然后,對接口進行編碼以便允許您在服務(wù)器上讀取和寫入工作列表:
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 進行編碼。在服務(wù)器端,您必須編碼出具有以下特征的類:
- 擴展 Google 的 com.google.gwt.user.server.rpc.RemoteServiceServlet 類(該類反過來會擴展 Java 的 javax.servlet.http.HttpServlet,有效使其成為 servlet)
- 實施步驟 1 中編寫的接口
- 位于服務(wù)器程序包(本例為 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 中只存儲工作列表;這當(dāng)然是保存數(shù)據(jù)的基本方法。在一般的應(yīng)用程序中,您可以使用 JNDI 訪問 EJB,或者使用任何經(jīng)典模式從 servlet 訪問業(yè)務(wù)服務(wù)。
最后,您必須在 servlet 容器內(nèi)配置該 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>
如果您希望在其他應(yīng)用服務(wù)器(如 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 類,它必須遵循幾個規(guī)則:
- 位于客戶端程序包(otn.todo.client)中。
- 其名稱與步驟 1 中描述的接口的名稱相同,最后面添加 Async。
- 其方法與步驟 1 中描述的接口的方法相同,但是它們都回調(diào)一個附加參數(shù) 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);
}
在應(yīng)用程序內(nèi)使用該類。要從客戶端應(yīng)用程序內(nèi)訪問服務(wù)器端代碼,使用 com.google.gwt.core.client.GWT 類,該類可以創(chuàng)建一個很特殊的對象:
TodoListBackupServiceAsync todoListBackupService = (TodoListBackupServiceAsync) GWT.create(TodoListBackupService.class);
這將在運行時創(chuàng)建一個實施兩個接口的類:
- 我們剛剛在步驟 3 中進行編碼的 Async 接口
- Google 的 com.google.gwt.user.client.rpc.ServiceDefTarget 接口
第二個接口用于配置類以便它可以指向步驟 2 中定義的 servlet:
ServiceDefTarget endpoint = (ServiceDefTarget) todoListBackupService; endpoint.setServiceEntryPoint("/todoListBackupService");
現(xiàn)在您已經(jīng)將該對象配置為可訪問服務(wù)器端服務(wù),讓我們來訪問服務(wù)。如您在步驟 3 中所見,Async 接口允許您通過添加 AsyncCallback 回調(diào)參數(shù)訪問在服務(wù)中定義的所有方法。該參數(shù)用于定義應(yīng)用程序的行為,具體取決于服務(wù)器端調(diào)用的成功或失敗:
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.");
}
};
讓我們把它們?nèi)挤旁谝黄稹O旅媸窃L問 TodoListBackupService 業(yè)務(wù)服務(wù)的兩個客戶端方法的完整代碼:一個用于在服務(wù)器端保存工作列表,另一個用于讀取該列表:
/**
* 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);
}
示例應(yīng)用程序在啟動時進行服務(wù)器端調(diào)用。該調(diào)用將返回用戶的 HttpSession 中保存的最新工作列表,或者包含“Hello from the server”工作的新工作列表:
管理 Back 按鈕
在高端 Web 應(yīng)用程序中,瀏覽器的 Back 按鈕經(jīng)常斷開。經(jīng)典的 Ajax 應(yīng)用程序不支持返回前一 Web 頁的標(biāo)準(zhǔn) Web 行為。
另一方面,GWT 允許對 Back 按鈕進行編程處理。這是一個功能強大卻又很難處理的特性,我們將在示例應(yīng)用程序中對其進行探究。提議是將 Back 按鈕用作 Undo 按鈕:單擊該按鈕將顯示最新事件之前的工作列表。同樣地,F(xiàn)orward 按鈕將用作 Redo 按鈕。
實施 HistoryListener 接口。要以編程方式管理 Back 按鈕,GWT 應(yīng)用程序必須實施 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();
}
該方法意味著當(dāng)瀏覽器的歷史記錄更改時接收事件。您必須將其作為監(jiān)聽器添加到 GWT 的 History 對象中。該操作通常在 onModuleLoad() 方法中完成,以便 History 對象在啟動時正確初始化:
/**
* This is the entry point method.
*/
public void onModuleLoad() {
History.addHistoryListener(this);
}
現(xiàn)在,每次瀏覽器的歷史記錄更改時都會調(diào)用 onHistoryChanged(String _historyToken) 方法。
該方法可以根據(jù)作為參數(shù)傳遞的令牌重新創(chuàng)建應(yīng)用程序的狀態(tài)。本例中,您將使用該令牌作為密鑰來查找存儲在歷史地圖中的工作列表。
向歷史記錄中添加條目。要使 onHistoryChanged(String _historyToken) 方法起作用,您必須預(yù)先在歷史記錄中存儲一些條目。
利用 History 對象很容易實現(xiàn),可以使用其靜態(tài) 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++;
}
在本例中,您將應(yīng)用程序狀態(tài)存儲在了地圖中,因此使用歷史記錄令牌可以找到它。注意,您使用了一個數(shù)字作為歷史記錄令牌,也可以改用任何字符串。
部署您的 Web 應(yīng)用程序
要部署通過 GWT 構(gòu)建的 Web 應(yīng)用程序,您需要編譯客戶端代碼,在 Web 應(yīng)用程序的 .war 文件中打包結(jié)果,然后將 .war 文件部署到相應(yīng)的應(yīng)用服務(wù)器 OC4J 上。
編譯客戶端代碼
編譯客戶端代碼的方法有多種。使用 applicationCreator 腳本后,GWT 將創(chuàng)建一個名為 TodoApp-compile 的 shell 腳本。您可以從命令行啟動該腳本。與 TodoApp-shell 一樣,這是編譯應(yīng)用程序的很好方法;但是,您可能更喜歡直接從 JDeveloper 內(nèi)部啟動該腳本。
另一種編譯代碼的方法使以托管模式執(zhí)行應(yīng)用程序,以便直接從 JDeveloper 編譯代碼。您的應(yīng)用程序的窗口的工具欄包含編譯/瀏覽按鈕,與下圖類似:

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