 |  |
|
난이도 : 초급 Richard Hightower, CTO, Mammatus Inc.
원문 게재일 : 2009 년 8 월 11 일 번역 게재일 : 2009 년 9 월 15 일 Google App Engine for Java™와 같은 클라우드 플랫폼의 가장 중요한 특징은 매우 많은
비용과 노력을 들이지 않고도 전문가 수준의 킬러 애플리케이션을 상상하고, 개발하고, 전개할
수 있다는 것입니다. Google App Engine for Java를 소개하는 세 편의 기사 중 이 두 번째 기사에서는
기존 예제를 설명했던 Part 1과는 달리 Google App Engine for Java를 사용하여
간단한 연락처 관리 애플리케이션을 작성 및 전개하는 과정을 단계별로 안내합니다.
App Engine for Java를 이용하여 확장 가능한 Java 애플리케이션을 개발하는 방법을 설명하는
이 시리즈의 Part 1에서는
Eclipse 도구와 Java 개발자를 위한 Google의 클라우드 컴퓨팅 플랫폼(또는 PAAS) 인프라에 대해
살펴보았다. Part 1에서는 기존 예제를 사용했기 때문에 App Engine for Java과
Eclipse의 통합에 집중할 수 있었고 다양한 유형의 애플리케이션 즉, GWT(Google Web Toolkit)를
사용하여 작성한 애플리케이션과 서블릿 기반 애플리케이션의 작성 및 전개 과정을 빠르게 익힐
수 있었다. 이 기사에서는 이 시리즈의 Part 3에서 설명할 고급 프로그래밍 주제를 위한 토대를
마련하고 준비한다.
앞으로 작성할 연락처 관리 애플리케이션에서는 사용자가 이름, 이메일 주소, 전화 번호 등의
기본적인 연락처 정보를 저장할 수 있다. 이 애플리케이션을 작성하기 위해 Eclipse GWT 프로젝트
작성 마법사를 사용한다.
CRUD에서 연락처로
지금까지 살펴본 대로 App Engine for Java에서 새 애플리케이션을 작성하기 위해 먼저 Eclipse의
프로젝트 작성 마법사를 실행한다. 이 마법사에서 GWT 프로젝트 시작 마법사를 실행하여 GWT 프로젝트를
작성할 수 있다. (이 시리즈의 Part
1에서 GWT 프로젝트를 App Engine for Java에서 작성하는 방법에 대한 자세한 지침을 볼 수 있다.)
이 기사의 예제에서는 간단한 CRUD 애플리케이션부터 시작한 후 나중에 실제 저장소를 추가한다. 지금은
모의 구현을 가지고 있는 DAO(Data Access Object)를 사용한다(Listing 1 참조).
Listing 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
인터페이스이다. Listing 2의 Contact 클래스가 도메인 오브젝트이다.
Listing 2. Contact 도메인 오브젝트(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;
}
}
|
이 첫 번째 버전의 애플리케이션에서는 연락처를 메모리 내 컬렉션에 저장하는 모의
오브젝트를 사용한다(Listing 3 참조).
Listing 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를 작성할 차례이다. 이 GUI에서는 ContactDAO
인터페이스의 모든 메소드를 사용해야 한다. 가장 먼저 수행할 작업은 Listing 4와 같이 DAO 클래스의 기능을
하나의 서비스로 랩핑하는 것이다. (후속 버전에서는 서버측에 있는 데이터 저장소와 실제로 통신할 것이므로
이러한 기능이 서버에 있어야 한다).
Listing 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에 맵핑된다(Listing 5 참조).
Listing 5. ContactService에 대한 web.xml
<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 프론트 엔드에서 이 서비스에 액세스하는 것을 허용하려면 원격 서비스 인터페이스와
비동기 원격 서비스 인터페이스 둘 다를 정의해야 한다(Listing 6 및 7 참조).
Listing 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);
}
|
Listing 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 인터페이스를
구현하고 "contacts"의 상대 경로를 지정하는 @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를 애플리케이션에 대한 기본 진입점으로
지정한다(Listing 8 참조).
Listing 8. ContactList.gwt.xml
<entry-point class='gaej.example.contact.client.ContactListEntryPoint'/>
|
ContactListEntryPoint 클래스는 GWT(com.google.gwt.core.client.EntryPoint)의
EntryPoint 인터페이스를 구현한다. 즉, 이 클래스는 GUI를 초기화하기 위해
호출된다. ContactListEntryPoint는 많은 작업을 수행하지 않는다. ContactListGUI의
인스턴스와 ContactServiceDelegate의 인스턴스를 작성한 후 두 인스턴스가
협업할 수 있도록 상대 인스턴스에 대한 정보를 제공한다. 그런 다음 ContactListEntryPoint는
GUI 이벤트 연결을 수행한다. Listing 9에서는 ContactListEntryPoint를 보여 준다.
Listing 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 이벤트에 응답하기 위한 gui_eventXXX
메소드가 있다.
ContactListGUI는 GUI 위젯을 작성하고 이러한 위젯의 이벤트에 응답한다. ContactListGUI는
GUI 이벤트를 사용자가 ContactsService에 대해 수행하려는 동작으로 변환한다. ContactListGUI는
ContactServiceDelegate를 사용하여 ContactService의 메소드를 호출하며 ContactServiceDelegate는
ContactService에 대한 비동기 인터페이스를 작성한 후 이 인터페이스를 사용하여 비동기 Ajax 호출을
만든다. ContactServiceDelegate는 ContactListGUI에 서비스의 이벤트(성공
또는 실패 리턴)를 통지한다. Listing 10에서는 ContactServiceDelegate를 보여 준다.
Listing 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 목록을 관리한다(Listing 11 참조).
Listing 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에 사용되는 위젯
Listing 12에서는 ContactListGUI가 위젯과 연락처 양식을 작성한 후 위젯을
양식에 배치하는 방법을 보여 준다.
Listing 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 메소드를 호출하여 이 GUI 애플리케이션을 호스팅하는 HTML 페이지에
정의되어 있는 슬롯(Listing 13 참조)에 contactGrid, formGrid,
status 및 addNewButton 위젯을 배치한다.
Listing 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는
서비스의 listContact 메소드를 비동기적으로 호출하는 ContactServiceDelegate의
listContacts 메소드를 호출한다. listContact 메소드가
리턴되면 ContactServiceDelegate에 정의된 익명 내부 클래스가 service_eventListRetrievedFromService라는
서비스 이벤트 처리기 메소드를 호출한다(Listing 14 참조).
Listing 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 링크를 클릭하면 Listing 15의 gui_eventContactGridClicked가 호출된다.
Listing 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(Listing
15 참조)이 호출된다. 이제 formGrid가 표시되도록
설정되고 연락처가 편집 중인 것으로 설정되며 연락처 특성이 emailField,
phoneField 및 nameField 위젯에 복사된다.
사용자가 updateButton을 클릭하면 gui_eventUpdateButtonClicked
이벤트 처리기 메소드가 호출된다(Listing 16 참조). 이 메소드는 addNewButton을 표시되도록
설정하고(따라서 사용자가 새 연락처를 추가할 수 있음) formGrid를 숨긴다. 그런 다음
copyFieldDateToContact를 호출하여 emailField, phoneField
및 nameField 위젯의 텍스트를 currentContact의 특성에 다시
복사한다. 그런 다음 ContactServiceDelegate updateContact
메소드를 호출하여 새로 업데이트된 연락처를 서비스에 다시 전달한다.
Listing 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에서 제공하는 인프라를
활용하는 방법을 알 수 있다. Listing 17에서는 ContactListGUI의 전체
코드를 보여 준다.
Listing 17. ContactListGUI의 전체 코드
package gaej.example.contact.client;
import java.util.List;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
public class ContactListGUI {
/* Constants. */
private static final String CONTACT_LISTING_ROOT_PANEL = "contactListing";
private static final String CONTACT_FORM_ROOT_PANEL = "contactForm";
private static final String CONTACT_STATUS_ROOT_PANEL = "contactStatus";
private static final String CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar";
private static final int EDIT_LINK = 3;
private static final int REMOVE_LINK = 4;
/* GUI Widgets */
protected Button addButton;
protected Button updateButton;
protected Button addNewButton;
protected TextBox nameField;
protected TextBox emailField;
protected TextBox phoneField;
protected Label status;
protected Grid contactGrid;
protected Grid formGrid;
/* Data model */
private List<Contact> contacts;
private Contact currentContact;
protected ContactServiceDelegate contactService;
public void init() {
addButton = new Button("Add new contact");
addNewButton = new Button("Add new contact");
updateButton = new Button("Update contact");
nameField = new TextBox();
emailField = new TextBox();
phoneField = new TextBox();
status = new Label();
contactGrid = new Grid(2,5);
buildForm();
placeWidgets();
}
private void buildForm() {
formGrid = new Grid(4,3);
formGrid.setVisible(false);
formGrid.setWidget(0, 0, new Label("Name"));
formGrid.setWidget(0, 1, nameField);
formGrid.setWidget(1, 0, new Label("email"));
formGrid.setWidget(1, 1, emailField);
formGrid.setWidget(2, 0, new Label("phone"));
formGrid.setWidget(2, 1, phoneField);
formGrid.setWidget(3, 0, updateButton);
formGrid.setWidget(3, 1, addButton);
}
private void placeWidgets() {
RootPanel.get(CONTACT_LISTING_ROOT_PANEL).add(contactGrid);
RootPanel.get(CONTACT_FORM_ROOT_PANEL).add(formGrid);
RootPanel.get(CONTACT_STATUS_ROOT_PANEL).add(status);
RootPanel.get(CONTACT_TOOL_BAR_ROOT_PANEL).add(addNewButton);
}
private void loadForm(Contact contact) {
this.formGrid.setVisible(true);
currentContact = contact;
this.emailField.setText(contact.getEmail());
this.phoneField.setText(contact.getPhone());
this.nameField.setText(contact.getName());
}
private void copyFieldDateToContact() {
currentContact.setEmail(emailField.getText());
currentContact.setName(nameField.getText());
currentContact.setPhone(phoneField.getText());
}
public void gui_eventContactGridClicked(Cell cellClicked) {
int row = cellClicked.getRowIndex();
int col = cellClicked.getCellIndex();
Contact contact = this.contacts.get(row);
this.status.setText("Name was " + contact.getName() + " clicked ");
if (col==EDIT_LINK) {
this.addNewButton.setVisible(false);
this.updateButton.setVisible(true);
this.addButton.setVisible(false);
this.emailField.setReadOnly(true);
loadForm(contact);
} else if (col==REMOVE_LINK) {
this.contactService.removeContact(contact);
}
}
public void gui_eventAddButtonClicked() {
addNewButton.setVisible(true);
formGrid.setVisible(false);
copyFieldDateToContact();
this.phoneField.getText();
this.contactService.addContact(currentContact);
}
public void gui_eventUpdateButtonClicked() {
addNewButton.setVisible(true);
formGrid.setVisible(false);
copyFieldDateToContact();
this.contactService.updateContact(currentContact);
}
public void gui_eventAddNewButtonClicked() {
this.addNewButton.setVisible(false);
this.updateButton.setVisible(false);
this.addButton.setVisible(true);
this.emailField.setReadOnly(false);
loadForm(new Contact());
}
public void service_eventListRetrievedFromService(List<Contact> result) {
status.setText("Retrieved contact list");
this.contacts = result;
this.contactGrid.clear();
this.contactGrid.resizeRows(this.contacts.size());
int row = 0;
for (Contact contact : result) {
this.contactGrid.setWidget(row, 0, new Label(contact.getName()));
this.contactGrid.setWidget(row, 1, new Label (contact.getPhone()));
this.contactGrid.setWidget(row, 2, new Label (contact.getEmail()));
this.contactGrid.setWidget(row, EDIT_LINK, new Hyperlink("Edit", null));
this.contactGrid.setWidget(row, REMOVE_LINK, new Hyperlink("Remove", null));
row ++;
}
}
public void service_eventAddContactSuccessful() {
status.setText("Contact was successfully added");
this.contactService.listContacts();
}
public void service_eventUpdateSuccessful() {
status.setText("Contact was successfully updated");
this.contactService.listContacts();
}
public void service_eventRemoveContactSuccessful() {
status.setText("Contact was removed");
this.contactService.listContacts();
}
public void service_eventUpdateContactFailed(Throwable caught) {
status.setText("Update contact failed");
}
public void service_eventAddContactFailed(Throwable caught) {
status.setText("Unable to update contact");
}
public void service_eventRemoveContactFailed(Throwable caught) {
status.setText("Remove contact failed");
}
public void service_eventListContactsFailed(Throwable caught) {
status.setText("Unable to get contact list");
}
}
|
 |

