| 級別: 初級 Richard Hightower, 首席技術(shù)官, Mammatus Inc.
2009 年 8 月 31 日 諸 如 Google App Engine for Java? 這樣的云平臺的關(guān)鍵在于能夠設計、構建和部署專(zhuān)業(yè)級的應用程序 —— 可以非常容易地進(jìn)行伸縮。在這個(gè)包含三部分的 Google App Engine for Java 系列文章第二篇中,Rick Hightower 將通過(guò)一個(gè)分步指南,使用 Google App Engine for Java 來(lái)編寫(xiě)和部署一個(gè)簡(jiǎn)單的聯(lián)系人管理應用程序,從而超越 第 1 部分 中提供的現成示例。 在介紹使用 App Engine for Java 構建可伸縮 Java 應用程序的 第 1 部分 中,您了解了 Google 云計算平臺(即 PAAS)為 Java 開(kāi)發(fā)人員提供的 Eclipse 工具和基礎設施。該文章中的示例都是預先準備的,這樣您可以將精力集中到 App Engine for Java 與 Eclipse 的集成中,并快速構建和部署不同類(lèi)型的應用程序 — 即使用 Google Web Toolkit (GWT) 構建的應用程序和基于 servlet 的應用程序。本文將在此基礎上展開(kāi),并且在本系列第 3 部分中提供了更加高級的編程實(shí)踐。 您將構建的聯(lián)系人管理應用程序允許用戶(hù)存儲基本的聯(lián)系人信息,比如名稱(chēng)、電子郵件地址和電話(huà)號碼。要創(chuàng )建這個(gè)應用程序,將需要使用 Eclipse GWT 項目創(chuàng )建向導。 | 云計算空間 您是否希望隨時(shí)獲取最新的云計算消息?是否想得到云計算相關(guān)的技術(shù)知識?developerWorks 云計算空間就是這樣一個(gè)云計算信息資源的門(mén)戶(hù),在這里您可以了解來(lái)自 IBM 和業(yè)界其他媒體的最新信息,并且得到如何在云環(huán)境中使用 IBM 軟件的入門(mén)知識。 IBM 在 Amazon EC2 云計算環(huán)境中提供了 DB2、Informix、Lotus、WebSphere 等方面的 AMI 鏡像資源。您只需按使用量支付少量費用,就可以使用到云上的數據、門(mén)戶(hù)、Web 內容管理、情景應用等服務(wù)。歡迎您隨時(shí)訪(fǎng)問(wèn) 云計算空間,獲取更多信息。 | | 從 CRUD 到聯(lián)系人應用程序 正如目前您已經(jīng)了解到的一樣,在 App Engine for Java 中構建新應用程序的第一步就是在 Eclipse 啟動(dòng)項目創(chuàng )建向導。之后,您可以打開(kāi) GWT 項目啟動(dòng)向導來(lái)創(chuàng )建 GWT 項目(本文 第 1 部分 給出了在 App Engine for Java 中創(chuàng )建 GWT 項目的詳細說(shuō)明)。 對于這個(gè)練習,您將啟動(dòng)一個(gè)簡(jiǎn)單的 CRUD 應用程序,并稍后添加實(shí)際的存儲。我們將使用一個(gè)具有模擬實(shí)現的數據訪(fǎng)問(wèn)對象(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 添加了各種方法,可以添加聯(lián)系人、刪除聯(lián)系人、更新聯(lián)系人,并返回一個(gè)所有聯(lián)系人的列表。它是一個(gè)非?;镜?CRUD 接口,可以管理聯(lián)系人。Contact 類(lèi)是您的域對象,如清單 2 所示: 清單 2. 聯(lián)系人域對象(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; } } | 對于這個(gè)應用程序的第一個(gè)版本,您將使用一個(gè)模擬對象將聯(lián)系人存儲在一個(gè)內存集合中,如清單 3 所示: 清單 3. Mock DAO 類(lèi) 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); } } | 創(chuàng )建遠程服務(wù) 您現在的目標是創(chuàng )建一個(gè)允許您使用 DAO 的 GWT GUI。將使用 ContactDAO 接口上的所有方法。第一步是將 DAP 類(lèi)(未來(lái)版本將直接與服務(wù)器端的數據存儲通信,因此必須位于服務(wù)器中)的功能封裝到一個(gè)服務(wù)中,如清單 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 實(shí)現了 RemoteServiceServlet,隨后定義方法來(lái)添加聯(lián)系人、列出聯(lián)系人、刪除聯(lián)系人,以及更新聯(lián)系人。它將所有這些操作委托給 ContactDAOMock。ContactServiceImpl 不過(guò)是一個(gè)圍繞 ContactDAO 的包裝器,后者將 ContactDAO 功能公開(kāi)給 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 前端訪(fǎng)問(wèn)該服務(wù),需要定義一個(gè)遠程服務(wù)接口和一個(gè)異步遠程服務(wù)接口,如清單 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 實(shí)現了 RemoteService 接口并定義了一個(gè) @RemoteServiceRelativePath,指定了 “聯(lián)系人” 的相對路徑。相對路徑與您在 web.xml 文件中為服務(wù)定義的路徑是對應的(必須匹配)。ContactServiceAsync 包含回調對象,因此 GWT GUI 可以收到來(lái)自服務(wù)器的調用的通知,而不會(huì )阻塞其他客戶(hù)機行為。 避免編寫(xiě)雜亂的代碼 我 并不喜歡編寫(xiě)雜亂的代碼,因此在可能的情況下會(huì )盡量避免編寫(xiě)這類(lèi)代碼。這類(lèi)代碼的一個(gè)例子就是一組匿名內部類(lèi),這些類(lèi)的方法定義匿名內部類(lèi)。這些內部類(lèi)反 過(guò)來(lái)執行回調,調用在某個(gè)內部類(lèi)中以?xún)嚷?lián)方式定義的方法。坦白說(shuō),我無(wú)法閱讀或是理解這些糾纏在一起的代碼,即使是我自己編寫(xiě)的!因此,為了將代碼稍微簡(jiǎn) 單化,我建議將 GWT GUI 分解為三個(gè)部分: -
ContactListEntryPoint -
ContactServiceDelegate -
ContactListGUI ContactListEntryPoint 是主要的入口點(diǎn);它執行 GUI 事件連接。ContactServiceDelegate 封裝 ContactService 功能并隱藏內部類(lèi)回調連接。ContactListGUI 管理所有 GUI 組件并處理來(lái)自 GUI 和 Service 的事件。ContactListGUI 使用 ContactServiceDelegate 發(fā)出 ContactService 請求。 ContactList.gwt.xml 文件(位于 gaej.example.contact 下的一個(gè)資源)使用 entry-point 元素將 ContactListEntryPoint 指定為應用程序的主要入口點(diǎn),如清單 8 所示: 清單 8. ContactList.gwt.xml <entry-point class='gaej.example.contact.client.ContactListEntryPoint'/> | ContactListEntryPoint 類(lèi)實(shí)現了 GWT 的 EntryPoint 接口(com.google.gwt.core.client.EntryPoint),并指定將調用該類(lèi)來(lái)初始化 GUI。ContactListEntryPoint 所做的工作并不多。它創(chuàng )建一個(gè) ContactListGUI 實(shí)例和一個(gè) ContactServiceDelegate 實(shí)例,然后讓它們彼此了解對方,這樣就可以展開(kāi)協(xié)作。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(); }}); } } | 注意,ContactListEntryPoint 為 addButton、updateButton、contactGrid 和 addNewButton 連接事件。具體做法是注冊為小部件事件實(shí)現偵聽(tīng)器接口的匿名內部類(lèi)。這與 Swing 中的事件處理非常相似。這些小部件事件來(lái)自由 GUI 創(chuàng )建的小部件(ContactListGUI),我將稍后進(jìn)行討論。注意,GUI 類(lèi)包含 gui_eventXXX 方法來(lái)響應 GUI 事件。 ContactListGUI 創(chuàng )建了 GUI 小部件并響應來(lái)自它們的事件。ContactListGUI 將 GUI 事件轉換為用戶(hù)希望對 ContactsService 執行的操作。ContactListGUI 使用 ContactServiceDelegate 對 ContactService 調用方法。ContactServiceDelegate 對 ContactService 創(chuàng )建一個(gè)異步接口并使用它發(fā)出異步 Ajax 調用。ContactServiceDelegate 向 ContactListGUI 通知來(lái)自服務(wù)的事件(成功或失?。?。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 通過(guò)以 service_eventXXX 開(kāi)頭的方法向 ContactListGUI 發(fā)出有關(guān)服務(wù)事件的通知。如前所述,我編寫(xiě) ContactListGUI 的目標之一就是避免嵌套的內部類(lèi)并創(chuàng )建一個(gè)相對扁平的 GUI 類(lèi)(我可以非常方便地閱讀和理解的類(lèi))。ContactListGUI 只有 186 行,因此非常簡(jiǎn)單。ContactListGUI 管理 9 個(gè) GUI 小部件并與 ContactServiceDelegate 協(xié)作來(lái)管理一個(gè) CRUD 清單,如清單 11 所示: 清單 11. ContactListGUI 的實(shí)際使用 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 跟蹤表單中加載的當前聯(lián)系人(currentContact)和清單中的聯(lián)系人列表(contacts)。圖 1 展示了小部件如何對應于創(chuàng )建的 GUI: 圖 1. 聯(lián)系人管理 GUI 中活動(dòng)的小部件 清單 12 展示了 ContactListGUI 如何創(chuàng )建小部件和聯(lián)系人表單,并將小部件放到表單中: 清單 12. ContactListGUI 創(chuàng )建并放置小部件 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 方法創(chuàng )建。init 方法調用 buildForm 方法來(lái)創(chuàng )建新的表單網(wǎng)格并使用字段填充,以編輯聯(lián)系人數據。init 方法隨后調用 placeWidgets 方法,隨后將 contactGrid、formGrid、status 和 addNewButton 小部件放到 HTML 頁(yè)面中定義的插槽中,這個(gè) HTML 頁(yè)面托管了清單 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 頁(yè)面中定義的元素的 ID(類(lèi)似 id="contactListing")。這允許頁(yè)面設計師進(jìn)一步控制應用程序小部件的布局。 對于基本的應用程序構建,讓我們了解幾個(gè)常見(jiàn)的使用場(chǎng)景。 展示一個(gè)有關(guān)頁(yè)面加載的鏈接 當聯(lián)系人管理應用程序的頁(yè)面首次加載時(shí),它將調用 ContactListEntryPoint 的 onModuleLoad 方法。onModuleLoad 調用 ContactServiceDelegate 的 listContacts 方法,后者異步調用服務(wù)的 listContact 方法。當 listContact 方法返回時(shí),ContactServiceDelegate 中定義的匿名內部類(lèi)將調用名為 service_eventListRetrievedFromService 的服務(wù)事件處理器方法,如清單 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 事件處理器方法存儲由服務(wù)器發(fā)送的聯(lián)系人列表。然后它將清空顯示聯(lián)系人列表的 contactGrid。它將重新調整行數,以匹配服務(wù)器返回的聯(lián)系人列表的大小。隨后遍歷聯(lián)系人列表,將每個(gè)聯(lián)系人的姓名、電話(huà)、電子郵件數據放到每一行的前三個(gè)列中。它還為每個(gè)聯(lián)系人提供了 Edit 鏈接和一個(gè) Remove 鏈接,使用戶(hù)能夠輕松地刪除和編輯聯(lián)系人。 用戶(hù)編輯現有的聯(lián)系人 當用戶(hù)單擊聯(lián)系人列表中的 Edit 鏈接時(shí),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 鏈接是否被單擊。具體做法是找到那個(gè)列被單擊。隨后隱藏 addNewButton 和 addButton,并使 updateButton 可見(jiàn)。updateButton 顯示在 formGrid 中,允許用戶(hù)將更新信息發(fā)送回 ContactService。它還使 emailField 變?yōu)橹蛔x,這樣用戶(hù)就不能編輯電子郵件字段。接下來(lái),gui_eventContactGridClicked 調用 loadForm(如 清單 15 所示),后者將 formGrid 設置為可見(jiàn),設置正在被編輯的聯(lián)系人,然后將聯(lián)系人屬性復制到 emailField、phoneField 和 nameField 小部件中。 當用戶(hù)單擊 updateButton 時(shí),gui_eventUpdateButtonClicked 事件處理器方法被調用,如清單 16 所示。這個(gè)方法使 addNewButton 變?yōu)榭梢?jiàn)(這樣用戶(hù)就可以編輯新的聯(lián)系人)并隱藏了 formGrid。它隨后調用 copyFieldDateToContact,后者將來(lái)自 emailField、phoneField 和 nameField 小部件的文本復制回 currentContact 的屬性。隨后調用 ContactServiceDelegate updateContact 方法來(lái)將新更新的聯(lián)系人傳遞回服務(wù)。 清單 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()); } | 這兩個(gè)場(chǎng)景應當使您了解到應用程序是如何工作的,以及它如何依賴(lài)于 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"); } } |
結束語(yǔ) 這 個(gè)共包含三部分的 Google App Engine for Java 系列文章的第二部分向您介紹了如何使用 App Engine for Java 的 Eclipse 插件工具創(chuàng )建定制 GWT 應用程序。在構建簡(jiǎn)單的聯(lián)系人管理應用程序的過(guò)程中,您學(xué)會(huì )了以下內容: - 構建可以異步工作的遠程服務(wù)
- 組織 GUI 代碼以避免嵌套的內部類(lèi)聲明
- 利用 GWT 為兩個(gè)關(guān)鍵用例實(shí)現功能
敬請期待本文的第 3 部分,您將對聯(lián)系人管理應用程序進(jìn)行優(yōu)化,并通過(guò) App Engine for Java 數據存儲功能添加對持久化 Contact 對象的支持。
參考資料 學(xué)習 獲得產(chǎn)品和技術(shù) 討論
關(guān)于作者 | | | | Rick Hightower 是 Mammatus Inc. 的首席技術(shù)官,Mammatus Inc. 是一家從事云計算、GWT、Java EE、Spring 和 Hibernate 開(kāi)發(fā)的培訓公司。他是暢銷(xiāo)書(shū) Java Tools for Extreme Programming 的合著(zhù)者,并且撰寫(xiě)了 Struts Live 的第一版 — 該書(shū)多年來(lái)在 TheServerSide.com 上的下載次數一直位列第一。他還為 IBM developerWorks 撰寫(xiě)文章和教程,并且是 Java Developer's Journal 的編委會(huì )的成員,他還經(jīng)常在 DZone 上針對 Java 和 Groovy 主題發(fā)表文章。 | |