Google App Engine for Java: 第 1 部分:運轉起來!
Google App Engine 曾經一度是 Python 開發人員 的專利。那是一段黑暗的歲月。Google Inc. 在 2009 年 4 月向 Java™ 開發人員開放了其云計算平臺。在這個共分三部分的系列文章中,Java 技術作家兼培訓師 Rick Hightower 將帶領您了解這個可靠、健壯、有趣的平臺,并將它用于基于 Java 的開發。在本文中,您將了解到為什么 Google App Engine for Java 將成為您構建高度可伸縮的殺手級應用程序的開發平臺,然后開始使用 Google Plugin for Eclipse 構建兩個示例應用程序:一個基于 Google Web Toolkit (GWT),另一個基于 Java Servlet API。您將了解到 Google App Engine for Java 帶來的巨大改變,包括從頭構建應用程序以及將它部署到高達 5 百萬個視圖。(這僅僅是免費版提供的功能)。
頭腦里出現的想法就好像是被蚊蟲叮了一樣:您需要抓癢癢,這樣做才感覺舒服一些。作為軟件開發人員,我們花了大量時間來為各種應用程序捕捉想法。很有趣,不是嗎?困難的部分在于想出如何使一個軟件產品獲得成功。需要構想出一些東西并隨后 實現它。考慮其他的問題(即沒有被抓過的癢處)只會讓人灰心。
許多應用程序從未獲得進展的一個原因就是無法滿足對基礎設施的需求。一個得到良好維護的基礎設施常常需要一個由系統管理員、DBA 和網絡工程師組成的團隊,到目前為止,這一直是企業獲得成功的主因。即使雇用第三方來托管您的應用程序也絕不簡單:如果應用程序大受歡迎并且突然之間獲得很高的點擊率,會發生什么?所謂的 Slashdot 效應 可以幫助獲得一個好的想法,僅僅因為很難預測加載峰值。
但是,眾所周知,事物是不斷變化的。Web 服務的基礎在不斷演變,如今它為我們帶來了許多新方式,通過云計算和強大的平臺即服務/PAAS 更輕松地構建、部署和發布應用程序。現在,在編寫下一個 Twitter 并將其部署到云平臺上時,它將不斷擴展。哇,感覺很棒!
在這份共分三部分的系列文章中,您將了解到為什么云計算/PAAS 對于軟件開發來說是如此重要的一個演變,同時開始使用一種令人振奮的新平臺進行 Java 開發:Google App Engine for Java,目前可以使用它的預覽版。我將首先對 App Engine for Java 進行概述,包括它所提供的應用程序服務的類型。之后將直接查看第一個應用程序示例(共兩個),它使用 App Engine for Java Google Plugin for Eclipse。第一個應用程序示例將利用 App Engine for Java 對 Java Servlet API 的支持,第二個示例將利用對 GWT 的支持。在 第 2 部分 中,您將利用 App Engine for Java 對 servlets 和 GWT 提供的支持創建一個小型的聯系人管理應用程序。在第 3 部分中,將使用自己構建的應用程序來利用 App Engine for Java 的基于 Java 的持久性支持,這種支持的基礎是 Java Data Objects (JDO) 和 Java Persistence API (JPA)。
好的,不說廢話了:讓我們開始吧!
Google(同時也是一些搜索引擎的創建者)于 2008 年 4 月首度發布了 Google App Engine。令許多 Java 開發人員失望的是,初始版完全只服務于 Python 程序員 — 那些認為應該大塊使用空白的人!(我曾經撰寫過一本有關 Python 的書,因此我想我應該知道)。Google 響應了用戶的普遍要求,于 2009 年 4 月發布了 Google App Engine for Java。
Google App Engine for Java 為企業 Java 開發提供了一個端到端解決方案:一個易于使用的基于瀏覽器的 Ajax GUI、Eclipse 工具支持以及后端的 Google App Engine。易于使用和工具支持是 Google App Engine for Java 優于其他云計算解決方案的兩大優勢。
App Engine for Java 中的應用程序開發意味著使用 Google 的資源存儲和檢索 Java 對象。數據存儲的基礎是 BigTable,但是使用的是 JDO 和 JPA 接口,這些接口允許您編寫沒有直接綁定到 BigTable 的代碼。事實上,Google 為許多 API 提供了基于標準的支持,這樣就可以編寫沒有全部綁定到 App Engine for Java 的代碼。
App Engine for Java 依賴以下標準 Java API:
java.net.URL
,檢索服務(通過使用 HTTP 和 HTTPS 協議與其他主機通信)- JavaMail,發送郵件消息
- 一個通向 Memcache 的 JCache (JSR 107) 接口,提供快速、臨時的分布式存儲,用于緩存查詢和計算
此外,App Engine for Java 為以下應用程序服務提供了支持:
- 用戶身份驗證和授權
- CRON
- 數據導入/導出
- 訪問防火墻數據
對于將數據從其他來源移動到您的 App Engine for Java 應用程序,數據導入/導出十分重要。這也不需要綁定到 App Engine for Java。Google 的 CRON 支持基于對某個調度的內部 URL 命中率,從而使它成為不需要綁定 App Engine for Java 的出色服務。用戶身份驗證和授權機制是 特定于 App Engine for Java 的,但是您可以編寫一個 ServletFilter
、aspect 或 Spring Security 插件,以便最小化這種緊密耦合。
如果您已經閱讀了前面的內容,那么已經準備好開始構建第一個 App Engine for Java 應用程序。首先需要 安裝 Google Plugin for Eclipse for App Engine for Java;安裝之后,您就有了好的起點。
打開 Eclipse IDE,您將看到在 Eclipse IDE 中,Printer 按鈕旁邊出現了三個新按鈕:一個 G 顯示在藍色小球中,另一個 G 顯示在紅色工具箱中,還有一個 App Engine for Java 迷你噴氣式飛機,如圖 1 所示:
圖 1. Eclipse IDE 中的新按鈕

