周一的專欄

          Java架構專題

          2009年9月22日

          Google App Engine for Java: 第 2 部分:構建殺手級應用

          在介紹使用 App Engine for Java 構建可伸縮 Java 應用程序的 第 1 部分 中,您了解了 Google 云計算平臺(即 PAAS)為 Java 開發人員提供的 Eclipse 工具和基礎設施。該文章中的示例都是預先準備的,這樣您可以將精力集中到 App Engine for Java 與 Eclipse 的集成中,并快速構建和部署不同類型的應用程序 — 即使用 Google Web Toolkit (GWT) 構建的應用程序和基于 servlet 的應用程序。本文將在此基礎上展開,并且在本系列第 3 部分中提供了更加高級的編程實踐。

          您將構建的聯系人管理應用程序允許用戶存儲基本的聯系人信息,比如名稱、電子郵件地址和電話號碼。要創建這個應用程序,將需要使用 Eclipse GWT 項目創建向導。


          從 CRUD 到聯系人應用程序

          正如目前您已經了解到的一樣,在 App Engine for Java 中構建新應用程序的第一步就是在 Eclipse 啟動項目創建向導。之后,您可以打開 GWT 項目啟動向導來創建 GWT 項目(本文 第 1 部分 給出了在 App Engine for Java 中創建 GWT 項目的詳細說明)。

          對于這個練習,您將啟動一個簡單的 CRUD 應用程序,并稍后添加實際的存儲。我們將使用一個具有模擬實現的數據訪問對象(DAO),如清單 1 所示:


          清單 1. ContactDAO 接口
                      package gaej.example.contact.server;
                      import java.util.List;
                      import gaej.example.contact.client.Contact;
                      public interface ContactDAO {
                      void addContact(Contact contact);
                      void removeContact(Contact contact);
                      void updateContact(Contact contact);
                      List<Contact> listContacts();
                      }
                      

          ContactDAO 添加了各種方法,可以添加聯系人、刪除聯系人、更新聯系人,并返回一個所有聯系人的列表。它是一個非常基本的 CRUD 接口,可以管理聯系人。Contact 類是您的域對象,如清單 2 所示:



          清單 2. 聯系人域對象(gaej.example.contact.client.Contact)
                      package gaej.example.contact.client;
                      import java.io.Serializable;
                      public class Contact implements Serializable {
                      private static final long serialVersionUID = 1L;
                      private String name;
                      private String email;
                      private String phone;
                      public Contact() {
                      }
                      public Contact(String name, String email, String phone) {
                      super();
                      this.name = name;
                      this.email = email;
                      this.phone = phone;
                      }
                      public String getName() {
                      return name;
                      }
                      public void setName(String name) {
                      this.name = name;
                      }
                      public String getEmail() {
                      return email;
                      }
                      public void setEmail(String email) {
                      this.email = email;
                      }
                      public String getPhone() {
                      return phone;
                      }
                      public void setPhone(String phone) {
                      this.phone = phone;
                      }
                      }
                      

          對于這個應用程序的第一個版本,您將使用一個模擬對象將聯系人存儲在一個內存集合中,如清單 3 所示:


          清單 3. Mock DAO 類
                      package gaej.example.contact.server;
                      import gaej.example.contact.client.Contact;
                      import java.util.ArrayList;
                      import java.util.Collections;
                      import java.util.LinkedHashMap;
                      import java.util.List;
                      import java.util.Map;
                      public class ContactDAOMock implements ContactDAO {
                      Map<String, Contact> map = new LinkedHashMap<String, Contact>();
                      {
                      map.put("rhightower@mammatus.com",
                      new Contact("Rick Hightower", "rhightower@mammatus.com", "520-555-1212"));
                      map.put("scott@mammatus.com",
                      new Contact("Scott Fauerbach", "scott@mammatus.com", "520-555-1213"));
                      map.put("bob@mammatus.com",
                      new Contact("Bob Dean", "bob@mammatus.com", "520-555-1214"));
                      }
                      public void addContact(Contact contact) {
                      String email = contact.getEmail();
                      map.put(email, contact);
                      }
                      public List<Contact> listContacts() {
                      return Collections.unmodifiableList(new ArrayList<Contact>(map.values()));
                      }
                      public void removeContact(Contact contact) {
                      map.remove(contact.getEmail());
                      }
                      public void updateContact(Contact contact) {
                      map.put(contact.getEmail(), contact);
                      }
                      }
                      

          創建遠程服務

          您現在的目標是創建一個允許您使用 DAO 的 GWT GUI。將使用 ContactDAO 接口上的所有方法。第一步是將 DAP 類(未來版本將直接與服務器端的數據存儲通信,因此必須位于服務器中)的功能封裝到一個服務中,如清單 4 所示:


          清單 4. ContactServiceImpl
                      package gaej.example.contact.server;
                      import java.util.ArrayList;
                      import java.util.List;
                      import gaej.example.contact.client.Contact;
                      import gaej.example.contact.client.ContactService;
                      import com.google.gwt.user.server.rpc.RemoteServiceServlet;
                      public class ContactServiceImpl extends RemoteServiceServlet implements ContactService {
                      private static final long serialVersionUID = 1L;
                      private ContactDAO contactDAO = new ContactDAOMock();
                      public void addContact(Contact contact) {
                      contactDAO.addContact(contact);
                      }
                      public List<Contact> listContacts() {
                      List<Contact> listContacts = contactDAO.listContacts();
                      return new ArrayList<Contact> (listContacts);
                      }
                      public void removeContact(Contact contact) {
                      contactDAO.removeContact(contact);
                      }
                      public void updateContact(Contact contact) {
                      contactDAO.updateContact(contact);
                      }
                      }
                      

          注意,ContactServiceImpl 實現了 RemoteServiceServlet,隨后定義方法來添加聯系人、列出聯系人、刪除聯系人,以及更新聯系人。它將所有這些操作委托給 ContactDAOMockContactServiceImpl 不過是一個圍繞 ContactDAO 的包裝器,后者將 ContactDAO 功能公開給 GWT GUI。ContactServiceImpl 在 web.xml 文件中被映射到 URI /contactlist/contacts,如清單 5 所示:


          清單 5. web.xml 中的 ContactService
                      <servlet>
                      <servlet-name>contacts</servlet-name>
                      <servlet-class>gaej.example.contact.server.ContactServiceImpl</servlet-class>
                      </servlet>
                      <servlet-mapping>
                      <servlet-name>contacts</servlet-name>
                      <url-pattern>/contactlist/contacts</url-pattern>
                      </servlet-mapping>
                      

          要使 GUI 前端訪問該服務,需要定義一個遠程服務接口和一個異步遠程服務接口,如清單 6 和 7 所示:


          清單 6. ContactService
                      package gaej.example.contact.client;
                      import java.util.List;
                      import com.google.gwt.user.client.rpc.RemoteService;
                      import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
                      @RemoteServiceRelativePath("contacts")
                      public interface ContactService extends RemoteService {
                      List<Contact> listContacts();
                      void addContact(Contact contact);
                      void removeContact(Contact contact);
                      void updateContact(Contact contact);
                      }
                      


          清單 7. ContactServiceAsync
                      package gaej.example.contact.client;
                      import java.util.List;
                      import com.google.gwt.user.client.rpc.AsyncCallback;
                      public interface ContactServiceAsync  {
                      void listContacts(AsyncCallback<List <Contact>> callback);
                      void addContact(Contact contact, AsyncCallback<Void> callback);
                      void removeContact(Contact contact, AsyncCallback<Void> callback);
                      void updateContact(Contact contact, AsyncCallback<Void> callback);
                      }
                      

          注意,ContactService 實現了 RemoteService 接口并定義了一個 @RemoteServiceRelativePath,指定了 “聯系人” 的相對路徑。相對路徑與您在 web.xml 文件中為服務定義的路徑是對應的(必須匹配)。ContactServiceAsync 包含回調對象,因此 GWT GUI 可以收到來自服務器的調用的通知,而不會阻塞其他客戶機行為。

          避免編寫雜亂的代碼

          我并不喜歡編寫雜亂的代碼,因此在可能的情況下會盡量避免編寫這類代碼。這類代碼的一個例子就是一組匿名內部類,這些類的方法定義匿名內部類。這些內部類反過來執行回調,調用在某個內部類中以內聯方式定義的方法。坦白說,我無法閱讀或是理解這些糾纏在一起的代碼,即使是我自己編寫的!因此,為了將代碼稍微簡單化,我建議將 GWT GUI 分解為三個部分:

          • ContactListEntryPoint
          • ContactServiceDelegate
          • ContactListGUI

          ContactListEntryPoint 是主要的入口點;它執行 GUI 事件連接。ContactServiceDelegate 封裝 ContactService 功能并隱藏內部類回調連接。ContactListGUI 管理所有 GUI 組件并處理來自 GUIService 的事件。ContactListGUI 使用 ContactServiceDelegate 發出 ContactService 請求。

          ContactList.gwt.xml 文件(位于 gaej.example.contact 下的一個資源)使用 entry-point 元素將 ContactListEntryPoint 指定為應用程序的主要入口點,如清單 8 所示:


          清單 8. ContactList.gwt.xml
                      <entry-point class='gaej.example.contact.client.ContactListEntryPoint'/>
                      

          ContactListEntryPoint 類實現了 GWT 的 EntryPoint 接口(com.google.gwt.core.client.EntryPoint),并指定將調用該類來初始化 GUI。ContactListEntryPoint 所做的工作并不多。它創建一個 ContactListGUI 實例和一個 ContactServiceDelegate 實例,然后讓它們彼此了解對方,這樣就可以展開協作。ContactListEntryPoint 然后執行 GUI 事件連接。ContactListEntryPoint 如清單 9 所示:


          清單 9. ContactListEntryPoint
                      package gaej.example.contact.client;
                      import com.google.gwt.core.client.EntryPoint;
                      import com.google.gwt.event.dom.client.ClickEvent;
                      import com.google.gwt.event.dom.client.ClickHandler;
                      import com.google.gwt.user.client.ui.HTMLTable.Cell;
                      /**
                      * Entry point classes define onModuleLoad().
                      */
                      public class ContactListEntryPoint implements EntryPoint {
                      private ContactListGUI gui;
                      private ContactServiceDelegate delegate;
                      /**
                      * This is the entry point method.
                      */
                      public void onModuleLoad() {
                      gui = new ContactListGUI();
                      delegate = new ContactServiceDelegate();
                      gui.contactService = delegate;
                      delegate.gui = gui;
                      gui.init();
                      delegate.listContacts();
                      wireGUIEvents();
                      }
                      private void wireGUIEvents() {
                      gui.contactGrid.addClickHandler(new ClickHandler(){
                      public void onClick(ClickEvent event) {
                      Cell cellForEvent = gui.contactGrid.getCellForEvent(event);
                      gui.gui_eventContactGridClicked(cellForEvent);
                      }});
                      gui.addButton.addClickHandler(new ClickHandler(){
                      public void onClick(ClickEvent event) {
                      gui.gui_eventAddButtonClicked();
                      }});
                      gui.updateButton.addClickHandler(new ClickHandler(){
                      public void onClick(ClickEvent event) {
                      gui.gui_eventUpdateButtonClicked();
                      }});
                      gui.addNewButton.addClickHandler(new ClickHandler(){
                      public void onClick(ClickEvent event) {
                      gui.gui_eventAddNewButtonClicked();
                      }});
                      }
                      }
                      

          注意,ContactListEntryPointaddButtonupdateButtoncontactGridaddNewButton 連接事件。具體做法是注冊為小部件事件實現偵聽器接口的匿名內部類。這與 Swing 中的事件處理非常相似。這些小部件事件來自由 GUI 創建的小部件(ContactListGUI),我將稍后進行討論。注意,GUI 類包含 gui_eventXXX 方法來響應 GUI 事件。

          ContactListGUI 創建了 GUI 小部件并響應來自它們的事件。ContactListGUI 將 GUI 事件轉換為用戶希望對 ContactsService 執行的操作。ContactListGUI 使用 ContactServiceDelegateContactService 調用方法。ContactServiceDelegateContactService 創建一個異步接口并使用它發出異步 Ajax 調用。ContactServiceDelegateContactListGUI 通知來自服務的事件(成功或失敗)。ContactServiceDelegate 如清單 10 所示:


          清單 10. ContactServiceDelegatepackage gaej.example.contact.client;
                      import java.util.List;
                      import com.google.gwt.core.client.GWT;
                      import com.google.gwt.user.client.rpc.AsyncCallback;
                      public class ContactServiceDelegate {
                      private ContactServiceAsync contactService = GWT.create(ContactService.class);
                      ContactListGUI gui;
                      void listContacts() {
                      contactService.listContacts(new AsyncCallback<List<Contact>> () {
                      public void onFailure(Throwable caught) {
                      gui.service_eventListContactsFailed(caught);
                      }
                      public void onSuccess(List<Contact> result) {
                      gui.service_eventListRetrievedFromService(result);
                      }
                      }//end of inner class
                      );//end of listContacts method call.
                      }
                      void addContact(final Contact contact) {
                      contactService.addContact(contact, new AsyncCallback<Void> () {
                      public void onFailure(Throwable caught) {
                      gui.service_eventAddContactFailed(caught);
                      }
                      public void onSuccess(Void result) {
                      gui.service_eventAddContactSuccessful();
                      }
                      }//end of inner class
                      );//end of addContact method call.
                      }
                      void updateContact(final Contact contact) {
                      contactService.updateContact(contact, new AsyncCallback<Void> () {
                      public void onFailure(Throwable caught) {
                      gui.service_eventUpdateContactFailed(caught);
                      }
                      public void onSuccess(Void result) {
                      gui.service_eventUpdateSuccessful();
                      }
                      }//end of inner class
                      );//end of updateContact method call.
                      }
                      void removeContact(final Contact contact) {
                      contactService.removeContact(contact, new AsyncCallback<Void> () {
                      public void onFailure(Throwable caught) {
                      gui.service_eventRemoveContactFailed(caught);
                      }
                      public void onSuccess(Void result) {
                      gui.service_eventRemoveContactSuccessful();
                      }
                      }//end of inner class
                      );//end of updateContact method call.
                      }
                      }
                      

          注意,ContactServiceDelegate 通過以 service_eventXXX 開頭的方法向 ContactListGUI 發出有關服務事件的通知。如前所述,我編寫 ContactListGUI 的目標之一就是避免嵌套的內部類并創建一個相對扁平的 GUI 類(我可以非常方便地閱讀和理解的類)。ContactListGUI 只有 186 行,因此非常簡單。ContactListGUI 管理 9 個 GUI 小部件并與 ContactServiceDelegate 協作來管理一個 CRUD 清單,如清單 11 所示:


          清單 11. ContactListGUI 的實際使用
                      package gaej.example.contact.client;
                      import java.util.List;
                      import com.google.gwt.user.client.ui.Button;
                      import com.google.gwt.user.client.ui.Grid;
                      import com.google.gwt.user.client.ui.Hyperlink;
                      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.HTMLTable.Cell;
                      public class ContactListGUI {
                      /* Constants. */
                      private static final String CONTACT_LISTING_ROOT_PANEL = "contactListing";
                      private static final String CONTACT_FORM_ROOT_PANEL = "contactForm";
                      private static final String CONTACT_STATUS_ROOT_PANEL = "contactStatus";
                      private static final String CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar";
                      private static final int EDIT_LINK = 3;
                      private static final int REMOVE_LINK = 4;
                      /* GUI Widgets */
                      protected Button addButton;
                      protected Button updateButton;
                      protected Button addNewButton;
                      protected TextBox nameField;
                      protected TextBox emailField;
                      protected TextBox phoneField;
                      protected Label status;
                      protected Grid contactGrid;
                      protected Grid formGrid;
                      /* Data model */
                      private List<Contact> contacts;
                      private Contact currentContact;
                      protected ContactServiceDelegate contactService;
                      

          注意,ContactListGUI 跟蹤表單中加載的當前聯系人(currentContact)和清單中的聯系人列表(contacts)。圖 1 展示了小部件如何對應于創建的 GUI:


          圖 1. 聯系人管理 GUI 中活動的小部件
          圖 1. 聯系人管理 GUI 中活動的小部件

          清單 12 展示了 ContactListGUI 如何創建小部件和聯系人表單,并將小部件放到表單中:


          清單 12. ContactListGUI 創建并放置小部件
                      public class ContactListGUI {
                      /* Constants. */
                      private static final String CONTACT_LISTING_ROOT_PANEL = "contactListing";
                      private static final String CONTACT_FORM_ROOT_PANEL = "contactForm";
                      private static final String CONTACT_STATUS_ROOT_PANEL = "contactStatus";
                      private static final String CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar";
                      ...
                      public void init() {
                      addButton = new Button("Add new contact");
                      addNewButton = new Button("Add new contact");
                      updateButton = new Button("Update contact");
                      nameField = new TextBox();
                      emailField = new TextBox();
                      phoneField = new TextBox();
                      status = new Label();
                      contactGrid = new Grid(2,5);
                      buildForm();
                      placeWidgets();
                      }
                      private void buildForm() {
                      formGrid = new Grid(4,3);
                      formGrid.setVisible(false);
                      formGrid.setWidget(0, 0, new Label("Name"));
                      formGrid.setWidget(0, 1, nameField);
                      formGrid.setWidget(1, 0, new Label("email"));
                      formGrid.setWidget(1, 1, emailField);
                      formGrid.setWidget(2, 0, new Label("phone"));
                      formGrid.setWidget(2, 1, phoneField);
                      formGrid.setWidget(3, 0, updateButton);
                      formGrid.setWidget(3, 1, addButton);
                      }
                      private void placeWidgets() {
                      RootPanel.get(CONTACT_LISTING_ROOT_PANEL).add(contactGrid);
                      RootPanel.get(CONTACT_FORM_ROOT_PANEL).add(formGrid);
                      RootPanel.get(CONTACT_STATUS_ROOT_PANEL).add(status);
                      RootPanel.get(CONTACT_TOOL_BAR_ROOT_PANEL).add(addNewButton);
                      }
                      

          ContactListGUI init 方法由 ContactListEntryPoint.onModuleLoad 方法創建。init 方法調用 buildForm 方法來創建新的表單網格并使用字段填充,以編輯聯系人數據。init 方法隨后調用 placeWidgets 方法,隨后將 contactGridformGridstatusaddNewButton 小部件放到 HTML 頁面中定義的插槽中,這個 HTML 頁面托管了清單 13 中定義的 GUI 應用程序:


          清單 13. ContactList.html 定義了用于小部件的插槽
                      <h1>Contact List Example</h1>
                      <table align="center">
                      <tr>
                      <td id="contactStatus"></td> <td id="contactToolBar"></td>
                      </tr>
                      <tr>
                      <td id="contactForm"></td>
                      </tr>
                      <tr>
                      <td id="contactListing"></td>
                      </tr>
                      </table>
                      

          常量(比如 CONTACT_LISTING_ROOT_PANEL="contactListing")對應于 HTML 頁面中定義的元素的 ID(類似 id="contactListing")。這允許頁面設計師進一步控制應用程序小部件的布局。

          對于基本的應用程序構建,讓我們了解幾個常見的使用場景。

          展示一個有關頁面加載的鏈接

          當聯系人管理應用程序的頁面首次加載時,它將調用 ContactListEntryPointonModuleLoad 方法。onModuleLoad 調用 ContactServiceDelegatelistContacts 方法,后者異步調用服務的 listContact 方法。當 listContact 方法返回時,ContactServiceDelegate 中定義的匿名內部類將調用名為 service_eventListRetrievedFromService 的服務事件處理器方法,如清單 14 所示:


          清單 14. 調用 listContact 事件處理器
                      public class ContactListGUI {
                      ...
                      public void service_eventListRetrievedFromService(List<Contact> result) {
                      status.setText("Retrieved contact list");
                      this.contacts = result;
                      this.contactGrid.clear();
                      this.contactGrid.resizeRows(this.contacts.size());
                      int row = 0;
                      for (Contact contact : result) {
                      this.contactGrid.setWidget(row, 0, new Label(contact.getName()));
                      this.contactGrid.setWidget(row, 1, new Label (contact.getPhone()));
                      this.contactGrid.setWidget(row, 2, new Label (contact.getEmail()));
                      this.contactGrid.setWidget(row, EDIT_LINK, new Hyperlink("Edit", null));
                      this.contactGrid.setWidget(row, REMOVE_LINK, new Hyperlink("Remove", null));
                      row ++;
                      }
                      }
                      

          service_eventListRetrievedFromService 事件處理器方法存儲由服務器發送的聯系人列表。然后它將清空顯示聯系人列表的 contactGrid。它將重新調整行數,以匹配服務器返回的聯系人列表的大小。隨后遍歷聯系人列表,將每個聯系人的姓名、電話、電子郵件數據放到每一行的前三個列中。它還為每個聯系人提供了 Edit 鏈接和一個 Remove 鏈接,使用戶能夠輕松地刪除和編輯聯系人。

          用戶編輯現有的聯系人

          當用戶單擊聯系人列表中的 Edit 鏈接時,gui_eventContactGridClicked 將得到調用,如清單 15 所示:


          清單 15. ContactListGUI 的 gui_eventContactGridClicked 事件處理器方法
                      public class ContactListGUI {
                      ...
                      public void gui_eventContactGridClicked(Cell cellClicked) {
                      int row = cellClicked.getRowIndex();
                      int col = cellClicked.getCellIndex();
                      Contact contact = this.contacts.get(row);
                      this.status.setText("Name was " + contact.getName() + " clicked ");
                      if (col==EDIT_LINK) {
                      this.addNewButton.setVisible(false);
                      this.updateButton.setVisible(true);
                      this.addButton.setVisible(false);
                      this.emailField.setReadOnly(true);
                      loadForm(contact);
                      } else if (col==REMOVE_LINK) {
                      this.contactService.removeContact(contact);
                      }
                      }
                      ...
                      private void loadForm(Contact contact) {
                      this.formGrid.setVisible(true);
                      currentContact = contact;
                      this.emailField.setText(contact.getEmail());
                      this.phoneField.setText(contact.getPhone());
                      this.nameField.setText(contact.getName());
                      }
                      

          gui_eventContactGridClicked 方法必須確定 Edit 鏈接或 Remove 鏈接是否被單擊。具體做法是找到那個列被單擊。隨后隱藏 addNewButtonaddButton,并使 updateButton 可見。updateButton 顯示在 formGrid 中,允許用戶將更新信息發送回 ContactService。它還使 emailField 變為只讀,這樣用戶就不能編輯電子郵件字段。接下來,gui_eventContactGridClicked 調用 loadForm(如 清單 15 所示),后者將 formGrid 設置為可見,設置正在被編輯的聯系人,然后將聯系人屬性復制到 emailFieldphoneFieldnameField 小部件中。

          當用戶單擊 updateButton 時,gui_eventUpdateButtonClicked 事件處理器方法被調用,如清單 16 所示。這個方法使 addNewButton 變為可見(這樣用戶就可以編輯新的聯系人)并隱藏了 formGrid。它隨后調用 copyFieldDateToContact,后者將來自 emailFieldphoneFieldnameField 小部件的文本復制回 currentContact 的屬性。隨后調用 ContactServiceDelegate updateContact 方法來將新更新的聯系人傳遞回服務。


          清單 16. ContactListGUI 的 gui_eventUpdateButtonClicked 事件處理器方法
                      public class ContactListGUI {
                      ...
                      public void gui_eventUpdateButtonClicked() {
                      addNewButton.setVisible(true);
                      formGrid.setVisible(false);
                      copyFieldDateToContact();
                      this.contactService.updateContact(currentContact);
                      }
                      private void copyFieldDateToContact() {
                      currentContact.setEmail(emailField.getText());
                      currentContact.setName(nameField.getText());
                      currentContact.setPhone(phoneField.getText());
                      }
                      

          這兩個場景應當使您了解到應用程序是如何工作的,以及它如何依賴于 App Engine for Java 提供的基礎設施。ContactListGUI 的完整代碼如清單 17 所示:


          清單 17. ContactListGUI 的完整代碼
                      package gaej.example.contact.client;
                      import java.util.List;
                      import com.google.gwt.user.client.ui.Button;
                      import com.google.gwt.user.client.ui.Grid;
                      import com.google.gwt.user.client.ui.Hyperlink;
                      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.HTMLTable.Cell;
                      public class ContactListGUI {
                      /* Constants. */
                      private static final String CONTACT_LISTING_ROOT_PANEL = "contactListing";
                      private static final String CONTACT_FORM_ROOT_PANEL = "contactForm";
                      private static final String CONTACT_STATUS_ROOT_PANEL = "contactStatus";
                      private static final String CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar";
                      private static final int EDIT_LINK = 3;
                      private static final int REMOVE_LINK = 4;
                      /* GUI Widgets */
                      protected Button addButton;
                      protected Button updateButton;
                      protected Button addNewButton;
                      protected TextBox nameField;
                      protected TextBox emailField;
                      protected TextBox phoneField;
                      protected Label status;
                      protected Grid contactGrid;
                      protected Grid formGrid;
                      /* Data model */
                      private List<Contact> contacts;
                      private Contact currentContact;
                      protected ContactServiceDelegate contactService;
                      public void init() {
                      addButton = new Button("Add new contact");
                      addNewButton = new Button("Add new contact");
                      updateButton = new Button("Update contact");
                      nameField = new TextBox();
                      emailField = new TextBox();
                      phoneField = new TextBox();
                      status = new Label();
                      contactGrid = new Grid(2,5);
                      buildForm();
                      placeWidgets();
                      }
                      private void buildForm() {
                      formGrid = new Grid(4,3);
                      formGrid.setVisible(false);
                      formGrid.setWidget(0, 0, new Label("Name"));
                      formGrid.setWidget(0, 1, nameField);
                      formGrid.setWidget(1, 0, new Label("email"));
                      formGrid.setWidget(1, 1, emailField);
                      formGrid.setWidget(2, 0, new Label("phone"));
                      formGrid.setWidget(2, 1, phoneField);
                      formGrid.setWidget(3, 0, updateButton);
                      formGrid.setWidget(3, 1, addButton);
                      }
                      private void placeWidgets() {
                      RootPanel.get(CONTACT_LISTING_ROOT_PANEL).add(contactGrid);
                      RootPanel.get(CONTACT_FORM_ROOT_PANEL).add(formGrid);
                      RootPanel.get(CONTACT_STATUS_ROOT_PANEL).add(status);
                      RootPanel.get(CONTACT_TOOL_BAR_ROOT_PANEL).add(addNewButton);
                      }
                      private void loadForm(Contact contact) {
                      this.formGrid.setVisible(true);
                      currentContact = contact;
                      this.emailField.setText(contact.getEmail());
                      this.phoneField.setText(contact.getPhone());
                      this.nameField.setText(contact.getName());
                      }
                      private void copyFieldDateToContact() {
                      currentContact.setEmail(emailField.getText());
                      currentContact.setName(nameField.getText());
                      currentContact.setPhone(phoneField.getText());
                      }
                      public void gui_eventContactGridClicked(Cell cellClicked) {
                      int row = cellClicked.getRowIndex();
                      int col = cellClicked.getCellIndex();
                      Contact contact = this.contacts.get(row);
                      this.status.setText("Name was " + contact.getName() + " clicked ");
                      if (col==EDIT_LINK) {
                      this.addNewButton.setVisible(false);
                      this.updateButton.setVisible(true);
                      this.addButton.setVisible(false);
                      this.emailField.setReadOnly(true);
                      loadForm(contact);
                      } else if (col==REMOVE_LINK) {
                      this.contactService.removeContact(contact);
                      }
                      }
                      public void gui_eventAddButtonClicked() {
                      addNewButton.setVisible(true);
                      formGrid.setVisible(false);
                      copyFieldDateToContact();
                      this.phoneField.getText();
                      this.contactService.addContact(currentContact);
                      }
                      public void gui_eventUpdateButtonClicked() {
                      addNewButton.setVisible(true);
                      formGrid.setVisible(false);
                      copyFieldDateToContact();
                      this.contactService.updateContact(currentContact);
                      }
                      public void gui_eventAddNewButtonClicked() {
                      this.addNewButton.setVisible(false);
                      this.updateButton.setVisible(false);
                      this.addButton.setVisible(true);
                      this.emailField.setReadOnly(false);
                      loadForm(new Contact());
                      }
                      public void service_eventListRetrievedFromService(List<Contact> result) {
                      status.setText("Retrieved contact list");
                      this.contacts = result;
                      this.contactGrid.clear();
                      this.contactGrid.resizeRows(this.contacts.size());
                      int row = 0;
                      for (Contact contact : result) {
                      this.contactGrid.setWidget(row, 0, new Label(contact.getName()));
                      this.contactGrid.setWidget(row, 1, new Label (contact.getPhone()));
                      this.contactGrid.setWidget(row, 2, new Label (contact.getEmail()));
                      this.contactGrid.setWidget(row, EDIT_LINK, new Hyperlink("Edit", null));
                      this.contactGrid.setWidget(row, REMOVE_LINK, new Hyperlink("Remove", null));
                      row ++;
                      }
                      }
                      public void service_eventAddContactSuccessful() {
                      status.setText("Contact was successfully added");
                      this.contactService.listContacts();
                      }
                      public void service_eventUpdateSuccessful() {
                      status.setText("Contact was successfully updated");
                      this.contactService.listContacts();
                      }
                      public void service_eventRemoveContactSuccessful() {
                      status.setText("Contact was removed");
                      this.contactService.listContacts();
                      }
                      public void service_eventUpdateContactFailed(Throwable caught) {
                      status.setText("Update contact failed");
                      }
                      public void service_eventAddContactFailed(Throwable caught) {
                      status.setText("Unable to update contact");
                      }
                      public void service_eventRemoveContactFailed(Throwable caught) {
                      status.setText("Remove contact failed");
                      }
                      public void service_eventListContactsFailed(Throwable caught) {
                      status.setText("Unable to get contact list");
                      }
                      }
                      





          結束語

          這個共包含三部分的 Google App Engine for Java 系列文章的第二部分向您介紹了如何使用 App Engine for Java 的 Eclipse 插件工具創建定制 GWT 應用程序。在構建簡單的聯系人管理應用程序的過程中,您學會了以下內容:

          • 構建可以異步工作的遠程服務
          • 組織 GUI 代碼以避免嵌套的內部類聲明
          • 利用 GWT 為兩個關鍵用例實現功能

          敬請期待本文的第 3 部分,您將對聯系人管理應用程序進行優化,并通過 App Engine for Java 數據存儲功能添加對持久化 Contact 對象的支持。



          posted @ 2009-09-22 19:54 周一 閱讀(140) | 評論 (0)編輯 收藏

          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 App Engine for Java

          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 應用程序

          如果您已經閱讀了前面的內容,那么已經準備好開始構建第一個 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 中的新按鈕
          圖 1. Eclipse IDE 中的新按鈕

          下面列出了這些按鈕的功能:

          • 藍色小球讓您能夠訪問 App Engine for Java 項目創建向導。
          • 紅色工具箱讓您編譯一個 GWT 項目。
          • 迷你噴氣式飛機圖標讓您能夠部署一個 App Engine 項目。

          您將使用項目創建向導創建兩個新項目:一個基于 servlets,另一個使用 GWT 構建。將使用工具箱功能編譯 GWT 項目。當您準備好部署 App Engine 項目時,將啟動迷你噴氣式分機,激活項目。

          首先創建一個 App Engine for Java 項目。第一步,單擊藍色小球以訪問項目創建向導。然后使用名稱為 gaej.example 的包創建名為 SimpleServletApp 的應用程序,如圖 2 所示:


          圖 2. 開始一個新項目
          圖 2. 開始一個新項目

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


          圖 3. SimpleServletApp 項目
          圖 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 部署服務器
          圖 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 應用程序
          圖 5. 使用 App Engine for Java 項目創建向導創建一個簡單的 GWT 應用程序

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



          圖 6. 為 GWT 應用程序提供的代碼工件
          圖 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 應用程序
          圖 7. 運行樣例 GWT 應用程序

          在下一小節中,我將帶領您遍歷樣例 GWT 應用程序。如果希望了解更多關于 GWT 的信息(或者獲得 GWT 教程),請參見 參考資料

          詳細了解 GWT 應用程序

          根據已經提供的配置,Eclipse 的 GWT 工具創建了一個啟動應用程序,包含一個 HTML 前端(SimpleGWTApp.html,如 清單 10 所示),可以加載 simplegwtapp.js 和 simplegwtapp.nocache.js。這是由 GWT 從 Java 代碼中生成的 JavaScript 代碼;也就是說,位于 gaej.example.client 包中的 src 目錄下的代碼(參見清單 678)。

          創建 GUI 的主要入口點是 gaej.example.client.SimpleGWTApp,如 清單 8 所示。該類創建 GWT GUI 元素并將它們與 SimpleGWTApp.html 中的 HTML DOM 元素關聯起來(參見 清單 10)。SimpleGWTApp.html 定義了兩個 DOM 元素,分別命名為 nameFieldContainersendButtonContainer(表中的兩列)。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 目錄中(來自清單 678)。


          清單 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 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 應用程序
          圖 8. 創建一個新的 App Engine for Java 應用程序

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


          圖 9. App Engine for Java Eclipse 插件
          圖 9. App Engine for Java Eclipse 插件

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


          圖 10. 部署項目
          圖 10. 部署項目

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


          圖 11. Google App Engine 的項目設置
          圖 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 前端。


          posted @ 2009-09-22 19:51 周一 閱讀(297) | 評論 (0)編輯 收藏

          僅列出標題  
          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導航

          統計

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 陆丰市| 当涂县| 蒲城县| 平邑县| 兴义市| 惠安县| 顺平县| 松潘县| 景宁| 高阳县| 安福县| 紫阳县| 海晏县| 宣武区| 老河口市| 临澧县| 陆川县| 新安县| 肥西县| 桓台县| 涡阳县| 连南| 孝感市| 北辰区| 丽江市| 新昌县| 沙湾县| 德安县| 渭南市| 五原县| 新兴县| 尼勒克县| 阳新县| 咸阳市| 平顺县| 嘉定区| 芜湖县| 渝中区| 策勒县| 永宁县| 宜兰市|