在线一级观看,国产精品一区二区久久精品,国产一区二区三区站长工具http://www.aygfsteel.com/startfromheart/Java架構(gòu)專題zh-cnMon, 04 Aug 2025 09:30:44 GMTMon, 04 Aug 2025 09:30:44 GMT60Google App Engine for Java: 第 2 部分:構(gòu)建殺手級應(yīng)用http://www.aygfsteel.com/startfromheart/archive/2009/09/22/296077.html周一周一Tue, 22 Sep 2009 11:54:00 GMThttp://www.aygfsteel.com/startfromheart/archive/2009/09/22/296077.htmlhttp://www.aygfsteel.com/startfromheart/comments/296077.htmlhttp://www.aygfsteel.com/startfromheart/archive/2009/09/22/296077.html#Feedback0http://www.aygfsteel.com/startfromheart/comments/commentRss/296077.htmlhttp://www.aygfsteel.com/startfromheart/services/trackbacks/296077.html在介紹使用 App Engine for Java 構(gòu)建可伸縮 Java 應(yīng)用程序的 第 1 部分 中,您了解了 Google 云計算平臺(即 PAAS)為 Java 開發(fā)人員提供的 Eclipse 工具和基礎(chǔ)設(shè)施。該文章中的示例都是預(yù)先準(zhǔn)備的,這樣您可以將精力集中到 App Engine for Java 與 Eclipse 的集成中,并快速構(gòu)建和部署不同類型的應(yīng)用程序 — 即使用 Google Web Toolkit (GWT) 構(gòu)建的應(yīng)用程序和基于 servlet 的應(yīng)用程序。本文將在此基礎(chǔ)上展開,并且在本系列第 3 部分中提供了更加高級的編程實(shí)踐。

您將構(gòu)建的聯(lián)系人管理應(yīng)用程序允許用戶存儲基本的聯(lián)系人信息,比如名稱、電子郵件地址和電話號碼。要創(chuàng)建這個應(yīng)用程序,將需要使用 Eclipse GWT 項(xiàng)目創(chuàng)建向?qū)А?


從 CRUD 到聯(lián)系人應(yīng)用程序

正如目前您已經(jīng)了解到的一樣,在 App Engine for Java 中構(gòu)建新應(yīng)用程序的第一步就是在 Eclipse 啟動項(xiàng)目創(chuàng)建向?qū)АV螅梢源蜷_ GWT 項(xiàng)目啟動向?qū)韯?chuàng)建 GWT 項(xiàng)目(本文 第 1 部分 給出了在 App Engine for Java 中創(chuàng)建 GWT 項(xiàng)目的詳細(xì)說明)。

對于這個練習(xí),您將啟動一個簡單的 CRUD 應(yīng)用程序,并稍后添加實(shí)際的存儲。我們將使用一個具有模擬實(shí)現(xiàn)的數(shù)據(jù)訪問對象(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)系人,并返回一個所有聯(lián)系人的列表。它是一個非常基本的 CRUD 接口,可以管理聯(lián)系人。Contact 類是您的域?qū)ο螅缜鍐?2 所示:



清單 2. 聯(lián)系人域?qū)ο螅╣aej.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;
            }
            }
            

對于這個應(yīng)用程序的第一個版本,您將使用一個模擬對象將聯(lián)系人存儲在一個內(nèi)存集合中,如清單 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);
            }
            }
            

創(chuàng)建遠(yuǎn)程服務(wù)

您現(xiàn)在的目標(biāo)是創(chuàng)建一個允許您使用 DAO 的 GWT GUI。將使用 ContactDAO 接口上的所有方法。第一步是將 DAP 類(未來版本將直接與服務(wù)器端的數(shù)據(jù)存儲通信,因此必須位于服務(wù)器中)的功能封裝到一個服務(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í)現(xiàn)了 RemoteServiceServlet,隨后定義方法來添加聯(lián)系人、列出聯(lián)系人、刪除聯(lián)系人,以及更新聯(lián)系人。它將所有這些操作委托給 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 前端訪問該服務(wù),需要定義一個遠(yuǎn)程服務(wù)接口和一個異步遠(yuǎn)程服務(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í)現(xiàn)了 RemoteService 接口并定義了一個 @RemoteServiceRelativePath,指定了 “聯(lián)系人” 的相對路徑。相對路徑與您在 web.xml 文件中為服務(wù)定義的路徑是對應(yīng)的(必須匹配)。ContactServiceAsync 包含回調(diào)對象,因此 GWT GUI 可以收到來自服務(wù)器的調(diào)用的通知,而不會阻塞其他客戶機(jī)行為。

避免編寫雜亂的代碼

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

  • ContactListEntryPoint
  • ContactServiceDelegate
  • ContactListGUI

ContactListEntryPoint 是主要的入口點(diǎn);它執(zhí)行 GUI 事件連接。ContactServiceDelegate 封裝 ContactService 功能并隱藏內(nèi)部類回調(diào)連接。ContactListGUI 管理所有 GUI 組件并處理來自 GUIService 的事件。ContactListGUI 使用 ContactServiceDelegate 發(fā)出 ContactService 請求。

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


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

ContactListEntryPoint 類實(shí)現(xiàn)了 GWT 的 EntryPoint 接口(com.google.gwt.core.client.EntryPoint),并指定將調(diào)用該類來初始化 GUI。ContactListEntryPoint 所做的工作并不多。它創(chuàng)建一個 ContactListGUI 實(shí)例和一個 ContactServiceDelegate 實(shí)例,然后讓它們彼此了解對方,這樣就可以展開協(xié)作。ContactListEntryPoint 然后執(zhí)行 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 連接事件。具體做法是注冊為小部件事件實(shí)現(xiàn)偵聽器接口的匿名內(nèi)部類。這與 Swing 中的事件處理非常相似。這些小部件事件來自由 GUI 創(chuàng)建的小部件(ContactListGUI),我將稍后進(jìn)行討論。注意,GUI 類包含 gui_eventXXX 方法來響應(yīng) GUI 事件。

