-
[Google App Engine] Google Web Toolkitcase Computer : 2011. 4. 13. 10:00이번에 소개할 내용은 GWT 웹 퉅킷 입니다.
Google Web Toolkit
소개
Google Web Toolkit(이하 GWT)는 Java 코드를 JavaScript로 컴파일 하여 브라우저(클라이언트)에서 실행할 수 있도록 도와주는 도구이다.
GWT는 클라이언트/서버 사이드 모두 Java 만 사용한다.
클라이언트 사이드에 Java 코드를 JavaScript 와 DHTML로 변환하기 위한 컴파일러를 따로 제공한다. (App Engine 코드 보다 컴파일 시간이 오래 걸림.)
JavaScript가 Java의 API를 완벽히 제공하고 있지 않기 때문에 클라이언트 사이드의 코드를 작성시 제약사항이 있다.
제약사항
모든 primitive Type(char, int, float, byte 등) 과 관련 클래스등은 모두 지원하지만 long 은 double로 변환되기때문에 int를 사용하는 것을 권장한다.
JavaScript는 Single-Thread 방식을 사용하기 때문에 멀티스레드 API는 제공되지 않는다.
Reflection은 지원되지 않는다. GWT.getTypeName 메소드를 통해 오브젝트 클래스 명을 얻을 수 있다.
Java.util에서 제공하는 몇가지 오브젝트 컨테이너(Stack, Vector, HashMap 등)를 사용할수 있다. (Date도 사용가능)
프로젝트 디렉토리
app Engine 프로젝트와 GWT 프로젝트 폴더는 좀 차이가 있다.
소스를 보면 크게 *.client와 *.server 두가지로 나뉘어져 있다. 이렇게 나눈 이유는 *.client 팩키지 부분의 소스는 javaScript로 Convert 된다.
/src
/*.client
Client 용 java 코드가 들어있다. 컴파일후 JavaScript 가 되는부분
/*.server
Server 용 java 코드가 들어있다.
/test : JUnit test 를 위한 디렉토리
UI
UI 위젯
UI를 만드는 과정은 Java에서 Swing과 매우 유사하다.
Com.google.gwt.user.client.ui.*
최상위 패널은 RootPanel이며, HTML 소스과 연결 방법은 매우 간단하다.
*.html
…
<div id=”uiPanel”></div>
...
------------------------------------------------------------------
*.java
...
public void onModuleLoad(){
RootPanel.get(“uiPanel”);...
위젯 이벤트
마우스/키보드 이벤트 역시 동일하게 작성하면 된다.
Com.google.gwt.event.dom.client.*
private VerticalPanel buttonPanel = new VerticalPanel();
private Button memoAdd = new Button("Add");
public void onModuleLoad() {
buttonPanel.add(memoAdd);
RootPanel.get("memoList").add(buttonPanel);
memoAdd.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
//action...
}
});
}
위젯 스타일 작성
기본적인 스타일은 위젯마다 기본적인 size나 들어있다.
Table의 setBorderwidth 같은 아주 기본적인 역할을 하는 함수가 있다.
CSS
html에서 사용하듯 css 를 이용하여 스타일을 작성할수 있다.
각 위젯 마다 addStyleName() 메소드를 사용하여 쉽게 스타일을 추가 할 수 있다.
CSS 파일은 /war 디렉토리에 html과 jsp 파일과 동일한 위치에 있다.
test.css
-------------------
...
.watchList {
border: 1px solid silver;
padding: 2px;
margin-bottom: 6px;
}
.cellSyle{
background-color: #2062B8;
color: white;
}
*.html
------------------
…
<head>
<link tpe=”text/css” rel=”stylesheet” href=”test.css”>
</head>
<body>
<div id=”test”></div>
...
*.java
-------------------
private FlexTable table = new FlexTable();
public void onModuleLoad() {
table.addStyleName(“watchList”);
table.getCellFormatter().addStyleName(0, 0, “cellStyle”);
...
JavaScript 변환
javaScript 로 변환전
label 과 Button 하나씩 세로로 노인 코드이다.
...
public class MAL implements EntryPoint {
private VerticalPanel pp = new VerticalPanel();
private Label label = new Label();
private Button button = new Button();
public void onModuleLoad() {
label.setText(“name”);
button.setText(“ButtonName”);
pp.add(label);
pp.add(button);
RootPanel.get("malware").add(pp);
}
}
Html 코드
...
<body>
<div id="malware"></div>
</body>
…
javaScript 로 변환후
코드를 보면 UI코드가 해당 id가 있는 tag의 들어간다.
iframe 태그를 사용해 변환된 script를 가져온다.
이 변환된 script는 알아보기가 매우 힘들게 되어있다.
...
<body>
<div id="malware">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td align="left" style="vertical-align: top; "><div class="gwt-Label">name</div></td>
</tr>
<tr>
<td align="left" style="vertical-align: top; "><button type="button" class="gwt-Button">buttonName</button></td>
</tr>
</tbody>
</table>
</div>
<iframe src="javascript:''" id="mal" style="position: absolute; width: 0px; height: 0px; border-top-style: none; border-right-style: none; border-bottom-style: none; border-left-style: none; border-width: initial; border-color: initial; " tabindex="-1">
<html>
…
…
</html>
</iframe>
</body>
Client-Server 통신
GWT 어플리케이션은 클라이언트에서 실행되는 프로그램이기때문에 데이터를 저장할수 있는 방법이 없다. 그외에 클라이언트와 서버 간의 데이터를 주고 받기 위하여 서버-클라이언트간 통신방법이 필요하다.
GWT 에서 JSON, RPC, Cross-Site 3가지 방법을 제공한다.
JSON
JSON은 XML과 비슷한 데이터 표현 포멧 형태이다.
Format
서버 어플리케이션에서 제공하려는 데이터를 다음과 같은 포멧형태로 제공한다.
기본적인 object 표현 포멧 형식
-
[
{ “property” : “value”,
…
},
{“property” : “value”,
…
},
...
]
배열 표현 형식
-
[
value,
value,
....
]
Client 에서 JSON data 받아오기
JSON Object 를 받을 Class 생성
import java.io.Serializable;
public class StockPrice implements Serializable{
private String symbol;
private double price;
public StockPrice(){}
public StockPrice(String symbol, double price, double change){
this.symbol = symbol;
this.price = price;
this.change = change;
}
…
getter/setter
...
JSON Object 의 메소드를 이어주는 JavaScriptObject 생성
javascript code를 만들려면 /*- java code -*/; 사이에 넣으면 된다.
...
public class StockData extends JavaScriptObject{
protected StockData() {}
public final native String getSymbol() /*-{ return this.symbol; }-*/;
public final native double getPrice() /*-{ return this.price; }-*/;
}
JSON string 을 위에서 만든 객체의 배열로 변환 할수 있는 코드
private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
return eval(json)
}-*/;
Request 생성
url은 JSON Data를 받을수 있는 페이지이다.
Response 를 받은경우 Response 객체의 JSON data 가 문자열 형태로 들어 있다.
문자열을 위에서 만든 객체 배열로 변환하는 함수를 통해 JsArray 형태의 객체 배열을 얻는다.
url = URL.encode(url);
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
try {
Request request = builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
displayError("Error");
}
public void onResponseReceived(Request request, Response response) {
if (200 == response.getStatusCode()) {
updateTable(asArrayOfStockData(response.getText()));
} else {
displayError(response.getStatusCode());
}
}
});
} catch (RequestException e) {
e.printStackTrace();
}
...
RPC
서버와 직접적으로 통신할수 있는 방법으로 서버측의 함수를 직접 호출하는 방법이다.
필요한 만큼 구현해야되는 사항도 많다.
클라이언트
서버로 부터 서비스 받을 interface를 작성한다.
RemoteService 클래스를 상속받아야 한다.
Annotation “RemoteServiceRelativePath” 을 통해 실제 호출할 함수의 위치를 입력한다.
package com.memo.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@RemoteServiceRelativePath("/savememo")
public interface saveMemoService extends RemoteService{
Integer saveMemo(String title, String content);
}
서비스를 호출하기 위한 interface 작성
위에서 작성한 메소드명과 동일하게 작성한다.
파라메터는 두가지로 분류된다. 일반 파라메터와 Callback 부분으로 Callback 부분은 항상 마지막에 와야 하며, 반드시 하나가 존재해야한다. 그에 반해 일반 파라메터는 서버로 전송할 정보가 없다면 없어도 된다.
Callback 은 서버로 부터 RPC 서비스의 대한 결과값을 받는 부분이다.
package com.memo.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface saveMemoServiceAsync {
void saveMemo(String title, String content, AsyncCallback<Integer> callback);
}
서비스 호출
서비스를 호출할수 있는 saveMemoServiceAsync 의 객체를 생성
callback의 인스턴스를 생성한다.
서비스가 성공적이인 경우 callback에 onSuccess() 호출 되고 실패시에 onFailure() 가 호출된다.
서비스를 호출한다.
package com.visual.memo.client;
...
public class Memo implements EntryPoint {
..
private saveMemoServiceAsync saveMemoSvc = GWT.create(saveMemoService.class);
…
private void saveMemo(){
ServiceDefTarget target = (ServiceDefTarget) saveMemoSvc;
target.setServiceEntryPoint("/savememo");
AsyncCallback<Integer> callback = new AsyncCallback<Integer>() {
public void onSuccess(Integer result) {
stateLabel.setText("saved/code:"+result.toString());
}
public void onFailure(Throwable caught) {
stateLabel.setText("Error : saveMemo_" + caught.getMessage());
}
};
saveMemoSvc.saveMemo(“title”, “content”, callback);
}
…
}
서버
서비스 구현
RCP 서비스 구현을 위해 RemoteServiceServelt을 상속받는다.
또한 클라이언트에서 작성된 interface를 이곳에서 구현한다.
package com.visual.memo.server;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.visual.memo.client.saveMemoService;
public class saveMemoServiceImpl extends RemoteServiceServlet implements saveMemoService{
public Integer saveMemo(String title, String content) {
…
return 0;
}
}
Web.xml 수정
실제 브라우저 상에서 http://real_domain/savememo 라는 입력이 왔을때 com.memo.server.saveMemoServiceImpl Servlet을 실행되게 하는 부분이다.
…
<web-app>
<servlet>
<servlet-name>saveMemo</servlet-name>
<servlet-class>com.memo.server.saveMemoServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>saveMemo</servlet-name>
<url-pattern>/savememo</url-pattern>
</servlet-mapping>
…
</web-app>
Internationalization
소개
하나의 어플리케이션에서 다양한 언어를 지원하기 위한 방법이다.
구현
Properties 파일 생성
파일이름은 “interfaceName_언어.properties”
interface 이름은 모두 동일하게 생성한다.
langConstants_ko.properties
name=이름
title=제목
langConstants_jp.properties
-
name=なまえ
title=だいもく
Constants 를 상속받은 interface를 생성한다.
파일명은 properties 파일에서 interfaceName 명을 사용해야한다.
DefaultStringValue Annotation를 사용해서 언어선택을 하지 않은 경우 기본적으로 출력할 값을 입력한다.
Properties 파일로부터 받아올 문자열 필드명을 메소드명과 동일하게 한다.
package com.visual.mal.client;
import com.google.gwt.i18n.client.Constants;
public interface langConstants extends Constants {
@DefaultStringValue("name")
String name();
@DefaultStringValue("buttonName")
String title();
}
*.gwt.xml 파일 수정
properties 파일의 언어 부분을 values 값으로 넣는다.
<module>
...
<extend-property name="locale" values="ko"/>
<extend-property name="locale" values="jp"/>
…
</module>
JUnit test
JUnit Test 는 GWT 프로젝트에 대해 단위 테스트를 할수 있게 해준다.
/test 디렉토리에 test class 를 만들고 GWTTestCase Class 상속받음으로 사용할수 있다.
테스트의 사용되는 함수
assertTrue(조건) : 조건이 참이면 pass
assertFalse(조건) : 조건이 거진이면 pass
assertEquals( Object, Object ) : 두 비교 객체가 같으면 pass
객체가 아니어도 Primitive Data Type 도 가능assertNotSame(Object, Obejct) : 두 비교 객체가 같이 않으면 pass
assertNotNull(Object) : 객체가 null 이 아니면 pass
위의 테스트를 pass 못하면 Errors 가 발생하게 된다.
GAE 프로젝트와 GWT 프로젝트를 동시의 개발하는 경우에 아래 와 같은 Error가 발생한다.
Timeout class가 GWT와 GAE 라이브러리의 중복되 들어있어서 아래와 같은 에러가 발생한다.
java.lang.NoSuchMethodError: org.mortbay.thread.Timeout
반응형'case Computer :' 카테고리의 다른 글
지식경제부 Software Maestro 2기 선발 (0) 2011.04.18 Google Code Jam 2011 개최 (1) 2011.04.15 [Google App Engine] 주요 기능 및 서비스 (0) 2011.04.08 Google App Engine 을 아시나요? (1) 2011.03.29 VMware에서 Port forwarding 방법 (0) 2011.01.10