[GWT] 비동기 호출에 익숙해지기

참조:http://code.google.com/intl/ko-KR/webtoolkit/doc/latest/DevGuideServerCommunication.html#DevGuideGettingUsedToAsyncCalls

코드를 꼭 순차적으로 실행할 필요없는데.. 그렇게해서 오히려 개발자가 대기 화면을 만들어야 하는 부담이 생긴다.

비동기 RPC를 사용해서 병렬처리를 할 수 있다. 예를 들어, 화면을 그리는데 1초, 데이터를 가져오는데 1초가 걸린다면, 이 두 작업을 병렬로 처리해서 1초 동안 화면을 그릴때 데이터를 가져올 수 있다.

직렬처리

GettingUsedToAsyncCalls1.png

병렬처리

GettingUsedToAsyncCalls2.png

비동기 호출로 실행되는 코드는 블럭처리하지 않는다(non-blocking).

 // 이 코드는 RPC를 하기 전에 실행된다.
//
if (startRow == lastStartRow) {
...
}

// RPC 호출 실행, 콜백 메서드를 구현한다:
//
calService
.getPeople(startRow, maxRows, new AsyncCallback<Person[]>() {

// RPC가 끝났을 때 실패했다면 이 코드를 실행한다.
public void onFailure(Throwable caught) {
statusLabel
.setText("Query failed: " + caught.getMessage());
acceptor
.failed(caught);
}

// RPC가 끝났을 때 성공했다면 이 코드를 실행한다.
public void onSuccess(Person[] result) {
lastStartRow
= startRow;
lastMaxRows
= maxRows;
lastPeople
= result;
pushResults
(acceptor, startRow, result);
statusLabel
.setText("Query reutrned " + result.length + " rows.");
}
});

// 위 코드를 실행하느라 대기하지 않고 위 코드는 바로 반환된다.
// 다음 코드가 RPC 처리 중에 실행될 것이다.
//
statusLabel
.setText("Query in progress...");
...

[GWT] 직렬화

참조: http://code.google.com/intl/ko-KR/webtoolkit/doc/latest/DevGuideServerCommunication.html#DevGuideSerializableTypes

GWT에서 RPC를 할 때 메서드로 넘기는 매개변수나 메서드의 리턴값에 직렬화 개념을 적용합니다. 이때 GWT의 직렬화랑 자바의 직렬화가 약간 다르기 때문에 GWT에서 제공하는 마커 인터페이스인 IsSerializable을 사용할 수 있고 기본으로 직렬화 되는 타입도 있습니다.

직렬화 가능한 타입

  • 기본타입: char, byte, short, int, long, double, float, boolean…
  • String, Date, 기본타입의 래퍼 클래스(Character, Byte, Short, Integer, …)
  • enum: 필드 값들은 직렬화 하지 않고 enum 이름만.
  • 직렬화 가능한 타입의 배열
  • 직렬화 가능한 사용자 정의 클래스
  • 직렬화 가능한 하위 타입을 하나라도 가지고 있는 타입

java.lang.Object는 직렬화 가능한 타입이 아니기 때문에 Object의 콜렉션은 직렬화되지 않는다.

직렬화 가능한 사용자 정의 클래스

다음 세가지 조건을 모두 만족해야 한다.

  • IsSerializable 또는 Serializable 인터페이스 구현.
  • final transient가 아닌 모든 필드가 직렬화 가능해야 함.
  • GWT 1.5부터는 기본 생성자를 가지고 있거나 생성자가 하나도 없는 클래스.

transient 키워드는 원래 직렬화에서 제외할 용도로 만들어진 것이고, final로 선언한 필드도 직렬화가 되지 않기 때문에 transient를 붙여주는게 좋다.

다형성

지원함.

로우타입

콜렉션을 직렬화 하려면 제네릭을 사용해서 타입을 명시해야 함.

조작 클래스 직렬화

서버쪽에서 Enhanced 클래스(조작 클래스)를 사용하면 바이트코드나 클래스 소스 코드에 부가적인 코드를 집어넣거나 기존 코드를 변경하기 때문에 클라이언트 코드와 서버쪽 코드가 달라져서 GWT 이전 버전에서는 RPC 할때 직렬화 할 수 없었다.

그러나 GWT 2.0 부터는 다음과 같은 클래스들은 직렬화 할 수 있다.

  • JDO 애노테이션을 붙인 클래스: detachable=true 속성을 가지고 있는 PersistenceCapable 애노테이션
  • JPA 애노테이션을 붙인 클래스: Entity
  • .gwt.xml에 rpc.enhancedClasses에 설정한 클래스