ContactListGUI 創(chuàng)建了 GUI 小部件并響應(yīng)來自它們的事件。ContactListGUI 將 GUI 事件轉(zhuǎn)換為用戶希望對 ContactsService 執(zhí)行的操作。ContactListGUI 使用 ContactServiceDelegateContactService 調(diào)用方法。ContactServiceDelegateContactService 創(chuàng)建一個異步接口并使用它發(fā)出異步 Ajax 調(diào)用。ContactServiceDelegateContactListGUI 通知來自服務(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 通過以 service_eventXXX 開頭的方法向 ContactListGUI 發(fā)出有關(guān)服務(wù)事件的通知。如前所述,我編寫 ContactListGUI 的目標(biāo)之一就是避免嵌套的內(nèi)部類并創(chuàng)建一個相對扁平的 GUI 類(我可以非常方便地閱讀和理解的類)。ContactListGUI 只有 186 行,因此非常簡單。ContactListGUI 管理 9 個 GUI 小部件并與 ContactServiceDelegate 協(xié)作來管理一個 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 跟蹤表單中加載的當(dāng)前聯(lián)系人(currentContact)和清單中的聯(lián)系人列表(contacts)。圖 1 展示了小部件如何對應(yīng)于創(chuàng)建的 GUI:


圖 1. 聯(lián)系人管理 GUI 中活動的小部件
圖 1. 聯(lián)系人管理 GUI 中活動的小部件

清單 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 方法調(diào)用 buildForm 方法來創(chuàng)建新的表單網(wǎng)格并使用字段填充,以編輯聯(lián)系人數(shù)據(jù)。init 方法隨后調(diào)用 placeWidgets 方法,隨后將 contactGridformGridstatusaddNewButton 小部件放到 HTML 頁面中定義的插槽中,這個 HTML 頁面托管了清單 13 中定義的 GUI 應(yīng)用程序:


清單 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")對應(yīng)于 HTML 頁面中定義的元素的 ID(類似 id="contactListing")。這允許頁面設(shè)計師進(jìn)一步控制應(yīng)用程序小部件的布局。

對于基本的應(yīng)用程序構(gòu)建,讓我們了解幾個常見的使用場景。

展示一個有關(guān)頁面加載的鏈接

當(dāng)聯(lián)系人管理應(yīng)用程序的頁面首次加載時,它將調(diào)用 ContactListEntryPointonModuleLoad 方法。onModuleLoad 調(diào)用 ContactServiceDelegatelistContacts 方法,后者異步調(diào)用服務(wù)的 listContact 方法。當(dāng) listContact 方法返回時,ContactServiceDelegate 中定義的匿名內(nèi)部類將調(diào)用名為 service_eventListRetrievedFromService 的服務(wù)事件處理器方法,如清單 14 所示:


清單 14. 調(diào)用 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。它將重新調(diào)整行數(shù),以匹配服務(wù)器返回的聯(lián)系人列表的大小。隨后遍歷聯(lián)系人列表,將每個聯(lián)系人的姓名、電話、電子郵件數(shù)據(jù)放到每一行的前三個列中。它還為每個聯(lián)系人提供了 Edit 鏈接和一個 Remove 鏈接,使用戶能夠輕松地刪除和編輯聯(lián)系人。

用戶編輯現(xiàn)有的聯(lián)系人

當(dāng)用戶單擊聯(lián)系人列表中的 Edit 鏈接時,gui_eventContactGridClicked 將得到調(diào)用,如清單 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 中,允許用戶將更新信息發(fā)送回 ContactService。它還使 emailField 變?yōu)橹蛔x,這樣用戶就不能編輯電子郵件字段。接下來,gui_eventContactGridClicked 調(diào)用 loadForm(如 清單 15 所示),后者將 formGrid 設(shè)置為可見,設(shè)置正在被編輯的聯(lián)系人,然后將聯(lián)系人屬性復(fù)制到 emailFieldphoneFieldnameField 小部件中。

當(dāng)用戶單擊 updateButton 時,gui_eventUpdateButtonClicked 事件處理器方法被調(diào)用,如清單 16 所示。這個方法使 addNewButton 變?yōu)榭梢姡ㄟ@樣用戶就可以編輯新的聯(lián)系人)并隱藏了 formGrid。它隨后調(diào)用 copyFieldDateToContact,后者將來自 emailFieldphoneFieldnameField 小部件的文本復(fù)制回 currentContact 的屬性。隨后調(diào)用 ContactServiceDelegate updateContact 方法來將新更新的聯(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());
            }
            

這兩個場景應(yīng)當(dāng)使您了解到應(yīng)用程序是如何工作的,以及它如何依賴于 App Engine for Java 提供的基礎(chǔ)設(shè)施。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");
            }
            }
            





結(jié)束語

這個共包含三部分的 Google App Engine for Java 系列文章的第二部分向您介紹了如何使用 App Engine for Java 的 Eclipse 插件工具創(chuàng)建定制 GWT 應(yīng)用程序。在構(gòu)建簡單的聯(lián)系人管理應(yīng)用程序的過程中,您學(xué)會了以下內(nèi)容:

  • 構(gòu)建可以異步工作的遠(yuǎn)程服務(wù)
  • 組織 GUI 代碼以避免嵌套的內(nèi)部類聲明
  • 利用 GWT 為兩個關(guān)鍵用例實(shí)現(xiàn)功能

敬請期待本文的第 3 部分,您將對聯(lián)系人管理應(yīng)用程序進(jìn)行優(yōu)化,并通過 App Engine for Java 數(shù)據(jù)存儲功能添加對持久化 Contact 對象的支持。





