Máy ứng dụng của Google cho Java: Phần 2: Xây dựng ứng dụng sát thủ Xây dựng ứng dụng quản lý liên hệ riêng của bạn trong Máy ứng dụng Rick Hightower , Giám đốc, eBlox Tóm tắt: Toàn bộ
Trang 1Máy ứng dụng của Google cho Java: Phần 2: Xây dựng ứng dụng sát thủ
Xây dựng ứng dụng quản lý liên hệ riêng của bạn trong Máy ứng dụng
Rick Hightower , Giám đốc, eBlox
Tóm tắt: Toàn bộ ý nghĩa của một nền tảng đám mây giống như Máy ứng dụng
của Google (Google App Engine) cho Java™ là ở chỗ có thể tưởng tượng, xây dựng và triển khai các ứng dụng sát thủ có chất lượng chuyên nghiệp có thể mở rộng — không phải phá sản ngân hàng hoặc biến mình thành người mất trí Trong phần thứ hai của bài viết ba phần của mình giới thiệu về Máy ứng dụng của
Google cho Java, Rick Hightower sẽ đưa bạn vượt ra ngoài các ví dụ làm sẵn của Phần 1 bằng một hướng dẫn từng bước để viết và triển khai một ứng dụng quản lý liên hệ đơn giản bằng cách sử dụng Máy ứng dụng cho Java
Trong Phần 1 của bài giới thiệu này về xây dựng các ứng dụng Java có khả năng
mở rộng với Máy ứng dụng (App Engine) cho Java, bạn đã tìm hiểu bộ công cụ Eclipse và cơ sở hạ tầng của nền tảng điện toán đám mây của Google (hay PAAS) cho các nhà phát triển Java Các ví dụ trong bài viết đã có sẵn, để cho bạn có thể tập trung vào việc tích hợp Máy ứng dụng cho Java với Eclipse và nhanh chóng thực hành xây dựng và triển khai các kiểu ứng dụng khác nhau — cụ thể là xây dựng một ứng dụng bằng cách sử dụng Bộ công cụ Web của Google (Google Web Toolkit - GWT) và một ứng dụng dựa trên servlet Bài viết này xây dựng trên nền tảng đó và cũng chuẩn bị cho bạn các bài tập lập trình nâng cao hơn trong Phần 3 của bài viết này
Ứng dụng quản lý-liên hệ mà bạn sẽ xây dựng cho phép một người sử dụng lưu trữ thông tin liên hệ cơ bản như tên, địa chỉ thư điện tử (e-mail) và số điện thoại Để tạo ứng dụng này, bạn sẽ sử dụng trình thủ thuật tạo dự án GWT của Eclipse
Để thực hiện bài tập này, bạn sẽ bắt đầu bằng một ứng dụng CRUD đơn giản và sau đó thêm kho lưu trữ thực Bây giờ, hãy sử dụng một đối tượng truy cập dữ liệu (DAO) với triển khai thực hiện giả, như thấy trong Liệt kê 1:
Trang 2Liệt kê 1 Giao diện với 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 thêm các phương thức để thêm một mối liên hệ, loại bỏ một mối liên
hệ, cập nhật một mối liên hệ và trả về một danh sách tất cả các mối liên hệ Nó là một giao diện CRUD rất cơ bản để quản lý các mối liên hệ Lớp Contact (Liên hệ)
là đối tượng miền của bạn, như thấy trong Liệt kê 2:
Liệt kê 2 Đối tượng miền liên hệ (gaej.example.contact.client.Contact)
Trang 3package 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;
Trang 5}
Đối với phiên bản đầu tiên của ứng dụng này, bạn sẽ làm việc với một đối tượng giả, lưu trữ các mối liên hệ trong một bộ sưu tập nằm ngay trong bộ nhớ, như thấy trong Liệt kê 3:
Liệt kê 3 Lớp DAO giả
Trang 6Map<String, Contact> map = new LinkedHashMap<String, Contact>();
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())); }
Trang 7public void removeContact(Contact contact) {
Liệt kê 4 ContactServiceImpl
package gaej.example.contact.server;
import java.util.ArrayList;
import java.util.List;
Trang 8private 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);
Trang 9Liệt kê 5 web.xml for ContactService
<servlet>
<servlet-name>contacts</servlet-name>
<servlet-class>gaej.example.contact.server.ContactServiceImpl</servlet-class> </servlet>
Trang 10Liệt kê 6 ContactService
Trang 11void addContact(Contact contact);
void removeContact(Contact contact);
void updateContact(Contact contact);
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); }
Trang 12Chú ý rằng ContactService triển khai thực hiện giao diện RemoteService và định nghĩa một @RemoteServiceRelativePath, chỉ rõ một đường dẫn tương đối của
"các mối liên hệ" Đường dẫn tương đối này tương ứng với đường dẫn mà bạn đã định nghĩa cho dịch vụ này trong tệp web.xml (chúng phải khớp)
ContactServiceAsync có các đối tượng gọi lại sao cho GWT GUI có thể được thông báo về các cuộc gọi từ máy chủ mà không chặn hoạt động của máy khách khác
Đi tắt qua mã rối (spaghetti code)
Tôi không phải là một người hâm mộ viết mã rắc rối và tôi tránh viết như thế bất
cứ khi nào tôi có thể Một ví dụ về mã rối sẽ là một nhóm các lớp bên trong ẩn danh mà các phương thức của nó xác định các lớp vô danh bên trong Các lớp bên trong này, đến lượt chúng, lại thực hiện các cuộc gọi lại để gọi các phương thức,
mà sau đó các phương thức này được định nghĩa nội tuyến trong một lớp bên
trong Eo ôi! Thành thật mà nói, tôi không thể đọc hoặc hiểu mã bị làm rối tung
lên, ngay cả khi nó chính là mã tôi viết! Vì vậy, để dàn phẳng những thứ này ra một chút, tôi muốn đề nghị chia GUI GWT thành ba phần:
Tệp ContactList.gwt.xml (một nguồn tài nguyên trong gaej.example.contact) chỉ
rõ ContactListEntryPoint là điểm vào chính cho ứng dụng này bằng cách sử dụng phần tử entry-point (điểm vào) như chỉ ra trong Liệt kê 8:
Liệt kê 8 ContactList.gwt.xml
<entry-point class='gaej.example.contact.client.ContactListEntryPoint'/>
Trang 13Lớp ContactListEntryPoint triển khai thực hiện giao diện EntryPoint từ GWT (com.google.gwt.core.client.EntryPoint), báo hiệu rằng lớp này sẽ được gọi để khởi tạo GUI ContactListEntryPoint không làm nhiều việc Nó tạo ra một cá thể của ContactListGUI và một cá thể của ContactServiceDelegate, và sau đó cho phép chúng biết về nhau để cho chúng có thể cộng tác ContactListEntryPoint sau
đó thực hiện việc kết nối sự kiện GUI ContactListEntryPoint được hiển thị trong Liệt kê 9:
Liệt kê 9 ContactListEntryPoint
public class ContactListEntryPoint implements EntryPoint {
private ContactListGUI gui;
private ContactServiceDelegate delegate;
Trang 14
gui = new ContactListGUI();
delegate = new ContactServiceDelegate();
public void onClick(ClickEvent event) {
Cell cellForEvent = gui.contactGrid.getCellForEvent(event); gui.gui_eventContactGridClicked(cellForEvent); }});
Trang 15Chú ý rằng ContactListEntryPoint kết nối các sự kiện dành cho addButton,
updateButton, contactGrid và addNewButton Nó làm điều này bằng cách đăng ký các lớp bên trong vô danh đã triển khai thực hiện giao diện lắng nghe (listener) các
Trang 16sự kiện của các tiện ích GUI ấy Đó là một kỹ thuật rất giống với xử lý sự kiện trong Swing Các sự kiện tiện ích GUI là từ các tiện ích được GUI
(ContactListGUI) tạo ra, tôi sẽ thảo luận nó một chút Chú ý rằng lớp GUI có các phương thức gui_eventXXX để đáp lại các sự kiện GUI
ContactListGUI tạo các tiện ích GUI và đáp lại các sự kiện từ chúng
ContactListGUI dịch các sự kiện GUI thành các hành động mà người dùng muốn thực hiện trên ContactsService ContactListGUI sử dụng ContactServiceDelegate
để gọi các phương thức trên ContactService ContactServiceDelegate tạo ra một giao diện không đồng bộ với ContactService và sử dụng nó để tiến hành các cuộc gọi Ajax không đồng bộ ContactServiceDelegate sẽ thông báo cho
ContactListGUI về các các sự kiện (trả về thành công hay thất bại) từ dịch vụ này ContactServiceDelegate được hiển thị trong Liệt kê 10:
Liệt kê 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 =
Trang 17public void onFailure(Throwable caught) {
}
}//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) {
Trang 18);//end of addContact method call
}
void updateContact(final Contact contact) {
contactService.updateContact(contact, new AsyncCallback<Void> () { public void onFailure(Throwable caught) {
}//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);
}
Trang 19public void onSuccess(Void result) {
gui.service_eventRemoveContactSuccessful();
}
}//end of inner class
);//end of updateContact method call
có thể dễ dàng đọc và làm việc với nó sau này) ContactListGUI chỉ dài 186 dòng
và khá đơn giản ContactListGUI quản lý chín tiện ích GUI và cũng cộng tác với ContactServiceDelegate để quản lý một danh sách CRUD, như trong Liệt kê 11:
Liệt kê 11 ContactListGUI đang hoạt động
package gaej.example.contact.client;
import java.util.List;
import com.google.gwt.user.client.ui.Button;
Trang 20private static final int EDIT_LINK = 3;
private static final int REMOVE_LINK = 4;
/* GUI Widgets */
protected Button addButton;
protected Button updateButton;
protected Button addNewButton;
Trang 21protected 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;
Chú ý rằng ContactListGUI theo dõi mối liên hệ hiện tại được nạp vào biểu mẫu (currentContact) và danh sách các mối liên hệ trong danh sách (contacts) Hình 1 cho thấy các tiện ích tương ứng với GUI được tạo ra như thế nào:
Trang 22Hình 1 Các tiện ích hoạt động trong GUI quản lý liên hệ
Liệt kê 12 cho thấy ContactListGUI tạo các tiện ích và biểu mẫu về một mối liên
hệ và sau đó đặt các tiện ích trong biểu mẫu đó:
Liệt kê 12 ContactListGUI tạo và đặt các tiện ích
public class ContactListGUI {
Trang 23private 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);
Trang 24
formGrid.setWidget(0, 0, new Label("Name"));
RootPanel.get(CONTACT_STATUS_ROOT_PANEL).add(status);
RootPanel.get(CONTACT_TOOL_BAR_ROOT_PANEL).add(addNewButton); }
Trang 25Phương thức ContactListEntryPoint.onModuleLoad gọi phương thức
ContactListGUI init Phương thức init gọi phương thức buildForm để tạo ra một biểu mẫu mới và điền các trường để chỉnh sửa các dữ liệu liên hệ Phương thức init sau đó gọi phương thức placeWidgets, để đặt các tiện ích contactGrid, formGrid, status và addNewButton vào các khe được định nghĩa trong các trang HTML chứa ứng dụng GUI này, được định nghĩa trong Liệt kê 13:
Liệt kê 13 ContactList.html định nghĩa các khe cho các tiên ích
<h1>Contact List Example</h1>
Trang 26Các hằng số (như CONTACT_LISTING_ROOT_PANEL="contactListing") tương ứng với các mã nhận dạng (ID) của các phần tử (như (like
id="contactListing") được định nghĩa trong trang HTML Điều này cho phép một nhà thiết kế trang có quyền kiểm soát nhiều hơn đối với cách bố trí các tiện ích của ứng dụng
Với ứng dụng cơ bản đã xây dựng, chúng ta hãy đi qua một vài kịch bản sử dụng thông thường
Hiển thị một danh sách khi nạp trang
Khi trang của ứng dụng quản lý liên hệ nạp lần đầu tiên nó gọi phương thức
onModuleLoad của ContactListEntryPoint onModuleLoad gọi phương thức
listContacts của ContactServiceDelegate, phương thức listContacts gọi phương thức listContact của dịch vụ một cách không đồng bộ Khi phương thức
listContact trả về, một lớp bên trong vô danh được định trong
ContactServiceDelegate gọi phương thức trình xử lý sự kiện dịch vụ có tên là service_eventListRetrievedFromService, như thấy trong Liệt kê 14:
Liệt kê 14 Gọi trình xử lý sự kiện listContact
public class ContactListGUI {
Trang 27for (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
ba cột đầu tiên của mỗi hàng Nó cũng cung cấp một đường liên kết Edit (Chỉnh sửa) và một đường liên kết Remove (Loại bỏ) cho mỗi mối liên hệ, cho phép người dùng dễ dàng loại bỏ và sửa mối liên hệ
Người dùng sửa đổi một mối liên hệ hiện có
Khi người dùng nhấn vào một đường liên kết Edit từ danh sách các các mối liên
hệ, gui_eventContactGridClicked được gọi, như chỉ ra trong Liệt kê 15:
Liệt kê 15 Phương thức trình xử lý sự kiện gui_eventContactGridClicked của ContactListGUI
Trang 28public 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 ");
Trang 29emailField chỉ đọc để cho người dùng không thể chỉnh sửa trường e-mail này Tiếp theo, gui_eventContactGridClicked gọi loadForm (như thấy trong Liệt kê
15), đặt trạng thái formGrid thành visible (nhìn thấy được), đặt trạng thái mối liên
hệ là đang được chỉnh sửa và sau đó sao chép các thuộc tính của mối liên hệ này vào các tiện ích emailField, phoneField và nameField
Khi người dùng nhấn vào updateButton, phương thức trình xử lý sự kiện
gui_eventUpdateButtonClicked được gọi, như trong Liệt kê 16 Phương thức này làm cho addNewButton nhìn thấy được (như vậy người sử dụng có thể thêm số liên hệ mới) và che dấu formGrid Sau đó nó gọi copyFieldDateToContact, sao chép văn bản từ các tiện ích emailField, phoneField và nameField trở lại vào các thuộc tính của currentContact Rồi nó gọi phương thức ContactServiceDelegate updateContact để chuyển mối liên hệ vừa được cập nhật trở lại cho dịch vụ
Liệt kê 16.Phương thức trình xử lý sự kiện gui_eventUpdateButtonClicked của ContactListGUI