영속성 구현체 사용시 주의할 것

  • RemoteService에 전달하거나 거기서 반환하는 객체는 반드시 Detached 상태여야 한다.
  • 조작된 필드가 final이나 transient가 아니라면 자바 직렬화가 가능한 필드여야 한다.
  • 부가작용이 있는 필드를 변경하려면 그 필드에 해당하는 세터가 있어야 한다.

서버에서 클라이언트로 보낼 때는 일반적인 GWT RPC가 적용된다. 즉 서버에서 조작된 필드가 추가로 클라이언트에 전달 된다.

클라이언트에서 서버로 넘어올 땐 인코딩해서 넘기고 서버쪽에서 세터로 디코딩해서 넣는다.

ps: 아흑. 도무지 아래 세문장은 잘 이해가 안되넹;; 어쨌거나 결국은 JPA 사용한 클래스도 RPC에서 직렬화 된다는거 아닌가;

[GWT] AsyncCallback 인터페이스

참조: http://google-web-toolkit.googlecode.com/svn/javadoc/2.0/com/google/gwt/user/client/rpc/AsyncCallback.html

GWT에서 RPC를 할 때 사용하는 주요 인터페이스다. 서비스 인터페이스의 비동기 인터페이스를 만들 때 사용한 콜백 인터페이스인데, 약간 특이하다. 우선 제네릭을 사용해서 AsyncCallback에 타입을 줄 수 있는데, 이 타입은 보통 서비스 인터페이스에서 호출할 메서드의 리턴 타입이 된다. 즉 서비스 인터페이스에서 Shape[]를 반환하는 메서드가 있었다고 치자.

Shape[] getShapres(String dbName) throws ShapeException;

그럼 AsyncCallback<Shape[]> 이런식으로 타입을 선언해주면 된다. 그리도 이 콜백 인터페이스에는 두 개의 메서드가 있는데 하나는 비동기 호출이 성공했을 때 호출될 녀석이고 다른 건 비동기 호출이 실패했을 때 사용하는 녀석이다. 여기서도 일종의 규칙이 있는데 성공과 실패는 서비스 구현체에서 예외를 사용해서 구분한다. 즉 예외가 발생하면 실패, 아니면 성공. 그래서 구현할 메서드 타입도 다음과 같이 생겼다.

pic1.png

실패했을 땐 서비스 구혀체에서 던진 예외를 받아올 수 있고, 성공했을 땐 서비스 메서드에서 반환한 값을 가져올 수 있다.

AyncCallback 타입 객체를 만들어서 비동기 인터페이스 타입에 있는 메서드 인자로 넘겨줘야 하는데 이 때도 여러가지 방법이 있을 수 있다. 가장 간단한 방법으로는 익명 클래스를 사용하는 방법이 있고, GWT 시작하기 문서처럼 로컬 클래스를 사용할 수도 있다. 뭐.. static 멤버 클래스나 non-static 멤법 클래스를 사용해도 되고 아주 별도의 클래스로 만들어 사용할 수도 있겠으나… 최대한 간단한 녀석으로 시도해 보고 해당 메서드 내부에서 공유해서 사용할 법하면 로컬 클래스로 빼고 여러 메서드에서 사용할 녀석이라면 static 멤버 클래스로 빼는 순으로 스코프를 정하는게 좋겠다.

[GWT] RPC

참조: http://code.google.com/intl/ko-KR/webtoolkit/doc/latest/tutorial/RPC.html

AnatomyOfServices.png

이미지 출처: http://code.google.com/intl/ko-KR/webtoolkit/doc/latest/tutorial/RPC.html

RPC를 사용해서 서버쪽에 서비스를 구현해 놓고 클라이언트에서 호출해서 사용하려면 RMI 하듯이 스켈레톤/스텁 코드를 써야 하나 봅니다. 그래서 만들께 세 가지인데.. 우선 클라이언트 쪽에 서비스 인터페이스 하나, 그리고 서버쪽에 그 인터페이스의 구현체 하나, 마지막으로 다시 클라이언트쪽에서 서버쪽에 있는 서비스 대신 호출할 비동기 서비스 인터페이스 하나를 만들어야 합니다.(처음 두개는 그렇다치고 마지막껀 어떻게 자동생성 안되려나…)

1. 서비스 인터페이스 만들기

서비스 인터페이스를 만들 때는 RemoteService 인터페이스를 상속해야 합니다.

package com.google.gwt.sample.stockwatcher.client;import com.google.gwt.user.client.rpc.RemoteService;import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("stockPrices")public interface StockPriceService extends RemoteService {

StockPrice[] getPrices(String[] symbols);
}

이런식으로요. 이때 패키지가 중요한데 반드시 client 패키지에 만들어야 합니다.

2. 서비스 구현체 만들기