周一 2009-09-22 19:54 發(fā)表評論
]]>
Google App Engine for Java: 第 1 部分:運(yùn)轉(zhuǎn)起來!http://www.aygfsteel.com/startfromheart/archive/2009/09/22/GAE.html周一周一Tue, 22 Sep 2009 11:51:00 GMThttp://www.aygfsteel.com/startfromheart/archive/2009/09/22/GAE.htmlhttp://www.aygfsteel.com/startfromheart/comments/296076.htmlhttp://www.aygfsteel.com/startfromheart/archive/2009/09/22/GAE.html#Feedback0http://www.aygfsteel.com/startfromheart/comments/commentRss/296076.htmlhttp://www.aygfsteel.com/startfromheart/services/trackbacks/296076.htmlGoogle App Engine 曾經(jīng)一度是 Python 開發(fā)人員 的專利。那是一段黑暗的歲月。Google Inc. 在 2009 年 4 月向 Java™ 開發(fā)人員開放了其云計算平臺。在這個共分三部分的系列文章中,Java 技術(shù)作家兼培訓(xùn)師 Rick Hightower 將帶領(lǐng)您了解這個可靠、健壯、有趣的平臺,并將它用于基于 Java 的開發(fā)。在本文中,您將了解到為什么 Google App Engine for Java 將成為您構(gòu)建高度可伸縮的殺手級應(yīng)用程序的開發(fā)平臺,然后開始使用 Google Plugin for Eclipse 構(gòu)建兩個示例應(yīng)用程序:一個基于 Google Web Toolkit (GWT),另一個基于 Java Servlet API。您將了解到 Google App Engine for Java 帶來的巨大改變,包括從頭構(gòu)建應(yīng)用程序以及將它部署到高達(dá) 5 百萬個視圖。(這僅僅是免費(fèi)版提供的功能)。

頭腦里出現(xiàn)的想法就好像是被蚊蟲叮了一樣:您需要抓癢癢,這樣做才感覺舒服一些。作為軟件開發(fā)人員,我們花了大量時間來為各種應(yīng)用程序捕捉想法。很有趣,不是嗎?困難的部分在于想出如何使一個軟件產(chǎn)品獲得成功。需要構(gòu)想出一些東西并隨后 實(shí)現(xiàn)它。考慮其他的問題(即沒有被抓過的癢處)只會讓人灰心。

許多應(yīng)用程序從未獲得進(jìn)展的一個原因就是無法滿足對基礎(chǔ)設(shè)施的需求。一個得到良好維護(hù)的基礎(chǔ)設(shè)施常常需要一個由系統(tǒng)管理員、DBA 和網(wǎng)絡(luò)工程師組成的團(tuán)隊(duì),到目前為止,這一直是企業(yè)獲得成功的主因。即使雇用第三方來托管您的應(yīng)用程序也絕不簡單:如果應(yīng)用程序大受歡迎并且突然之間獲得很高的點(diǎn)擊率,會發(fā)生什么?所謂的 Slashdot 效應(yīng) 可以幫助獲得一個好的想法,僅僅因?yàn)楹茈y預(yù)測加載峰值。


但是,眾所周知,事物是不斷變化的。Web 服務(wù)的基礎(chǔ)在不斷演變,如今它為我們帶來了許多新方式,通過云計算和強(qiáng)大的平臺即服務(wù)/PAAS 更輕松地構(gòu)建、部署和發(fā)布應(yīng)用程序。現(xiàn)在,在編寫下一個 Twitter 并將其部署到云平臺上時,它將不斷擴(kuò)展。哇,感覺很棒!

在這份共分三部分的系列文章中,您將了解到為什么云計算/PAAS 對于軟件開發(fā)來說是如此重要的一個演變,同時開始使用一種令人振奮的新平臺進(jìn)行 Java 開發(fā):Google App Engine for Java,目前可以使用它的預(yù)覽版。我將首先對 App Engine for Java 進(jìn)行概述,包括它所提供的應(yīng)用程序服務(wù)的類型。之后將直接查看第一個應(yīng)用程序示例(共兩個),它使用 App Engine for Java Google Plugin for Eclipse。第一個應(yīng)用程序示例將利用 App Engine for Java 對 Java Servlet API 的支持,第二個示例將利用對 GWT 的支持。在 第 2 部分 中,您將利用 App Engine for Java 對 servlets 和 GWT 提供的支持創(chuàng)建一個小型的聯(lián)系人管理應(yīng)用程序。在第 3 部分中,將使用自己構(gòu)建的應(yīng)用程序來利用 App Engine for Java 的基于 Java 的持久性支持,這種支持的基礎(chǔ)是 Java Data Objects (JDO) 和 Java Persistence API (JPA)。

好的,不說廢話了:讓我們開始吧!


關(guān)于 Google App Engine for Java

Google(同時也是一些搜索引擎的創(chuàng)建者)于 2008 年 4 月首度發(fā)布了 Google App Engine。令許多 Java 開發(fā)人員失望的是,初始版完全只服務(wù)于 Python 程序員 — 那些認(rèn)為應(yīng)該大塊使用空白的人!(我曾經(jīng)撰寫過一本有關(guān) Python 的書,因此我想我應(yīng)該知道)。Google 響應(yīng)了用戶的普遍要求,于 2009 年 4 月發(fā)布了 Google App Engine for Java。

Google App Engine for Java 為企業(yè) Java 開發(fā)提供了一個端到端解決方案:一個易于使用的基于瀏覽器的 Ajax GUI、Eclipse 工具支持以及后端的 Google App Engine。易于使用和工具支持是 Google App Engine for Java 優(yōu)于其他云計算解決方案的兩大優(yōu)勢。

App Engine for Java 中的應(yīng)用程序開發(fā)意味著使用 Google 的資源存儲和檢索 Java 對象。數(shù)據(jù)存儲的基礎(chǔ)是 BigTable,但是使用的是 JDO 和 JPA 接口,這些接口允許您編寫沒有直接綁定到 BigTable 的代碼。事實(shí)上,Google 為許多 API 提供了基于標(biāo)準(zhǔn)的支持,這樣就可以編寫沒有全部綁定到 App Engine for Java 的代碼。

App Engine for Java 依賴以下標(biāo)準(zhǔn) Java API:

  • java.net.URL,檢索服務(wù)(通過使用 HTTP 和 HTTPS 協(xié)議與其他主機(jī)通信)
  • JavaMail,發(fā)送郵件消息
  • 一個通向 Memcache 的 JCache (JSR 107) 接口,提供快速、臨時的分布式存儲,用于緩存查詢和計算

此外,App Engine for Java 為以下應(yīng)用程序服務(wù)提供了支持:

  • 用戶身份驗(yàn)證和授權(quán)
  • CRON
  • 數(shù)據(jù)導(dǎo)入/導(dǎo)出
  • 訪問防火墻數(shù)據(jù)

