Google App Engine for Java: 第 2 部分:构建杀手级应用程序--使用 App Engine 构建联系人管理应用程序

诸如 Google App Engine for Java 这样的云平台的关键在于能够设计、构建和部署专业级的应用程序 —— 可以非常容易地进行伸缩。在这个包含三部分的 Google App Engine for Java 系列文章第二篇中,Rick Hightower 将通过一个分步指南,使用 Google App Engine for Java 来编写和部署一个简单的联系人管理应用程序,从而超越第1部分中提供的现成示例。

在介绍使用 App Engine for Java 构建可伸缩 Java 应用程序的第1部分中,您了解了 Google 云计算平台(即 PAAS)为 Java 开发人员提供的 Eclipse 工具和基础设施。该文章中的示例都是预先准备的,这样您可以将精力集中到 App Engine for Java 与 Eclipse 的集成中,并快速构建和部署不同类型的应用程序 — 即使用 Google Web Toolkit (GWT) 构建的应用程序和基于 servlet 的应用程序。本文将在此基础上展开,并且在本系列第 3 部分中提供了更加高级的编程实践。

您将构建的联系人管理应用程序允许用户存储基本的联系人信息,比如名称、电子邮件地址和电话号码。要创建这个应用程序,将需要使用 Eclipse GWT 项目创建向导。

从 CRUD 到联系人应用程序

正如目前您已经了解到的一样,在 App Engine for Java 中构建新应用程序的第一步就是在 Eclipse 启动项目创建向导。之后,您可以打开 GWT 项目启动向导来创建 GWT 项目(本文第1部分给出了在 App Engine for Java 中创建 GWT 项目的详细说明)。

对于这个练习,您将启动一个简单的 CRUD 应用程序,并稍后添加实际的存储。我们将使用一个具有模拟实现的数据访问对象(DAO),如清单 1 所示:

清单 1. ContactDAO 接口

package gaej.example.contact.server;import java.util.List;import gaej.example.contact.client.Contact;public interface ContactDAO { void addContact(Contact contact); void removeContact(Contact contact); void updateContact(Contact contact); List<Contact> listContacts();}

ContactDAO 添加了各种方法,可以添加联系人、删除联系人、更新联系人,并返回一个所有联系人的列表。它是一个非常基本的 CRUD 接口,可以管理联系人。Contact 类是您的域对象,如清单 2 所示:

清单 2. 联系人域对象(gaej.example.contact.client.Contact)