下面列出了這些按鈕的功能:
- 藍色小球讓您能夠訪問 App Engine for Java 項目創建向導。
- 紅色工具箱讓您編譯一個 GWT 項目。
- 迷你噴氣式飛機圖標讓您能夠部署一個 App Engine 項目。
您將使用項目創建向導創建兩個新項目:一個基于 servlets,另一個使用 GWT 構建。將使用工具箱功能編譯 GWT 項目。當您準備好部署 App Engine 項目時,將啟動迷你噴氣式分機,激活項目。
首先創建一個 App Engine for Java 項目。第一步,單擊藍色小球以訪問項目創建向導。然后使用名稱為 gaej.example 的包創建名為 SimpleServletApp 的應用程序,如圖 2 所示:
圖 2. 開始一個新項目

注意,對于這第一個簡單示例,沒有選擇 GWT 支持。完成了這一步后,項目創建向導將創建一個簡單的基于 servlet 的應用程序,提供了一個 Hello World 類型的 servlet。圖 3 展示了這個項目的屏幕截圖。
圖 3. SimpleServletApp 項目

注意,這個新的基于 servlet 的項目自動包含了 JAR 文件:
- datanucleus-*.jar:用于使用標準 JDO 或低級 DataNucleus API訪問 App Engine for Java 數據庫
- appengine-api-sdk.1.2.0.jar:用于使用非標準 App Engine for Java 應用程序服務,比如 App Engine for Java Security
- geronimo-*.jar:用于使用標準 Java API,比如 Java Transaction Management API (JTA) 和 JPA
- jdo2-api-2.3-SNAPSHOT.jar:用于使用 JDO API
在本系列 第 2 部分 中,您將了解到如何使用 App Engine for Java 的持久化 API 和 App Engine for Java 的一些應用程序服務。
還需注意用于為 Google App Engine 配置運行時容器的文件,名為 appengine.xml。在本例中,appengine.xml 用于配置 logging.properties 文件,以使用 App Engine for Java 完成登錄。
App Engine for Java servlet 應用程序初探
在項目創建向導中完成所有配置后,App Engine for Java 將向您顯示一個 Hello World 風格的 servlet 應用程序的骨架。查看代碼并看看如何使用 App Engine for Java Eclipse 工具運行應用程序。該應用程序的主要入口點為 SimpleServletAppServlet
,如清單 1 所示:
清單 1. SimpleServletAppServlet
package gaej.example; import java.io.IOException; import javax.servlet.http.*; @SuppressWarnings("serial") public class SimpleServletAppServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/plain"); resp.getWriter().println("Hello, world"); } } |
servlet 在 web.xml 中的 URI /simpleservletapp
下完成映射,如清單 2 所示:
清單 2. web.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"> <servlet> <servlet-name>simpleservletapp</servlet-name> <servlet-class>gaej.example.SimpleServletAppServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>simpleservletapp</servlet-name> <url-pattern>/simpleservletapp</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app> |
項目創建還提供了一個 index.html 文件,包含了連接新 servlet 的鏈接,如清單 3 所示:
清單 3. 項目創建向導生成的 index.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <!-- The HTML 4.01 Transitional DOCTYPE declaration--> <!-- above set at the top of the file will set --> <!-- the browser's rendering engine into --> <!-- "Quirks Mode". Replacing this declaration --> <!-- with a "Standards Mode" doctype is supported, --> <!-- but may lead to some differences in layout. --> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!-- --> <!-- Any title is fine --> <!-- --> <title>Hello App Engine</title> </head> <!-- --> <!-- The body can have arbitrary html, or --> <!-- you can leave the body empty if you want --> <!-- to create a completely dynamic UI. --> <!-- --> <body> <h1>Hello App Engine!</h1> <table> <tr> <td colspan="2" style="font-weight:bold;">Available Servlets:</td> </tr> <tr> <td><a href="simpleservletapp"/>SimpleServletAppServlet</td> </tr> </table> </body> </html> |
您現在已經使用了一些 Java API 構建了一個簡單的 servlet 應用程序。并且重點在于:App Engine for Java 使用標準 Java API 封裝 App Engine 功能,使 App Engine 能夠支持面向 Java 平臺的豐富框架。
要使用 App Engine for Java Eclipse 工具運行我們的基于 servlet 的應用程序,首先右鍵單擊該項目并選擇 Run As 菜單,然后選擇旁邊有一個藍色小球的 “Web Application”,如圖 4 所示:
圖 4. 運行 App Engine for Java 部署服務器