對于將數(shù)據(jù)從其他來源移動到您的 App Engine for Java 應(yīng)用程序,數(shù)據(jù)導(dǎo)入/導(dǎo)出十分重要。這也不需要綁定到 App Engine for Java。Google 的 CRON 支持基于對某個調(diào)度的內(nèi)部 URL 命中率,從而使它成為不需要綁定 App Engine for Java 的出色服務(wù)。用戶身份驗(yàn)證和授權(quán)機(jī)制 特定于 App Engine for Java 的,但是您可以編寫一個 ServletFilter、aspect 或 Spring Security 插件,以便最小化這種緊密耦合。

創(chuàng)建 App Engine for Java 應(yīng)用程序

如果您已經(jīng)閱讀了前面的內(nèi)容,那么已經(jīng)準(zhǔn)備好開始構(gòu)建第一個 App Engine for Java 應(yīng)用程序。首先需要 安裝 Google Plugin for Eclipse for App Engine for Java;安裝之后,您就有了好的起點(diǎn)。

打開 Eclipse IDE,您將看到在 Eclipse IDE 中,Printer 按鈕旁邊出現(xiàn)了三個新按鈕:一個 G 顯示在藍(lán)色小球中,另一個 G 顯示在紅色工具箱中,還有一個 App Engine for Java 迷你噴氣式飛機(jī),如圖 1 所示:


圖 1. Eclipse IDE 中的新按鈕
圖 1. Eclipse IDE 中的新按鈕

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

  • 藍(lán)色小球讓您能夠訪問 App Engine for Java 項(xiàng)目創(chuàng)建向?qū)А?
  • 紅色工具箱讓您編譯一個 GWT 項(xiàng)目。
  • 迷你噴氣式飛機(jī)圖標(biāo)讓您能夠部署一個 App Engine 項(xiàng)目。

您將使用項(xiàng)目創(chuàng)建向?qū)?chuàng)建兩個新項(xiàng)目:一個基于 servlets,另一個使用 GWT 構(gòu)建。將使用工具箱功能編譯 GWT 項(xiàng)目。當(dāng)您準(zhǔn)備好部署 App Engine 項(xiàng)目時,將啟動迷你噴氣式分機(jī),激活項(xiàng)目。

首先創(chuàng)建一個 App Engine for Java 項(xiàng)目。第一步,單擊藍(lán)色小球以訪問項(xiàng)目創(chuàng)建向?qū)АH缓笫褂妹Q為 gaej.example 的包創(chuàng)建名為 SimpleServletApp 的應(yīng)用程序,如圖 2 所示:


圖 2. 開始一個新項(xiàng)目
圖 2. 開始一個新項(xiàng)目

注意,對于這第一個簡單示例,沒有選擇 GWT 支持。完成了這一步后,項(xiàng)目創(chuàng)建向?qū)?chuàng)建一個簡單的基于 servlet 的應(yīng)用程序,提供了一個 Hello World 類型的 servlet。圖 3 展示了這個項(xiàng)目的屏幕截圖。


圖 3. SimpleServletApp 項(xiàng)目
圖 3. SimpleServletApp 項(xiàng)目

注意,這個新的基于 servlet 的項(xiàng)目自動包含了 JAR 文件:

  • datanucleus-*.jar:用于使用標(biāo)準(zhǔn) JDO 或低級 DataNucleus API訪問 App Engine for Java 數(shù)據(jù)庫
  • appengine-api-sdk.1.2.0.jar:用于使用非標(biāo)準(zhǔn) App Engine for Java 應(yīng)用程序服務(wù),比如 App Engine for Java Security
  • geronimo-*.jar:用于使用標(biāo)準(zhǔn) Java API,比如 Java Transaction Management API (JTA) 和 JPA
  • jdo2-api-2.3-SNAPSHOT.jar:用于使用 JDO API

在本系列 第 2 部分 中,您將了解到如何使用 App Engine for Java 的持久化 API 和 App Engine for Java 的一些應(yīng)用程序服務(wù)。

還需注意用于為 Google App Engine 配置運(yùn)行時容器的文件,名為 appengine.xml。在本例中,appengine.xml 用于配置 logging.properties 文件,以使用 App Engine for Java 完成登錄。

App Engine for Java servlet 應(yīng)用程序初探

在項(xiàng)目創(chuàng)建向?qū)е型瓿伤信渲煤螅珹pp Engine for Java 將向您顯示一個 Hello World 風(fēng)格的 servlet 應(yīng)用程序的骨架。查看代碼并看看如何使用 App Engine for Java Eclipse 工具運(yùn)行應(yīng)用程序。該應(yīng)用程序的主要入口點(diǎn)為 SimpleServletAppServlet,如清單 1 所示:


清單 1. SimpleServletAppServlet
            package gaej.example;
            import java.io.IOException;
            import javax.servlet.http.*;
            @SuppressWarnings("serial")
            public class SimpleServletAppServlet extends HttpServlet {
            public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
            resp.setContentType("text/plain");
            resp.getWriter().println("Hello, world");
            }
            }
            

servlet 在 web.xml 中的 URI /simpleservletapp 下完成映射,如清單 2 所示:


清單 2. web.xml
            <?xml version="1.0" encoding="utf-8"?>
            <!DOCTYPE web-app PUBLIC
            "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd">
            <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
            <servlet>
            <servlet-name>simpleservletapp</servlet-name>
            <servlet-class>gaej.example.SimpleServletAppServlet</servlet-class>
            </servlet>
            <servlet-mapping>
            <servlet-name>simpleservletapp</servlet-name>
            <url-pattern>/simpleservletapp</url-pattern>
            </servlet-mapping>
            <welcome-file-list>
            <welcome-file>index.html</welcome-file>
            </welcome-file-list>
            </web-app>
            

項(xiàng)目創(chuàng)建還提供了一個 index.html 文件,包含了連接新 servlet 的鏈接,如清單 3 所示:


