周一的專欄

          Java架構專題

          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 on 2009-09-22 19:54 周一 閱讀(140) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2009年9月>
          303112345
          6789101112
          13141516171819
          20212223242526
          27282930123
          45678910

          導航

          統計

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 邢台市| 福建省| 高碑店市| 当雄县| 商丘市| 抚顺市| 平湖市| 双流县| 宣威市| 磴口县| 安义县| 顺昌县| 三门县| 和硕县| 得荣县| 甘孜县| 从江县| 许昌市| 潼南县| 长宁区| 佛教| 彰化市| 沅江市| 扎鲁特旗| 泾阳县| 舞阳县| 广西| 东平县| 确山县| 永清县| 新野县| 莫力| 响水县| 和政县| 闵行区| 盱眙县| 通州市| 汤原县| 辽源市| 井冈山市| 新蔡县|