現在您應當能夠在瀏覽器中導航到 http://localhost:8080/simpleservletapp 并看到應用程序顯示出 Hello World 消息。
創建 App Engine for Java/GWT 應用程序
您已經了解了一個簡單 App Engine for Java servlet 應用程序的工作原理,那么接下來讓我們探討一下面向 GWT 應用程序的 App Engine for Java Eclipse 工具。首先單擊 Eclipse IDE 工具欄中的藍色小球來激活 Google 項目創建向導。這一次,選擇 GWT 支持,如圖 5 所示:
圖 5. 使用 App Engine for Java 項目創建向導創建一個簡單的 GWT 應用程序

如圖 6 所示,與簡單的基于 servlet 的應用程序相比,App Engine for Java 為 GWT 應用程序提供了更多的代碼工件。示例應用程序是一個在 GWT 執行的 GUI,它可以與一個問候(greeting)服務應用程序通信。
圖 6. 為 GWT 應用程序提供的代碼工件

為 GWT 應用程序提供的一個額外 JAR 對于基于 servlet 的應用程序并不是必須的,這個 JAR 文件就是 gwt-servlet.jar。
其他工件包括:
- src/gaej/example:SimpleGWTApp.gwt.xml:GWT 模塊描述符
- src/gaej.example.server:GreetingServiceImpl.java:問候服務的實現
- src/gaej.example.client:GreetingService.java:問候服務的同步 API
- src/gaej.example.client:GreetingServiceAsync.java:問候服務的異步 API
- src/gaej.example.client:SimpleGWTApp.java:構建啟動 GUI 的主要入口點
- war/WEB-INF:web.xml:配置
GreetingServiceImpl
的部署描述符 - war:SimpleGWTApp.html:顯示 GWT GUI 的 HTML 頁面
- war:SimpleGWTApp.css:GWT GUI 的樣式表
在深入研究應用程序的架構和源代碼之前,看看運行它的時候會發生什么狀況。要運行應用程序,單擊工具欄中的紅色工具箱,然后單擊 Compile 按鈕。現在右鍵單擊項目并像剛才一樣選擇 Run As—> Web Application 菜單項。這一次,由于您處理的是一個 GWT 應用程序,將顯示一個 GWT Hosted Mode Console 和瀏覽器。繼續并使用 Web 應用程序輸入您的名字并觀察響應。我收到如圖 7 所示的響應:
圖 7. 運行樣例 GWT 應用程序