清單 3. 項(xiàng)目創(chuàng)建向?qū)傻?index.html
            <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
            <!-- The HTML 4.01 Transitional DOCTYPE declaration-->
            <!-- above set at the top of the file will set     -->
            <!-- the browser's rendering engine into           -->
            <!-- "Quirks Mode". Replacing this declaration     -->
            <!-- with a "Standards Mode" doctype is supported, -->
            <!-- but may lead to some differences in layout.   -->
            <html>
            <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <!--                                           -->
            <!-- Any title is fine                         -->
            <!--                                           -->
            <title>Hello App Engine</title>
            </head>
            <!--                                           -->
            <!-- The body can have arbitrary html, or      -->
            <!-- you can leave the body empty if you want  -->
            <!-- to create a completely dynamic UI.        -->
            <!--                                           -->
            <body>
            <h1>Hello App Engine!</h1>
            <table>
            <tr>
            <td colspan="2" style="font-weight:bold;">Available Servlets:</td>
            </tr>
            <tr>
            <td><a href="simpleservletapp"/>SimpleServletAppServlet</td>
            </tr>
            </table>
            </body>
            </html>
            


您現(xiàn)在已經(jīng)使用了一些 Java API 構(gòu)建了一個簡單的 servlet 應(yīng)用程序。并且重點(diǎn)在于:App Engine for Java 使用標(biāo)準(zhǔn) Java API 封裝 App Engine 功能,使 App Engine 能夠支持面向 Java 平臺的豐富框架。

部署應(yīng)用程序

要使用 App Engine for Java Eclipse 工具運(yùn)行我們的基于 servlet 的應(yīng)用程序,首先右鍵單擊該項(xiàng)目并選擇 Run As 菜單,然后選擇旁邊有一個藍(lán)色小球的 “Web Application”,如圖 4 所示:


圖 4. 運(yùn)行 App Engine for Java 部署服務(wù)器
圖 4. 運(yùn)行 App Engine for Java 部署服務(wù)器

現(xiàn)在您應(yīng)當(dāng)能夠在瀏覽器中導(dǎo)航到 http://localhost:8080/simpleservletapp 并看到應(yīng)用程序顯示出 Hello World 消息。


創(chuàng)建 App Engine for Java/GWT 應(yīng)用程序

您已經(jīng)了解了一個簡單 App Engine for Java servlet 應(yīng)用程序的工作原理,那么接下來讓我們探討一下面向 GWT 應(yīng)用程序的 App Engine for Java Eclipse 工具。首先單擊 Eclipse IDE 工具欄中的藍(lán)色小球來激活 Google 項(xiàng)目創(chuàng)建向?qū)А_@一次,選擇 GWT 支持,如圖 5 所示:


圖 5. 使用 App Engine for Java 項(xiàng)目創(chuàng)建向?qū)?chuàng)建一個簡單的 GWT 應(yīng)用程序
圖 5. 使用 App Engine for Java 項(xiàng)目創(chuàng)建向?qū)?chuàng)建一個簡單的 GWT 應(yīng)用程序

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



圖 6. 為 GWT 應(yīng)用程序提供的代碼工件
圖 6. 為 GWT 應(yīng)用程序提供的代碼工件

為 GWT 應(yīng)用程序提供的一個額外 JAR 對于基于 servlet 的應(yīng)用程序并不是必須的,這個 JAR 文件就是 gwt-servlet.jar。

其他工件包括:

  • src/gaej/example:SimpleGWTApp.gwt.xml:GWT 模塊描述符
  • src/gaej.example.server:GreetingServiceImpl.java:問候服務(wù)的實(shí)現(xiàn)
  • src/gaej.example.client:GreetingService.java:問候服務(wù)的同步 API
  • src/gaej.example.client:GreetingServiceAsync.java:問候服務(wù)的異步 API
  • src/gaej.example.client:SimpleGWTApp.java:構(gòu)建啟動 GUI 的主要入口點(diǎn)
  • war/WEB-INF:web.xml:配置 GreetingServiceImpl 的部署描述符
  • war:SimpleGWTApp.html:顯示 GWT GUI 的 HTML 頁面
  • war:SimpleGWTApp.css:GWT GUI 的樣式表

在深入研究應(yīng)用程序的架構(gòu)和源代碼之前,看看運(yùn)行它的時候會發(fā)生什么狀況。要運(yùn)行應(yīng)用程序,單擊工具欄中的紅色工具箱,然后單擊 Compile 按鈕。現(xiàn)在右鍵單擊項(xiàng)目并像剛才一樣選擇 Run As—> Web Application 菜單項(xiàng)。這一次,由于您處理的是一個 GWT 應(yīng)用程序,將顯示一個 GWT Hosted Mode Console 和瀏覽器。繼續(xù)并使用 Web 應(yīng)用程序輸入您的名字并觀察響應(yīng)。我收到如圖 7 所示的響應(yīng):


圖 7. 運(yùn)行樣例 GWT 應(yīng)用程序
圖 7. 運(yùn)行樣例 GWT 應(yīng)用程序

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

詳細(xì)了解 GWT 應(yīng)用程序

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

創(chuàng)建 GUI 的主要入口點(diǎn)是 gaej.example.client.SimpleGWTApp,如 清單 8 所示。該類創(chuàng)建 GWT GUI 元素并將它們與 SimpleGWTApp.html 中的 HTML DOM 元素關(guān)聯(lián)起來(參見 清單 10)。SimpleGWTApp.html 定義了兩個 DOM 元素,分別命名為 nameFieldContainersendButtonContainer(表中的兩列)。SimpleGWTApp 類使用 RootPanel.get("nameFieldContainer") 訪問與這些 DOM 元素關(guān)聯(lián)的面板并使用 GUI 元素替換它們。SimpleGWTApp 類隨后定義一個文本框和按鈕,可以使用它們輸入某人的名字并發(fā)送一句問候語(參見 清單 10)。

GWT 知道 SimpleGWTApp 類是應(yīng)用程序的主要入口點(diǎn),因?yàn)?SimpleGWTApp.gwt.xml 使用入口點(diǎn)元素進(jìn)行了指定。