package gaej.example.contact.client;import java.io.Serializable;public class Contact implements Serializable { private static final long serialVersionUID = 1L; private String name; private String email; private String phone; public Contact() { } public Contact(String name, String email, String phone) { super(); this.name = name; this.email = email; this.phone = phone; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }

对于这个应用程序的第一个版本,您将使用一个模拟对象将联系人存储在一个内存集合中,如清单 3 所示:

清单 3. Mock DAO 类

package gaej.example.contact.server;import gaej.example.contact.client.Contact;import java.util.ArrayList;import java.util.Collections;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;public class ContactDAOMock implements ContactDAO { Map<String, Contact> map = new LinkedHashMap<String, Contact>(); { map.put("rhightower@mammatus.com", new Contact("Rick Hightower", "rhightower@mammatus.com", "520-555-1212")); map.put("scott@mammatus.com", new Contact("Scott Fauerbach", "scott@mammatus.com", "520-555-1213")); map.put("bob@mammatus.com", new Contact("Bob Dean", "bob@mammatus.com", "520-555-1214")); } public void addContact(Contact contact) { String email = contact.getEmail(); map.put(email, contact); } public List<Contact> listContacts() { return Collections.unmodifiableList(new ArrayList<Contact>(map.values())); } public void removeContact(Contact contact) { map.remove(contact.getEmail()); } public void updateContact(Contact contact) { map.put(contact.getEmail(), contact); }}

创建远程服务

您现在的目标是创建一个允许您使用 DAO 的 GWT GUI。将使用 ContactDAO 接口上的所有方法。第一步是将 DAP 类(未来版本将直接与服务器端的数据存储通信,因此必须位于服务器中)的功能封装到一个服务中,如清单 4 所示:

清单 4. ContactServiceImpl

package gaej.example.contact.server;import java.util.ArrayList;import java.util.List;import gaej.example.contact.client.Contact;import gaej.example.contact.client.ContactService;import com.google.gwt.user.server.rpc.RemoteServiceServlet;public class ContactServiceImpl extends RemoteServiceServlet implements ContactService { private static final long serialVersionUID = 1L; private ContactDAO contactDAO = new ContactDAOMock(); public void addContact(Contact contact) { contactDAO.addContact(contact); } public List<Contact> listContacts() { List<Contact> listContacts = contactDAO.listContacts(); return new ArrayList<Contact> (listContacts); } public void removeContact(Contact contact) { contactDAO.removeContact(contact); } public void updateContact(Contact contact) { contactDAO.updateContact(contact); } }

注意,ContactServiceImpl 实现了 RemoteServiceServlet,随后定义方法来添加联系人、列出联系人、删除联系人,以及更新联系人。它将所有这些操作委托给 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 中活动的小部件

清单 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 co

时间: 2024-09-14 20:24:27

Google App Engine for Java: 第 2 部分:构建杀手级应用程序--使用 App Engine 构建联系人管理应用程序的相关文章

Google App Engine for Java,第 3 部分: 持久性和关系--基于 Java 的持久性和 Google App Engine 数据存储

在企业环境中,数据持久性是交付可伸缩应用程序的基础.Rick Hightower 在他撰写的有关 Google App Engine for Java 的系列文章的最后一篇中,介绍了 App Engine 当前基于 Java 的持久性框架.让我们学习一些基础知识,了解为什么当前预览版中的 Java 持久性还未到发布的最佳时间,同时获得一个良好的演示,看看您如何在 App Engine for Java 应用程序中保存数据.注意,您将需要启动并运行来自第2部分的联系人管理应用程序,在此过程中学习如

Google 打败 Oracle 赢得 Java 版权案官司

Google 和 Oracle 关于 Java 侵权案的陪审团裁定,谷歌使用 Oracle 创建的 Java 软件来创建 Android 系统是合理使用,不构成版权侵犯. 此判决标志着 Oracle 在 2012 年起诉 Google 关于侵犯 Java 版权的案子正式结束,Google 因此避免了 90 亿美元的赔偿.此案同时也给在编写不同平台上应用程序但没有许可证的程序员们最大的安慰. 甲骨文认为,谷歌需要一个许可才能在 Android 操作系统上使用 Java 编程语言,但是陪审团周四在旧

如何将MVC开发的程序转换为APP

问题描述 绝对是可行的,我要别人用java程序帮我转换过,过程不详,不知道怎么把一个网页程序c#编码的,转换为一个app 解决方案 解决方案二:APP外面包了个壳而已,实际还是网页--解决方案三:对我就是楼上这个意思,用C#可以完成以上的操作吗?如何操作呢,求解谢谢解决方案四:实际上连开发都不用,微信就是这样炒起来的.解决方案五:如果用威信做对比的那,那么要明白做对比的是微信本身,还是微信浏览器下展示一个简单的html?如果你要做一个值钱的微信平台,你不能用一个免费的网页页面的思维来研发它.因为

使用Acegi保护Java应用程序,第4部分: 保护JSF应用程序

针对 JavaServer Faces 应用程序的可配置安全性 本 系列 的前 3 部分讨论了如何使用 Acegi Security System 保护 Java 企业应用程序: 第 1 部分 解释了如何使用 Acegi 的内置过滤器实现一个简单的基于 URL 的安全系统. 第 2 部分 展示了如何编写访问控制策略.将其存储在 LDAP 目录服务器中 ,以及配置 Acegi 与 LDAP 服务器交互,从而实现访问控制策略. 第 3 部分 展示了如何在企业应用程序中使用 Acegi 保护对 Jav

服务器-做一款全国范围的App 可在APP内部切换城市 但所以城市的用户可以跨省 访问APP

问题描述 做一款全国范围的App 可在APP内部切换城市 但所以城市的用户可以跨省 访问APP 可在APP内部切换城市 但所以城市的用户可以跨省 访问APP的其他城市 如何做到各个服务器的用户数据共享 后台服务器该如何做架构 求思路! 解决方案 可以通过gps或者ip判断用户在哪个城市. 至于架构,不必要每个城市做一个数据库或者搞一个服务器.只要你的记录包含一个城市的字段,查询符合条件的数据就可以. 解决方案二: 统一架构按照需求部署服务器,而不是每个城市一台服务器的好处就是这样可以提高服务器效

mysql-《疯狂java实战演义》里面的第十三章MySQL管理器程序有没有做出往数据库导入Excel表的?

问题描述 <疯狂java实战演义>里面的第十三章MySQL管理器程序有没有做出往数据库导入Excel表的? 本人想在MySQL管理器程序源代码里面加上导入表的代码,但是总是失败,想向个位请教一下,有人这么做过吗,能否给一下代码,谢谢! 解决方案 http://download.csdn.net/detail/qianfu123/3589697 源代码,自己下

HTML应用程序(HTML App)

HTML应用程序(HTML App) 一个简单的 html app例子: <HTML> <HEAD> <TITLE>hta示例</TITLE> <HTA:APPLICATION ID="oHTA" APPLICATIONNAME="myApp" BORDER="thin" BORDERSTYLE="normal" CAPTION="yes" ICON=&

用java 编写一个手机联系人管理的GUI

问题描述 用java 编写一个手机联系人管理的GUI 使用hashtabel 保存数据 解决方案 hashtable效率不是很好,建议使用ConcurrentHashMap 解决方案二: 使用WindowBuilder,可视化编程,控件都是直接拖动就能布局

java 编程-关于java对象调用的一个菜鸟级问题

问题描述 关于java对象调用的一个菜鸟级问题 本人新手,币也不够,帮帮忙吧大家.学java不久,遇到这么一个问题:怎么在一个类中调用另一个类的对象获取该对象数据呢?(两个类不在同一个包里)如包a中有class A{} 和两个对象A a1=new A():A a2=new A();包b中有class B{}.我要在b包中访问a1和a2应该怎么访问呀?要求b包不再生成新的A类对象,且A类不能是单例. 解决方案 a中添加class Global{ static public A a1; static