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("[email protected]", new Contact("Rick Hightower", "[email protected]", "520-555-1212")); map.put("[email protected]", new Contact("Scott Fauerbach", "[email protected]", "520-555-1213")); map.put("[email protected]", new Contact("Bob Dean", "[email protected]", "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
,随后定义方法来添加联系人、列出联系人、删除联系人,以及更新联系人。它将所有这些操作委托给 ContactDAOMock
。ContactServiceImpl
不过是一个围绕 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 组件并处理来自 GUI
和 Service
的事件。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();
}});
}
}
|
注意,ContactListEntryPoint
为 addButton
、updateButton
、contactGrid
和 addNewButton
连接事件。具体做法是注册为小部件事件实现侦听器接口的匿名内部类。这与 Swing 中的事件处理非常相似。这些小部件事件来自由 GUI 创建的小部件(ContactListGUI
),我将稍后进行讨论。注意,GUI 类包含 gui_eventXXX
方法来响应 GUI 事件。
ContactListGUI
创建了 GUI 小部件并响应来自它们的事件。ContactListGUI
将 GUI 事件转换为用户希望对 ContactsService
执行的操作。ContactListGUI
使用 ContactServiceDelegate
对 ContactService
调用方法。ContactServiceDelegate
对 ContactService
创建一个异步接口并使用它发出异步 Ajax 调用。ContactServiceDelegate
向 ContactListGUI
通知来自服务的事件(成功或失败)。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 中活动的小部件
![Google App Engine for Java: 第 2 部分:构建杀手级应用_第1张图片](http://img.e-com-net.com/image/product/3d6b6e35de1e428d942142a6cba6194a.jpg)
清单 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
方法,随后将 contactGrid
、formGrid
、status
和 addNewButton
小部件放到 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"
)。这允许页面设计师进一步控制应用程序小部件的布局。
对于基本的应用程序构建,让我们了解几个常见的使用场景。
展示一个有关页面加载的链接
当联系人管理应用程序的页面首次加载时,它将调用 ContactListEntryPoint
的 onModuleLoad
方法。onModuleLoad
调用 ContactServiceDelegate
的 listContacts
方法,后者异步调用服务的 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 链接是否被单击。具体做法是找到那个列被单击。随后隐藏 addNewButton
和 addButton
,并使 updateButton
可见。updateButton
显示在 formGrid
中,允许用户将更新信息发送回 ContactService
。它还使 emailField
变为只读,这样用户就不能编辑电子邮件字段。接下来,gui_eventContactGridClicked
调用 loadForm
(如 清单 15 所示),后者将 formGrid
设置为可见,设置正在被编辑的联系人,然后将联系人属性复制到 emailField
、phoneField
和 nameField
小部件中。
当用户单击 updateButton
时,gui_eventUpdateButtonClicked
事件处理器方法被调用,如清单 16 所示。这个方法使 addNewButton
变为可见(这样用户就可以编辑新的联系人)并隐藏了 formGrid
。它随后调用 copyFieldDateToContact
,后者将来自 emailField
、phoneField
和 nameField
小部件的文本复制回 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
对象的支持。