SimpleGWTApp 封裝了名為 sendButton 的按鈕,這樣當(dāng)它被單擊時,SimpleGWTApp 將對 GreetingService 調(diào)用 greetServer 方法。GreetingService 接口在 src/gaej.example.client.GreetingService.java 中被定義(參見 清單 6)。

由于 Ajax 天生就具有異步性,因此 GWT 定義了一個異步接口來訪問遠(yuǎn)程服務(wù)。SimpleGWTApp 使用 src/gaej.example.client.GreetingServiceAsync.java 中定義的異步接口(參見 清單 7)。GreetingServiceImpl(src/gaej.example.server.GreetingServiceImpl.java)實(shí)現(xiàn)了 GreetingService 中定義的 greetServer方法(參見 清單 5)。GreetingServiceImpl.greetServer 方法返回一條問候消息 String,SimpleGWTApp 用它在所創(chuàng)建的對話框中顯示問候消息。

GWT 模塊描述符聲明了 GUI 應(yīng)用程序的主要入口點(diǎn),即 gaej.example.client.SimpleGWTApp,如清單 4 所示:


清單 4. GWT 模塊描述符(src/gaej/example/SimpleGWTApp.gwt.xml)
            <?xml version="1.0" encoding="UTF-8"?>
            <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN"
            "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/
            distro-source/core/src/gwt-module.dtd">
            <module rename-to='simplegwtapp'>
            <!-- Inherit the core Web Toolkit stuff.                        -->
            <inherits name='com.google.gwt.user.User'/>
            <!-- Inherit the default GWT style sheet.  You can change       -->
            <!-- the theme of your GWT application by uncommenting          -->
            <!-- any one of the following lines.                            -->
            <inherits name='com.google.gwt.user.theme.standard.Standard'/>
            <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
            <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->
            <!-- Other module inherits                                      -->
            <!-- Specify the app entry point class.                         -->
            <entry-point class='gaej.example.client.SimpleGWTApp'/>
            </module>
            

GreetingServiceImpl 是問候服務(wù)應(yīng)用程序的實(shí)際實(shí)現(xiàn),如清單 5 所示。它運(yùn)行在服務(wù)器端,并且客戶機(jī)代碼通過一個遠(yuǎn)程過程調(diào)用來調(diào)用它。


清單 5. greeting-service 應(yīng)用程序的實(shí)現(xiàn)(src/gaej.example.server.GreetingServiceImpl.java)
            package gaej.example.server;
            import gaej.example.client.GreetingService;
            import com.google.gwt.user.server.rpc.RemoteServiceServlet;
            /**
            * The server side implementation of the RPC service.
            */
            @SuppressWarnings("serial")
            public class GreetingServiceImpl extends RemoteServiceServlet implements
            GreetingService {
            public String greetServer(String input) {
            String serverInfo = getServletContext().getServerInfo();
            String userAgent = getThreadLocalRequest().getHeader("User-Agent");
            return "Hello, " + input + "!<br><br>I am running " + serverInfo
            + ".<br><br>It looks like you are using:<br>" + userAgent;
            }
            }
            

如清單 6 所示,GreetingService 是客戶機(jī)代碼使用的遠(yuǎn)程過程調(diào)用的接口:


清單 6. 同步 API (src/gaej.example.client.GreetingService.java)
            package gaej.example.client;
            import com.google.gwt.user.client.rpc.RemoteService;
            import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
            /**
            * The client side stub for the RPC service.
            */
            @RemoteServiceRelativePath("greet")
            public interface GreetingService extends RemoteService {
            String greetServer(String name);
            }
            

GreetingServiceAsync 是客戶機(jī)代碼將使用的實(shí)際接口,如清單 7 所示。每個方法都提供了一個回調(diào)對象,這樣就可以在完成遠(yuǎn)程過程調(diào)用后收到異步通知。至于內(nèi)部原理,GWT 使用了 Ajax。在客戶機(jī)上使用 Ajax 時,最好不要阻塞客戶機(jī),這樣就不會阻塞異步調(diào)用。阻塞會違背使用 Ajax 的初衷:


清單 7. 異步 API(src/gaej.example.client.GreetingServiceAsync.java)
            package gaej.example.client;
            import com.google.gwt.user.client.rpc.AsyncCallback;
            /**
            * The async counterpart of <code>GreetingService</code>.
            */
            public interface GreetingServiceAsync {
            void greetServer(String input, AsyncCallback<String> callback);
            }
            

SimpleGWTApp 中包含了最多的操作。它注冊了 GUI 事件,然后將 Ajax 請求發(fā)送到 GreetingService