|
결론
Google App Engine for Java를 소개하는 세 편의 기사 중 이 두 번째 기사에서는
App Engine for Java를 위한 Eclipse 플러그인 도구를 사용하여 사용자 정의 GWT
애플리케이션을 작성하는 과정을 살펴보았다. 간단한 연락처 관리 애플리케이션을 작성하는
과정을 통해 설명한 내용은 다음과 같다.
- 비동기적으로 작동하는 원격 서비스 작성하기
- 중첩된 내부 클래스 선언이 없도록 GUI 코드 구성하기
- GWT를 활용하여 두 가지 주요 사용 사례를 위한 기능 구현하기
이 시리즈의 Part 3에서는 연락처 관리 애플리케이션을 구체화한 후 App Engine for Java
데이터 저장소 기능을 사용하여 Contact 오브젝트를 유지하기 위해
필요한 지원을 추가한다.
참고자료 교육
- "Google App Engine for Java,
Part 1: 새로운 시작"(Rick Hightower 저, developerWorks, 2009년 8월): App Engine for Java를 위한 Eclipse 플러그인의 기본적인
사용법을 살펴본 후 확장 가능한 간단한 애플리케이션을 빠르게 작성하는 방법에 대해 설명한다.
-
"Google Web Toolkit, Apache Derby, Eclipse를 사용하여 Ajax 애플리케이션
구현하기, Part 1: 환상적인 프론트엔드"(Noel Rappin 저, developerWorks, 2006년 10월): GWT(Google Web Toolkit)를 사용하여 Ajax 프로그램을
작성하는 과정을 소개하는 네 편의 기사로 구성된 튜토리얼의 첫 번째 기사이다.
- "클라우드에 연결하기,
Part 1: 애플리케이션에서 클라우드 활용하기"(Mark O'Neill 저, developerWorks, 2009년 4월): 세 편의 기사를 통해 주요 공급자(Amazon,
Google, Microsoft 및 SalesForce.com)의 클라우드 컴퓨팅 플랫폼에 대한 개요를 살펴본다.
-
Google App Engine 홈 페이지: App Engine에 대한 자세한 정보를 볼 수 있다.
-
Google App Engine Java 설명서: App Engine for Java를 이용한 Java 개발에 유익한 정보를 제공한다.
-
Will it play in App Engine for Java?:
App Engine for Java와 호환되는 표준 Java API 및 프레임워크를 확인할 수 있다.
-
BigTable이란?: Google Research Publications에서 제공하는 BigTable에 대한 정보를 읽어보자.
-
"GWT: The most important announcement at JavaOne?"(Rick Hightower 저,
Java Developers Journal, 2006년 6월): GWT가 Java 애플리케이션 개발의 새 지평을 연 도구로 평가되는 이유를 알 수 있다.
제품 및 기술 얻기
토론
필자소개  | |  | Rick Hightower는 클라우드 컴퓨팅, GWT, Java EE, Spring 및 Hibernate 개발과 관련된
교육 서비스를 전문으로 제공하는 회사인 Mammatus Inc.의
CIO이다. 유명한 Java Tools for Extreme Programming의 공동 저자이며 TheServerSide.com에서
여러 해 동안 다운로드 수가 가장 높았던 Struts Live 초판의 저자이다. IBM developerWorks에
많은 기사와 튜토리얼을 기고하고 있는 그는 Java Developer's Journal의 편집위원으로 활동하고 있으며
DZone에도 Java 및 Groovy와 관련된 많은 글을 기고하고 있다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|  |