ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Google App Engine] Google Web Toolkit
    case Computer : 2011. 4. 13. 10:00
    이번에 소개할 내용은 GWT 웹 퉅킷 입니다.


    1. Google Web Toolkit

      1. 소개

      • Google Web Toolkit(이하 GWT)Java 코드를 JavaScript로 컴파일 하여 브라우저(클라이언트)에서 실행할 수 있도록 도와주는 도구이다.

      • GWT는 클라이언트/서버 사이드 모두 Java 만 사용한다.

      • 클라이언트 사이드에 Java 코드를 JavaScript DHTML로 변환하기 위한 컴파일러를 따로 제공한다. (App Engine 코드 보다 컴파일 시간이 오래 걸림.)

      • JavaScriptJavaAPI를 완벽히 제공하고 있지 않기 때문에 클라이언트 사이드의 코드를 작성시 제약사항이 있다.


      1. 제약사항

      • 모든 primitive Type(char, int, float, byte ) 과 관련 클래스등은 모두 지원하지만 long double로 변환되기때문에 int를 사용하는 것을 권장한다.

      • JavaScriptSingle-Thread 방식을 사용하기 때문에 멀티스레드 API는 제공되지 않는다.

      • Reflection은 지원되지 않는다. GWT.getTypeName 메소드를 통해 오브젝트 클래스 명을 얻을 수 있다.

      • Java.util에서 제공하는 몇가지 오브젝트 컨테이너(Stack, Vector, HashMap )를 사용할수 있다. (Date도 사용가능)



      1. 프로젝트 디렉토리

      • app Engine 프로젝트와 GWT 프로젝트 폴더는 좀 차이가 있다.

      • 소스를 보면 크게 *.client*.server 두가지로 나뉘어져 있다. 이렇게 나눈 이유는 *.client 팩키지 부분의 소스는 javaScriptConvert 된다.

      • /src

        • /*.client

          • Client java 코드가 들어있다. 컴파일후 JavaScript 가 되는부분

        • /*.server

          • Server java 코드가 들어있다.

      • /test : JUnit test 를 위한 디렉토리



      1. UI

        1. UI 위젯

        • UI를 만드는 과정은 Java에서 Swing과 매우 유사하다.

        • Com.google.gwt.user.client.ui.*

        • 최상위 패널은 RootPanel이며, HTML 소스과 연결 방법은 매우 간단하다.


    *.html

    <div id=”uiPanel”></div>

    ...

    ------------------------------------------------------------------

    *.java

    ...

    public void onModuleLoad(){


    RootPanel.get(“uiPanel”);

    ...



        1. 위젯 이벤트

        • 마우스/키보드 이벤트 역시 동일하게 작성하면 된다.

        • 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...

    }

    });

    }



        1. 위젯 스타일 작성

          • 기본적인 스타일은 위젯마다 기본적인 size나 들어있다.

          • TablesetBorderwidth 같은 아주 기본적인 역할을 하는 함수가 있다.

          1. CSS

          • html에서 사용하듯 css 를 이용하여 스타일을 작성할수 있다.

          • 각 위젯 마다 addStyleName() 메소드를 사용하여 쉽게 스타일을 추가 할 수 있다.

          • CSS 파일은 /war 디렉토리에 htmljsp 파일과 동일한 위치에 있다.


    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”);

    ...


        1. 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>



      1. Client-Server 통신

      • GWT 어플리케이션은 클라이언트에서 실행되는 프로그램이기때문에 데이터를 저장할수 있는 방법이 없다. 그외에 클라이언트와 서버 간의 데이터를 주고 받기 위하여 서버-클라이언트간 통신방법이 필요하다.

      • GWT 에서 JSON, RPC, Cross-Site 3가지 방법을 제공한다.

        1. JSON

        • JSONXML과 비슷한 데이터 표현 포멧 형태이다.

          1. Format

          • 서버 어플리케이션에서 제공하려는 데이터를 다음과 같은 포멧형태로 제공한다.

          • 기본적인 object 표현 포멧 형식

    [

    { “property” : “value”,

    },

    {“property” : “value”,

    },

    ...

    ]


          • 배열 표현 형식

    [

    value,

    value,

    ....

    ]



          1. 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 생성

            • urlJSON 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();

    }

    ...


        1. RPC

        • 서버와 직접적으로 통신할수 있는 방법으로 서버측의 함수를 직접 호출하는 방법이다.

        • 필요한 만큼 구현해야되는 사항도 많다.

          1. 클라이언트

        • 서버로 부터 서비스 받을 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의 인스턴스를 생성한다.

          • 서비스가 성공적이인 경우 callbackonSuccess() 호출 되고 실패시에 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);

    }

    }




          1. 서버

          • 서비스 구현

            • 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>


      1. Internationalization

        1. 소개

        • 하나의 어플리케이션에서 다양한 언어를 지원하기 위한 방법이다.

        1. 구현

        • 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>


      1. 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 classGWTGAE 라이브러리의 중복되 들어있어서 아래와 같은 에러가 발생한다.

        • java.lang.NoSuchMethodError: org.mortbay.thread.Timeout

    반응형

    댓글

Designed by Tistory.