清單 8. 應(yīng)用程序的主入口點(diǎn)還構(gòu)建了啟動 GUI(src/gaej.example.client.SimpleGWTApp.java)
            package gaej.example.client;
            import com.google.gwt.core.client.EntryPoint;
            import com.google.gwt.core.client.GWT;
            import com.google.gwt.event.dom.client.ClickEvent;
            import com.google.gwt.event.dom.client.ClickHandler;
            import com.google.gwt.event.dom.client.KeyCodes;
            import com.google.gwt.event.dom.client.KeyUpEvent;
            import com.google.gwt.event.dom.client.KeyUpHandler;
            import com.google.gwt.user.client.rpc.AsyncCallback;
            import com.google.gwt.user.client.ui.Button;
            import com.google.gwt.user.client.ui.DialogBox;
            import com.google.gwt.user.client.ui.HTML;
            import com.google.gwt.user.client.ui.Label;
            import com.google.gwt.user.client.ui.RootPanel;
            import com.google.gwt.user.client.ui.TextBox;
            import com.google.gwt.user.client.ui.VerticalPanel;
            /**
            * Entry point classes define <code>onModuleLoad()</code>.
            */
            public class SimpleGWTApp implements EntryPoint {
            /**
            * The message displayed to the user when the server cannot be reached or
            * returns an error.
            */
            private static final String SERVER_ERROR = "An error occurred while "
            + "attempting to contact the server. Please check your network "
            + "connection and try again.";
            /**
            * Create a remote service proxy to talk to the server-side Greeting service.
            */
            private final GreetingServiceAsync greetingService = GWT
            .create(GreetingService.class);
            /**
            * This is the entry point method.
            */
            public void onModuleLoad() {
            final Button sendButton = new Button("Send");
            final TextBox nameField = new TextBox();
            nameField.setText("GWT User");
            // You can add style names to widgets
            sendButton.addStyleName("sendButton");
            // Add the nameField and sendButton to the RootPanel
            // Use RootPanel.get() to get the entire body element
            RootPanel.get("nameFieldContainer").add(nameField);
            RootPanel.get("sendButtonContainer").add(sendButton);
            // Focus the cursor on the name field when the app loads
            nameField.setFocus(true);
            nameField.selectAll();
            // Create the popup dialog box
            final DialogBox dialogBox = new DialogBox();
            dialogBox.setText("Remote Procedure Call");
            dialogBox.setAnimationEnabled(true);
            final Button closeButton = new Button("Close");
            // You can set the id of a widget by accessing its Element
            closeButton.getElement().setId("closeButton");
            final Label textToServerLabel = new Label();
            final HTML serverResponseLabel = new HTML();
            VerticalPanel dialogVPanel = new VerticalPanel();
            dialogVPanel.addStyleName("dialogVPanel");
            dialogVPanel.add(new HTML("<b>Sending name to the server:</b>"));
            dialogVPanel.add(textToServerLabel);
            dialogVPanel.add(new HTML("<br><b>Server replies:</b>"));
            dialogVPanel.add(serverResponseLabel);
            dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
            dialogVPanel.add(closeButton);
            dialogBox.setWidget(dialogVPanel);
            // Add a handler to close the DialogBox
            closeButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
            dialogBox.hide();
            sendButton.setEnabled(true);
            sendButton.setFocus(true);
            }
            });
            // Create a handler for the sendButton and nameField
            class MyHandler implements ClickHandler, KeyUpHandler {
            /**
            * Fired when the user clicks on the sendButton.
            */
            public void onClick(ClickEvent event) {
            sendNameToServer();
            }
            /**
            * Fired when the user types in the nameField.
            */
            public void onKeyUp(KeyUpEvent event) {
            if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
            sendNameToServer();
            }
            }
            /**
            * Send the name from the nameField to the server and wait for a response.
            */
            private void sendNameToServer() {
            sendButton.setEnabled(false);
            String textToServer = nameField.getText();
            textToServerLabel.setText(textToServer);
            serverResponseLabel.setText("");
            greetingService.greetServer(textToServer,
            new AsyncCallback<String>() {
            public void onFailure(Throwable caught) {
            // Show the RPC error message to the user
            dialogBox
            .setText("Remote Procedure Call - Failure");
            serverResponseLabel
            .addStyleName("serverResponseLabelError");
            serverResponseLabel.setHTML(SERVER_ERROR);
            dialogBox.center();
            closeButton.setFocus(true);
            }
            public void onSuccess(String result) {
            dialogBox.setText("Remote Procedure Call");
            serverResponseLabel
            .removeStyleName("serverResponseLabelError");
            serverResponseLabel.setHTML(result);
            dialogBox.center();
            closeButton.setFocus(true);
            }
            });
            }
            }
            // Add a handler to send the name to the server
            MyHandler handler = new MyHandler();
            sendButton.addClickHandler(handler);
            nameField.addKeyUpHandler(handler);
            }
            }
            

Web 部署描述符(web.xml,如清單 9 所示)將 GreetingService 映射為一個基于 servlet 的 Web 資源。它在 /simplegwtapp/greet 名稱下映射 GreetingService servlet,這樣 SimpleGWTApp 就可以加載它并對它發(fā)起調(diào)用。Web 部署描述符還可以將 SimpleGWTApp.html 指定為應(yīng)用程序的歡迎頁面,這樣就會始終加載它。


清單 9. 配置 GreetingServiceImpl (war/WEB-INF/web.xml) 的部署描述符
            <?xml version="1.0" encoding="UTF-8"?>
            <!DOCTYPE web-app
            PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd">
            <web-app>
            <!-- Default page to serve -->
            <welcome-file-list>
            <welcome-file>SimpleGWTApp.html</welcome-file>
            </welcome-file-list>
            <!-- Servlets -->
            <servlet>
            <servlet-name>greetServlet</servlet-name>
            <servlet-class>gaej.example.server.GreetingServiceImpl</servlet-class>
            </servlet>
            <servlet-mapping>
            <servlet-name>greetServlet</servlet-name>
            <url-pattern>/simplegwtapp/greet</url-pattern>
            </servlet-mapping>
            </web-app>
            

HTML 前端為 SimpleGWTApp.html,如清單 10 所示。這是加載 simplegwtapp.js 和 simplegwtapp.nocache.js 的頁面,是由 GWT 從 Java 代碼中生成的 JavaScript 代碼。如前所述,這段代碼位于 gaej.example.client 包的 src 目錄中(來自清單 678)。


清單 10. 顯示 GWT GUI (war/SimpleGWTApp.html) 的 HTML 頁面
            <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
            <!-- The HTML 4.01 Transitional DOCTYPE declaration-->
            <!-- above set at the top of the file will set     -->
            <!-- the browser's rendering engine into           -->
            <!-- "Quirks Mode". Replacing this declaration     -->
            <!-- with a "Standards Mode" doctype is supported, -->
            <!-- but may lead to some differences in layout.   -->
            <html>
            <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <!--                                                               -->
            <!-- Consider inlining CSS to reduce the number of requested files -->
            <!--                                                               -->
            <link type="text/css" rel="stylesheet" href="SimpleGWTApp.css">
            <!--                                           -->
            <!-- Any title is fine                         -->
            <!--                                           -->
            <title>Web Application Starter Project</title>
            <!--                                           -->
            <!-- This script loads your compiled module.   -->
            <!-- If you add any GWT meta tags, they must   -->
            <!-- be added before this line.                -->
            <!--                                           -->
            <script type="text/javascript" language="javascript"
            src="simplegwtapp/simplegwtapp.nocache.js"></script>
            </head>
            <!--                                           -->
            <!-- The body can have arbitrary html, or      -->
            <!-- you can leave the body empty if you want  -->
            <!-- to create a completely dynamic UI.        -->
            <!--                                           -->
            <body>
            <!-- OPTIONAL: include this if you want history support -->
            <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1'
            style="position:absolute;width:0;height:0;border:0"></iframe>
            <h1>Web Application Starter Project</h1>
            <table align="center">
            <tr>
            <td colspan="2" style="font-weight:bold;">Please enter your name:</td>
            </tr>
            <tr>
            <td id="nameFieldContainer"></td>
            <td id="sendButtonContainer"></td>
            </tr>
            </table>
            </body>
            </html>
            