서비스 구현체는 RemoteServiceServlet을 상속받아야 하고 구현하려는 인터페이스를 구현합니다. 이때 RemoteServiceServlet을 반드시 상속 받아야 클라이언트와 서버간에 주고받는 데이터를 직렬화 해줍니다.

package com.google.gwt.sample.stockwatcher.server;import com.google.gwt.sample.stockwatcher.client.StockPrice;import com.google.gwt.sample.stockwatcher.client.StockPriceService;import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class StockPriceServiceImpl extends RemoteServiceServlet implements StockPriceService {

public StockPrice[] getPrices(String[] symbols) {// TODO Auto-generated method stubreturn null;}

}

이때 패키지는 반드시 server에 둬서 자바스크립트로 변환되지 않도록 합니다. 이 서비스는 서버에서 제공할테니 자바스크립트로 변환될 필요도 없고 그러려면 server 패키지 밑에둬야 합니다.

3. 서비스 등록하기

위에서 만든 서비스를 GWT 모듈에 넣으려면 web.xml에 서블릿 매핑을 사용해서 등록해야 합니다. 이때 URL 패턴을 주의해야 하는데 URL 앞부분은 GWT 모듈 설정 파일인 ~~.gwt.xml 파일의 module 엘리먼트의 rename-to 어트리뷰트의 값을 사용해야 하고 URL 뒷 부분은 서비스 인터페이스에 선언한 애노테이션의 값을 따라갑니다. 즉 모듈 설정에 rename-to의 값이 stockwatcher고 서비스 인터페이스에 선언한 @RemoveServiceRelativePath()에 설정한 값이 stockPrices기 때문에 URL은 /stockwathcer/stockPrices가 됩니다. 그런데.. 인텔리J는 똑똑해서 web.xml에 필요한 설정을 자동으로 추가해 주는군요. @_@;; 오호.. 놀라워라.

  <!-- Servlets -->
  <servlet>
<servlet-name>stockPriceServiceImpl</servlet-name>
<servlet-class>com.google.gwt.sample.stockwatcher.server.StockPriceServiceImpl</servlet-class>
</servlet><servlet-mapping>
<servlet-name>stockPriceServiceImpl</servlet-name>
<url-pattern>/stockwatcher/stockPrices</url-pattern>
</servlet-mapping>

GWT 1.6이전에는 GWT 모듈 설정파일에서 서블릿을 관리했는데 요즘은 web.xml에서 하는 듯.

4. 비동기 호출용 인터페이스 만들기

이부분은 본문을 읽어내려가면서 자동 생성되길 바랬던 클래스인데 인텔리J에서는 서비스 인터페이스 만들고 extends RemoteService를 하는 즉시 빨간불이 뜨면서 퀵픽스로 비동기 인터페이스를 만들어 줄 수 있더군요.

package com.google.gwt.sample.stockwatcher.client;import com.google.gwt.user.client.rpc.AsyncCallback;

public interface StockPriceServiceAsync {

void getPrices(String[] symbols, AsyncCallback<StockPrice[]> callback);

}

자동으로 만들어준 코드를 보면 메서드 이름과 매개변수는 같지만 AsyncCallBack이라는 매개변수가 추가됐고 리턴타입이 void로 바뀐걸 볼 수 있습니다.

5. 비동기 호출하기

GWT.create(StockPriceService.class); 를 사용해서 비공기 호출용 인터페이스 타입의 객체를 만들 수 있고, 그 객체에 AsyncCallback 타입 객체를 만들어 넘겨주면 됩니다.

private void refreshWatchList() {
    // Initialize the service proxy.
if (stockPriceSvc == null) {
stockPriceSvc = GWT.create(StockPriceService.class);
}// Set up the callback object.AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() {public void onFailure(Throwable caught) {// TODO: Do something with errors.}

public void onSuccess(StockPrice[] result) {updateTable(result);}};

// Make the call to the stock price service.
stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
}

6. 데이터 직렬화

서비스 인터페이스에서 반환타입으로 사용하는 기초타입과 기초타입의 래퍼클래스는 기본으로 직렬화가 가능하고 직렬화가 가능한 타입의 배열도 역시 직렬화 할 수 있습니다.

특정 클래스를 직렬화 하려면 다음 세가지 조건을 만족해야 합니다.

  • 자바의 Serializable 또는 GWT의 IsSerializable 구현
  • final이나 transient가 아니어야 함
  • 생성자가 있어야 함

직렬화와 관련된 자세한 내용은 여기 참조.

흠.. 역시 똑똑한 인텔리J는 이미 서비스 인터페이스 만들 때 부터 빨간불로 직렬화해야 한다고 알려주고 있었네요. 퀵픽스를 사용해서 자바의 Serializable을 구현하게 했습니다.