在下一小節中,我將帶領您遍歷樣例 GWT 應用程序。如果希望了解更多關于 GWT 的信息(或者獲得 GWT 教程),請參見 參考資料。
根據已經提供的配置,Eclipse 的 GWT 工具創建了一個啟動應用程序,包含一個 HTML 前端(SimpleGWTApp.html,如 清單 10 所示),可以加載 simplegwtapp.js 和 simplegwtapp.nocache.js。這是由 GWT 從 Java 代碼中生成的 JavaScript 代碼;也就是說,位于 gaej.example.client 包中的 src 目錄下的代碼(參見清單 6、7 和 8)。
創建 GUI 的主要入口點是 gaej.example.client.SimpleGWTApp
,如 清單 8 所示。該類創建 GWT GUI 元素并將它們與 SimpleGWTApp.html 中的 HTML DOM 元素關聯起來(參見 清單 10)。SimpleGWTApp.html 定義了兩個 DOM 元素,分別命名為 nameFieldContainer
和 sendButtonContainer
(表中的兩列)。SimpleGWTApp
類使用 RootPanel.get("nameFieldContainer")
訪問與這些 DOM 元素關聯的面板并使用 GUI 元素替換它們。SimpleGWTApp
類隨后定義一個文本框和按鈕,可以使用它們輸入某人的名字并發送一句問候語(參見 清單 10)。
GWT 知道 SimpleGWTApp
類是應用程序的主要入口點,因為 SimpleGWTApp.gwt.xml 使用入口點元素進行了指定。
SimpleGWTApp
封裝了名為 sendButton
的按鈕,這樣當它被單擊時,SimpleGWTApp
將對 GreetingService
調用 greetServer
方法。GreetingService
接口在 src/gaej.example.client.GreetingService.java 中被定義(參見 清單 6)。
由于 Ajax 天生就具有異步性,因此 GWT 定義了一個異步接口來訪問遠程服務。SimpleGWTApp 使用 src/gaej.example.client.GreetingServiceAsync.java 中定義的異步接口(參見 清單 7)。GreetingServiceImpl
(src/gaej.example.server.GreetingServiceImpl.java)實現了 GreetingService
中定義的 greetServer
方法(參見 清單 5)。GreetingServiceImpl.greetServer
方法返回一條問候消息 String
,SimpleGWTApp 用它在所創建的對話框中顯示問候消息。
GWT 模塊描述符聲明了 GUI 應用程序的主要入口點,即 gaej.example.client.SimpleGWTApp
,如清單 4 所示:
清單 4. GWT 模塊描述符(src/gaej/example/SimpleGWTApp.gwt.xml)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/ distro-source/core/src/gwt-module.dtd"> <module rename-to='simplegwtapp'> <!-- Inherit the core Web Toolkit stuff. --> <inherits name='com.google.gwt.user.User'/> <!-- Inherit the default GWT style sheet. You can change --> <!-- the theme of your GWT application by uncommenting --> <!-- any one of the following lines. --> <inherits name='com.google.gwt.user.theme.standard.Standard'/> <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> --> <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> --> <!-- Other module inherits --> <!-- Specify the app entry point class. --> <entry-point class='gaej.example.client.SimpleGWTApp'/> </module> |
GreetingServiceImpl
是問候服務應用程序的實際實現,如清單 5 所示。它運行在服務器端,并且客戶機代碼通過一個遠程過程調用來調用它。
清單 5. greeting-service 應用程序的實現(src/gaej.example.server.GreetingServiceImpl.java)
package gaej.example.server; import gaej.example.client.GreetingService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; /** * The server side implementation of the RPC service. */ @SuppressWarnings("serial") public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService { public String greetServer(String input) { String serverInfo = getServletContext().getServerInfo(); String userAgent = getThreadLocalRequest().getHeader("User-Agent"); return "Hello, " + input + "!<br><br>I am running " + serverInfo + ".<br><br>It looks like you are using:<br>" + userAgent; } } |
如清單 6 所示,GreetingService
是客戶機代碼使用的遠程過程調用的接口:
清單 6. 同步 API (src/gaej.example.client.GreetingService.java)
package gaej.example.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; /** * The client side stub for the RPC service. */ @RemoteServiceRelativePath("greet") public interface GreetingService extends RemoteService { String greetServer(String name); } |
GreetingServiceAsync
是客戶機代碼將使用的實際接口,如清單 7 所示。每個方法都提供了一個回調對象,這樣就可以在完成遠程過程調用后收到異步通知。至于內部原理,GWT 使用了 Ajax。在客戶機上使用 Ajax 時,最好不要阻塞客戶機,這樣就不會阻塞異步調用。阻塞會違背使用 Ajax 的初衷:
清單 7. 異步 API(src/gaej.example.client.GreetingServiceAsync.java)
package gaej.example.client; import com.google.gwt.user.client.rpc.AsyncCallback; /** * The async counterpart of <code>GreetingService</code>. */ public interface GreetingServiceAsync { void greetServer(String input, AsyncCallback<String> callback); } |
SimpleGWTApp
中包含了最多的操作。它注冊了 GUI 事件,然后將 Ajax 請求發送到 GreetingService
。
清單 8. 應用程序的主入口點還構建了啟動 GUI(src/gaej.example.client.SimpleGWTApp.java)
package gaej.example.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; /** * Entry point classes define <code>onModuleLoad()</code>. */ public class SimpleGWTApp implements EntryPoint { /** * The message displayed to the user when the server cannot be reached or * returns an error. */ private static final String SERVER_ERROR = "An error occurred while " + "attempting to contact the server. Please check your network " + "connection and try again."; /** * Create a remote service proxy to talk to the server-side Greeting service. */ private final GreetingServiceAsync greetingService = GWT .create(GreetingService.class); /** * This is the entry point method. */ public void onModuleLoad() { final Button sendButton = new Button("Send"); final TextBox nameField = new TextBox(); nameField.setText("GWT User"); // You can add style names to widgets sendButton.addStyleName("sendButton"); // Add the nameField and sendButton to the RootPanel // Use RootPanel.get() to get the entire body element RootPanel.get("nameFieldContainer").add(nameField); RootPanel.get("sendButtonContainer").add(sendButton); // Focus the cursor on the name field when the app loads nameField.setFocus(true); nameField.selectAll(); // Create the popup dialog box final DialogBox dialogBox = new DialogBox(); dialogBox.setText("Remote Procedure Call"); dialogBox.setAnimationEnabled(true); final Button closeButton = new Button("Close"); // You can set the id of a widget by accessing its Element closeButton.getElement().setId("closeButton"); final Label textToServerLabel = new Label(); final HTML serverResponseLabel = new HTML(); VerticalPanel dialogVPanel = new VerticalPanel(); dialogVPanel.addStyleName("dialogVPanel"); dialogVPanel.add(new HTML("<b>Sending name to the server:</b>")); dialogVPanel.add(textToServerLabel); dialogVPanel.add(new HTML("<br><b>Server replies:</b>")); dialogVPanel.add(serverResponseLabel); dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT); dialogVPanel.add(closeButton); dialogBox.setWidget(dialogVPanel); // Add a handler to close the DialogBox closeButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { dialogBox.hide(); sendButton.setEnabled(true); sendButton.setFocus(true); } }); // Create a handler for the sendButton and nameField class MyHandler implements ClickHandler, KeyUpHandler { /** * Fired when the user clicks on the sendButton. */ public void onClick(ClickEvent event) { sendNameToServer(); } /** * Fired when the user types in the nameField. */ public void onKeyUp(KeyUpEvent event) { if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { sendNameToServer(); } } /** * Send the name from the nameField to the server and wait for a response. */ private void sendNameToServer() { sendButton.setEnabled(false); String textToServer = nameField.getText(); textToServerLabel.setText(textToServer); serverResponseLabel.setText(""); greetingService.greetServer(textToServer, new AsyncCallback<String>() { public void onFailure(Throwable caught) { // Show the RPC error message to the user dialogBox .setText("Remote Procedure Call - Failure"); serverResponseLabel .addStyleName("serverResponseLabelError"); serverResponseLabel.setHTML(SERVER_ERROR); dialogBox.center(); closeButton.setFocus(true); } public void onSuccess(String result) { dialogBox.setText("Remote Procedure Call"); serverResponseLabel .removeStyleName("serverResponseLabelError"); serverResponseLabel.setHTML(result); dialogBox.center(); closeButton.setFocus(true); } }); } } // Add a handler to send the name to the server MyHandler handler = new MyHandler(); sendButton.addClickHandler(handler); nameField.addKeyUpHandler(handler); } } |
Web 部署描述符(web.xml,如清單 9 所示)將 GreetingService
映射為一個基于 servlet 的 Web 資源。它在 /simplegwtapp/greet
名稱下映射 GreetingService
servlet,這樣 SimpleGWTApp
就可以加載它并對它發起調用。Web 部署描述符還可以將 SimpleGWTApp.html 指定為應用程序的歡迎頁面,這樣就會始終加載它。
清單 9. 配置 GreetingServiceImpl (war/WEB-INF/web.xml) 的部署描述符
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <!-- Default page to serve --> <welcome-file-list> <welcome-file>SimpleGWTApp.html</welcome-file> </welcome-file-list> <!-- Servlets --> <servlet> <servlet-name>greetServlet</servlet-name> <servlet-class>gaej.example.server.GreetingServiceImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>greetServlet</servlet-name> <url-pattern>/simplegwtapp/greet</url-pattern> </servlet-mapping> </web-app> |
HTML 前端為 SimpleGWTApp.html,如清單 10 所示。這是加載 simplegwtapp.js 和 simplegwtapp.nocache.js 的頁面,是由 GWT 從 Java 代碼中生成的 JavaScript 代碼。如前所述,這段代碼位于 gaej.example.client 包的 src 目錄中(來自清單 6、7 和 8)。
清單 10. 顯示 GWT GUI (war/SimpleGWTApp.html) 的 HTML 頁面
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <!-- The HTML 4.01 Transitional DOCTYPE declaration--> <!-- above set at the top of the file will set --> <!-- the browser's rendering engine into --> <!-- "Quirks Mode". Replacing this declaration --> <!-- with a "Standards Mode" doctype is supported, --> <!-- but may lead to some differences in layout. --> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!-- --> <!-- Consider inlining CSS to reduce the number of requested files --> <!-- --> <link type="text/css" rel="stylesheet" href="SimpleGWTApp.css"> <!-- --> <!-- Any title is fine --> <!-- --> <title>Web Application Starter Project</title> <!-- --> <!-- This script loads your compiled module. --> <!-- If you add any GWT meta tags, they must --> <!-- be added before this line. --> <!-- --> <script type="text/javascript" language="javascript" src="simplegwtapp/simplegwtapp.nocache.js"></script> </head> <!-- --> <!-- The body can have arbitrary html, or --> <!-- you can leave the body empty if you want --> <!-- to create a completely dynamic UI. --> <!-- --> <body> <!-- OPTIONAL: include this if you want history support --> <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe> <h1>Web Application Starter Project</h1> <table align="center"> <tr> <td colspan="2" style="font-weight:bold;">Please enter your name:</td> </tr> <tr> <td id="nameFieldContainer"></td> <td id="sendButtonContainer"></td> </tr> </table> </body> </html> |
使用 GWT,您可以通過 CSS 控制應用程序的觀感,如清單 11 所示:
清單 11. GWT GUI (war/SimpleGWTApp.css) 的樣式表
/** Add css rules here for your application. */ /** Example rules used by the template application (remove for your app) */ h1 { font-size: 2em; font-weight: bold; color: #777777; margin: 40px 0px 70px; text-align: center; } .sendButton { display: block; font-size: 16pt; } /** Most GWT widgets already have a style name defined */ .gwt-DialogBox { width: 400px; } .dialogVPanel { margin: 5px; } .serverResponseLabelError { color: red; } /** Set ids using widget.getElement().setId("idOfElement") */ #closeButton { margin: 15px 6px 6px; } |
創建了下一代殺手級應用程序后(因為我們確實需要一個用戶友好的問候應用程序),您需要部署它。使用 Google App Engine 的重點就是可以將應用程序部署到 Google 提供的可靠基礎設施中,使它更易于擴展。Google App Engine 的設計初衷就是為構建可伸縮應用程序提供一個平臺,可伸縮應用程序就是指 “能夠在不觸動基礎設施的情況下將用戶輕松增長到數百萬”(正如 App Engine 主頁中描述的那樣)。為使用這種基礎設施,您需要一個 Google App Engine for Java 帳戶。
就像許多其他產品一樣,第一次體驗總是免費的。App Engine for Java 的免費版提供了一個已部署的應用程序,提供了足夠的 CPU、帶寬和存儲來為 500 萬個頁面訪問次數提供服務。超過使用次數開始收費(同樣需要注意,在撰寫本文時可以獲得 App Engine for Java 平臺的預覽版)。
獲得帳戶之后,您應該可以在 App Engine for Java 站點 看到一個空的應用程序列表。單擊 Create New Application 按鈕,應當出現一個如圖 8 所示的表單。輸入一個獨特的應用程序名稱和描述,之后您將看到一條顯示有應用程序標識符的確認消息。
該標識符也位于應用程序的 app.yaml 文件中。注意,該標識符不可修改。如果對您的應用程序使用 Google 身份驗證,那么在訪問應用程序時,“GAEj Article For Rick Part 1” 將顯示在 Sign In 頁面中。您將使用 gaejarticleforrick
和 App Engine for Java Eclipse 插件來將應用程序部署到 Google App Engine。
圖 8. 創建一個新的 App Engine for Java 應用程序

設置好應用程序 ID 后,可以從 Eclipse 中部署您的應用程序。首先,單擊看上去類似 Google App Engine 徽標的工具欄按鈕(顯示機翼和尾翼的噴氣式發動機),如圖 9 所示:
圖 9. App Engine for Java Eclipse 插件

在單擊圖 10 所示的對話框中的 Deploy 之前,可能需要確保 App Engine for Java 項目被選中。將要求您輸入 Google 憑證,即您的電子郵件地址和用戶名。
圖 10. 部署項目

圖 10 中的對話框包含一個 “App Engine Project setting” 鏈接。單擊此鏈接(也可以從項目設置文件訪問)并輸入應用程序 ID(在本例中為 gaejarticleforrick
),如圖 11 所示。填充好應用程序 ID 后,單擊 OK,然后單擊 Deploy。
圖 11. Google App Engine 的項目設置

部署完應用程序后,該應用程序可以通過 http://<application id>.appspot.com/
訪問。您還會看到應用程序出現在 http://gaejarticleforrick.appspot.com/ 中。
Google App Engine for Java 系列的第一部分到此結束。至此,您已經大致了解了 App Engine for Java 是什么,并通過使用 App Engine for Java Google Plugin for Eclipse 邁出了第一步。您創建了兩個啟動應用程序(一個基于 servlet,另一個基于 GWT)并隨后將 GWT 應用程序部署到 Google App Engine 平臺。
本文的示例到目前為止演示了可以更輕松地創建和部署基于 Java 的應用程序的工具和功能 — 這些應用程序有可能擴展到 YouTube 或 Facebook 那樣的規模。在 第 2 部分 中,您將繼續為 Java 開發人員介紹使用 App Engine for Java 的機會。從本文演示的示例應用程序出發,您將構建一個定制的聯系人管理應用程序。該應用程序將成為第 3 部分的中心內容,第 3 部分將進一步了解 App Engine for Java 的數據存儲及其 GUI 前端。