使用 GWT,您可以通過 CSS 控制應(yīng)用程序的觀感,如清單 11 所示:


清單 11. GWT GUI (war/SimpleGWTApp.css) 的樣式表
            /** Add css rules here for your application. */
            /** Example rules used by the template application (remove for your app) */
            h1 {
            font-size: 2em;
            font-weight: bold;
            color: #777777;
            margin: 40px 0px 70px;
            text-align: center;
            }
            .sendButton {
            display: block;
            font-size: 16pt;
            }
            /** Most GWT widgets already have a style name defined */
            .gwt-DialogBox {
            width: 400px;
            }
            .dialogVPanel {
            margin: 5px;
            }
            .serverResponseLabelError {
            color: red;
            }
            /** Set ids using widget.getElement().setId("idOfElement") */
            #closeButton {
            margin: 15px 6px 6px;
            }
            

部署到 Google App Engine

創(chuàng)建了下一代殺手級應(yīng)用程序后(因?yàn)槲覀兇_實(shí)需要一個用戶友好的問候應(yīng)用程序),您需要部署它。使用 Google App Engine 的重點(diǎn)就是可以將應(yīng)用程序部署到 Google 提供的可靠基礎(chǔ)設(shè)施中,使它更易于擴(kuò)展。Google App Engine 的設(shè)計初衷就是為構(gòu)建可伸縮應(yīng)用程序提供一個平臺,可伸縮應(yīng)用程序就是指 “能夠在不觸動基礎(chǔ)設(shè)施的情況下將用戶輕松增長到數(shù)百萬”(正如 App Engine 主頁中描述的那樣)。為使用這種基礎(chǔ)設(shè)施,您需要一個 Google App Engine for Java 帳戶

就像許多其他產(chǎn)品一樣,第一次體驗(yàn)總是免費(fèi)的。App Engine for Java 的免費(fèi)版提供了一個已部署的應(yīng)用程序,提供了足夠的 CPU、帶寬和存儲來為 500 萬個頁面訪問次數(shù)提供服務(wù)。超過使用次數(shù)開始收費(fèi)(同樣需要注意,在撰寫本文時可以獲得 App Engine for Java 平臺的預(yù)覽版)。

獲得帳戶之后,您應(yīng)該可以在 App Engine for Java 站點(diǎn) 看到一個空的應(yīng)用程序列表。單擊 Create New Application 按鈕,應(yīng)當(dāng)出現(xiàn)一個如圖 8 所示的表單。輸入一個獨(dú)特的應(yīng)用程序名稱和描述,之后您將看到一條顯示有應(yīng)用程序標(biāo)識符的確認(rèn)消息。

該標(biāo)識符也位于應(yīng)用程序的 app.yaml 文件中。注意,該標(biāo)識符不可修改。如果對您的應(yīng)用程序使用 Google 身份驗(yàn)證,那么在訪問應(yīng)用程序時,“GAEj Article For Rick Part 1” 將顯示在 Sign In 頁面中。您將使用 gaejarticleforrick 和 App Engine for Java Eclipse 插件來將應(yīng)用程序部署到 Google App Engine。


圖 8. 創(chuàng)建一個新的 App Engine for Java 應(yīng)用程序
圖 8. 創(chuàng)建一個新的 App Engine for Java 應(yīng)用程序

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


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

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


圖 10. 部署項(xiàng)目
圖 10. 部署項(xiàng)目

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


圖 11. Google App Engine 的項(xiàng)目設(shè)置
圖 11. Google App Engine 的項(xiàng)目設(shè)置

部署完應(yīng)用程序后,該應(yīng)用程序可以通過 http://<application id>.appspot.com/ 訪問。您還會看到應(yīng)用程序出現(xiàn)在 http://gaejarticleforrick.appspot.com/ 中。

結(jié)束語

Google App Engine for Java 系列的第一部分到此結(jié)束。至此,您已經(jīng)大致了解了 App Engine for Java 是什么,并通過使用 App Engine for Java Google Plugin for Eclipse 邁出了第一步。您創(chuàng)建了兩個啟動應(yīng)用程序(一個基于 servlet,另一個基于 GWT)并隨后將 GWT 應(yīng)用程序部署到 Google App Engine 平臺。

本文的示例到目前為止演示了可以更輕松地創(chuàng)建和部署基于 Java 的應(yīng)用程序的工具和功能 — 這些應(yīng)用程序有可能擴(kuò)展到 YouTube 或 Facebook 那樣的規(guī)模。在 第 2 部分 中,您將繼續(xù)為 Java 開發(fā)人員介紹使用 App Engine for Java 的機(jī)會。從本文演示的示例應(yīng)用程序出發(fā),您將構(gòu)建一個定制的聯(lián)系人管理應(yīng)用程序。該應(yīng)用程序?qū)⒊蔀榈?3 部分的中心內(nèi)容,第 3 部分將進(jìn)一步了解 App Engine for Java 的數(shù)據(jù)存儲及其 GUI 前端。




周一 2009-09-22 19:51 發(fā)表評論
]]>
主站蜘蛛池模板: 浦城县| 固镇县| 崇义县| 吉木萨尔县| 密云县| 周宁县| 防城港市| 汾西县| 治县。| 山西省| 台江县| 镇雄县| 穆棱市| 锡林浩特市| 龙川县| 葵青区| 塔河县| 航空| 修文县| 榆中县| 三江| 修水县| 阜平县| 工布江达县| 大名县| 渝北区| 景东| 南阳市| 台中市| 平果县| 泸水县| 崇仁县| 安图县| 扬州市| 炎陵县| 梨树县| 张北县| 墨竹工卡县| 龙泉市| 翁牛特旗| 山东省|