Web Programming

자바 개발자들을 위한 Ajax: 구글 웹 툴킷(Google Web Toolkit) 연구

_침묵_ 2006. 9. 7. 02:07
출처 : http://www-128.ibm.com/developerworks/kr/library/j-ajax4/index.html

자바 개발자를 위한 Ajax: 구글 웹 툴킷(Google Web Toolkit) 연구

단일 자바 코드 베이스에서 Ajax 애플리케이션 개발하기


난이도 : 고급

Philip McCarthy, 소프트웨어 개발 컨설턴트, Independent

2006 년 8 월 11 일
2006 년 8 월 11 일 수정

최근에 출시된 구글 웹 툴킷(GWT)은 거의 자바 코드로 표현된 동적 웹 애플리케이션을 생성하는 API 및 툴 세트입니다.GWT의 기능을 설명하고 여러분에게 맞는 것을 선택할 수 있는 방법을 제시합니다.

GWT(참고자료)는 웹 애플리케이션 개발에 있어 독특한 방식을 사용한다. 클라이언트 측 및 서버측 코드 베이스의 활용보다는 컴포넌트 기반 GUI를 생성하고 사용자 웹 브라우저에 표시하기 위한 용도로 GUI를 컴파일 하도록 하는 자바 API를 제공한다. GWT를 사용하는 과정은 일반적으로 자바 애플리케이션 개발과 관련된 경험보다는 Swing 또는 SWT로 개발하는 과정과 훨씬 더 유사하다. GWT의 사용을 통해 HTTP 프로토콜 및 HTML DOM 모델을 추상화하려는 시도를 하고 있다. 사실, GWT 애플리케이션이 웹 브라우저에서 표현된다는 사실은 거의 우연같이 느껴진다.

GWT는 코드 생성을 통해 이와 같은 기능을 이룩하고 GWT 컴파일러는 서버측 자바 코드에서 JavaScript를 생성한다. GWT는 GWT 자체에서 제공하는 API와 함께 java.lang 및 java.util의 단편들로 이루어져 있다. 하지만 이 단편들은 해독하기 어려워, 컴파일 된 GWT 애플리케이션은 블랙박스(GWT의 자바 바이트코드에 해당)로 여겨진다.

이 글에서 필자는 원격 웹 API로부터 날씨 정보를 가져와 이를 브라우저에 표시하는 단순한 GWT 애플리케이션의 생성에 대해 설명한다. GWT의 기능들을 가능하면 간단히 설명하고 GWT 기능에서 발생하는 몇 가지 잠재적인 문제들을 언급하려 한다.

시작은 간단히!

Listing 1은 GWT를 사용해 만들 수 있는 가장 단순한 애플리케이션의 자바 소스 코드를 나타낸다.


Listing 1. 가장 단순한 GWT에 관한 예
                
public class Simple implements EntryPoint {

   public void onModuleLoad() {
     final Button button = new Button("Say 'Hello'");

     button.addClickListener(new ClickListener() {
        public void onClick(Widget sender) {
        Window.alert("Hello World!");
        }
     });

     RootPanel.get().add(button);
   }
}

이는 Swing, AWT 또는 SWT에서 작성했을지도 모를 GUI 코드와 많이 유사하다. 추측했겠지만, Listing 1을 통해 클릭하면 "Hello World!" 라는 메시지를 디스플레이 하는 버튼을 생성하게 된다. 이 버튼은 HTML 페이지 본체 주위에 있는 GWT 래퍼인 RootPanel에 추가된다. 그림 1은 GWT 쉘 내에서 실행 중인 애플리케이션에 대해 나와 있다. 이 쉘은 디버깅 호스팅 환경으로 단순한 웹 브라우저를 포함하며 GWT SDK에 포함된다.


그림 1. 실행 중인 가장 단순한 GWT에 관한 예 
 



위로


Weather Reporter 애플리케이션 구축하기

필자는 GWT를 사용해 단순한 Weather Reporter 애플리케이션을 생성하려 한다. 애플리케이션의 GUI는 사용자에게 ZIP 코드 및 온도를 나타내는 ℃및 ℉ 중 하나를 선택하여 입력하는 입력 상자를 제공한다. 사용자가 전송 버튼을 클릭하면, GWT 애플리케이션은 Yahoo! 무료 Weather API를 이용해 선택된 위치의 RSS-포맷 리포트를 얻게 된다. 이 문서의 HTML 부분이 선택되어 화면에 나타나 사용자들이 보게 된다.

GWT 애플리케이션은 모듈로 패키지화되어 있고, 특정 구조에 맞아야 한다. 이른바 module-name.gwt.xml -- 라는 이름의 설정 파일은 애플리케이션의 엔트리 포인트로 작용하는 클래스를 정의하고 기타 GWT 모듈로부터 리소스 승계 여부를 나타낸다. 반드시 이 설정 파일을 모든 클라이언트 측 자바코드가 있는 client라는 이름의 패키지와 이미지, CSS 및 HTML과 같은 프로젝트 웹 자원을 포함하는 public이라는 이름의 디렉토리와 같은 레벨에 있는 GWT 애플리케이션의 소스 패키지 구조에 위치시켜야 한다. 마지막으로 public 디렉토리는 meta태그가 모듈의 정식 이름을 포함한 상태에서 HTML 파일을 반드시 포함해야 한다.

GWT의applicationCreator는 엔트리-포인트 클래스의 이름이 주어진 상태에서, 이와 같은 기본 구조를 생성한다. 따라서 
applicationCreator developerworks.gwt.weather.client.Weather를 불러오면 필자가 Weather Reporter 애플리케이션에 시작점으로 활용하는 프로젝트 개요를 생성한다. 이 애플리케이션에 대한 소스 다운로드 파일은 이 구조에 맞는 GWT 프로젝트와 같이 작용하는 유용한 타깃을 포함하는 Ant 구축파일(buildfile)을 포함한다. (Download)

기본 GUI 정의하기

먼저 임의의 기능 추가 없이, GWT 애플리케이션의 사용자-인터페이스 위젯에 관한 기본 레이아웃을 개발한다. GWT UI에서 표현할 수 있는 기능 중 거의 모든 기능을 나타내는 최고급 클래스는widget(위젯)클래스다. Widgets(위젯)은 항상 panels(패널)에 포함되어 있고 panels 자체는 Widget이라 내포되어 있다. 여러 가지 다른 타입의 패널은 각기 다른 레이아웃 기능을 제공한다. 따라서 GWT panel(패널)은 AWT/Swing 에서의 레이아웃(Layout)또는 XUL에서의 박스(BOX)의 패널과 비슷한 기능을 한다.

모든 위젯 및 패널은 위젯 및 패널을 호스팅 하는 웹 페이지에 부가되어야 한다. Listing 1에서 보다시피, 위젯 및 패널을 직접 RootPanel. Alternatively, you can use RootPanel에 부가할 수 있다. 교대로 Rootpanel을 사용해, ID 또는 클래스 네임으로 식별되는 HTML 컴포넌트에 대한 레퍼런스를 얻는다. 이 경우 필자는 input-container 및 output-container라는 명칭의 각각의 HTML DIV 컴포넌트를 사용한다. 첫 번째 파일은 Weather Reporter에 관한 UI 제어 기능을 포함하며, 두 번째 파일은 Weather Report 자체를 보여준다.

Listing 2는 기본 레이아웃 설정 시 필요한 코드를 나타내는데 이 코드는 자가 설명적이어야 한다. HTML 위젯은 단순히 HTML 작성을 위한 상자에 불과하고 Yahoo! 날씨 정보에서 나온 HTML 출력 정보를 나타내는 곳이다. 이와 같은 모든 코드는 EntryPoint 인터페이스에서 제공되는 Weather 클래스의 onModuleLoad() 메소드 내부로 들어간다. weather 모듈을 둘러싸고 있는 웹 페이지를 클라이언트 웹 브라우저로 로드 할 때 이 메소드를 호출한다.


Listing 2. Weather Reporter에 대한 메소드 코드
                
public void onModuleLoad() {

   HorizontalPanel inputPanel = new HorizontalPanel();

   // Align child widgets along middle of panel
   inputPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);

   Label lbl = new Label("5-digit zipcode: ");
   inputPanel.add(lbl);

   TextBox txBox = new TextBox();
   txBox.setVisibleLength(20);

   inputPanel.add(txBox);

   // Create radio button group to select units in C or F
   Panel radioPanel = new VerticalPanel();

   RadioButton ucRadio = new RadioButton("units", "Celsius");
   RadioButton ufRadio = new RadioButton("units", "Fahrenheit");

   // Default to Celsius
   ucRadio.setChecked(true);

   radioPanel.add(ucRadio);
   radioPanel.add(ufRadio);

   // Add radio buttons panel to inputs
   inputPanel.add(radioPanel);

   // Create Submit button
   Button btn = new Button("Submit");    

   // Add button to inputs, aligned to bottom
   inputPanel.add(btn);
   inputPanel.setCellVerticalAlignment(btn,
      HasVerticalAlignment.ALIGN_BOTTOM);

   RootPanel.get("input-container").add(inputPanel);

   // Create widget for HTML output
   HTML weatherHtml = new HTML();

   RootPanel.get("output-container").add(weatherHtml);
}

그림 2는 GWT 쉘에서 표현된 레이아웃을 나타내고 있다.


그림 2. 기본 GUI 레이아웃
 

CSS로 스타일링 기능 추가하기

표현된 웹 페이지는 보기에는 상당히 볼품없지만, CSS 스타일링 규칙으로 웹 페이지는 상당히 세련된 기능을 갖춘다. GWT 애플리케이션을 스타일링 하는 두 가지 방식을 사용한다. 먼저, 각 위젯은 디폴트로, project-widget 형식의 CSS 클래스 네임을 갖는다. 예를 들어, gwt-Button 및 gwt-RadioButton은 중심적인 GWT 위젯의 클래스 네임들이다. 일반적으로 내포된 테이블이 뒤죽박죽 모인 타입으로 패널을 구현한다. 패널은 디폴트 클래스네임을 가지지 않는다.

디폴트 위젯-형식 당 클래스 네임 방식으로 애플리케이션 전체에 균일하게 여러 위젯에 이름을 붙이기 용이해진다. 물론 정상적인 CSS 선택자 규칙이 적용되고, 이 규칙을 이용해, 여러 가지 다른 스타일을 애플리케이션 컨텍스트에 따라 동일한 위젯 타입에 적용할 수 있다. 더 높은 가변성을 위해, 위젯의 setStyleName() 및 addStyleName() 메소드를 불러와 특정적으로, 위젯의 디폴트 클래스네임을 바꾸거나 확대한다.

Listing 3은 이와 같은 방식들을 결합해 여러 스타일을 Weather Reporter 애플리케이션의 입력 패널에 적용시키는 방식을 보여주고 있다. weather-input-panel 클래스 네임은inputPanel.setStyleName("weather-input-panel");에 대한 호출을 통해, Weather.java에서 형성된다.


Listing 3. CSS 스타일을 Weather Reporter 입력 패널에 적용하기
                

/* Style the input panel itself */
.weather-input-panel {
   background-color: #AACCFF;
   border: 2px solid #3366CC;
   font-weight: bold;
}

/* Apply padding to every element within the input panel */
.weather-input-panel * {
   padding: 3px;
}

/* Override the default button style */
.gwt-Button {
   background-color: #3366CC;
   color: white;
   font-weight: bold;
   border: 1px solid #AACCFF;
}

/* Apply a hover effect to the button */
.gwt-Button:hover {
   background-color: #FF0084;
}

그림 3에서는 이와 같은 스타일이 적합한 상태에서 다시 Weather Reporter 애플리케이션에 대해 나와 있다.


그림 3. 스타일을 적용한 상태에서의 입력 패널
Input panel with styles applied 

클라이언트 측 기능 추가하기

애플리케이션의 기본 레이아웃 및 스타일링 기능이 행해졌으므로, 필자는 몇 가지 클라이언트 측 기능을 시작한다. 친숙한 리스너 패턴을 사용해 GWT에서의 이벤트 핸들링을 수행한다. GWT는 부가된 편의 기능의 몇 가지 어댑터 및 헬퍼-클래스 뿐만 아니라 마우스 이벤트, 키보드 이벤트 및 변경 이벤트 등의 리스너(Listener) 인터페이스를 제공한다.

일반적으로, Swing 프로그래머에게 친숙한 익명 내부-클래스 이디엄을 이용해 이벤트 리스너를 추가한다. 하지만 모든 GWT 리스너(Listener)의 첫 번째 매개변수는 이벤트 센더로 사용자가 대화하는 위젯이다. 이는 필요한 경우, 동일한 리스너(Listener) 인스턴스를 다중 위젯에 부가하고 센더 매개변수를 이용해 이벤트를 보내는 위젯이 어떤 것인지 결정한다는 것을 의미한다.

Listing 4는 Weather Reporter 애플리케이션에서의 두 가지 이벤트 리스너에 대한 구현을 나타낸다. 클릭 핸들러는 전송(Submit) 버튼에, 키핸들러는 TextBox에 각각 부가된다. TextBox를 중점적으로 볼 경우, 전송(Submit) 버튼을 클릭하거나, 엔터 키를 누르면 관련 핸들러에서 사설 validateAndSubmit() 메소드를 불러온다. Listing 4에 있는 코드뿐만 아니라, txBox 및 ucRadio는 Weather 클래스의 인스턴스 멤버가 되면서 확인 방법으로 처리된다.


Listing 4. 클라이언트 측 기능 추가하기
                
// Create Submit button, with click listener inner class attached
Button btn = new Button("Submit", new ClickListener() {

   public void onClick(Widget sender) {
      validateAndSubmit();
   }
});

// For usability, also submit data when the user hits Enter 
// when the textbox has focus
txBox.addKeyboardListener(new KeyboardListenerAdapter(){

   public void onKeyPress(Widget sender, char keyCode, int modifiers) {

      // Check for Enter key
      if ((keyCode == 13) && (modifiers == 0)) {
         validateAndSubmit();
      }        
   }      
});    

Listing 5는 validateAndSubmit() 메소드 구현을 보여준다. 이 구현 과정은 상당히 단순하며, 확인 로직을 요약한 ZipCodeValidator 클래스에 따라 다르다. 사용자가 유효한 다섯 글자 ZIP 코드를 입력하지 않을 경우, validateAndSubmit() 메소드는 Window.alert()에 대한 호출로 GWT 세계에 표현된 경보 박스에 있는 에러 메시지를 나타낸다. ZIP 코드가 유효한 경우, ZIP 코드 및 ℃/℉가운데 사용자의 선택 단위 등이 fetchWeatherHtml() 메소드를 통과한다. 이에 대해선 나중에 다루기로 하겠다.


Listing 5. validateAndSubmit 로직
                
private void validateAndSubmit() {

   // Trim whitespace from input
   String zip = txBox.getText().trim();

   if (!zipValidator.isValid(zip)) {
     Window.alert("Zip-code must have 5 digits");
     return;
   }

   // Disable the TextBox
   txBox.setEnabled(false);

   // Get choice of celsius/fahrenheit
   boolean celsius = ucRadio.isChecked();
   fetchWeatherHtml(zip, celsius);
}




위로


GWT 쉘로 클라이언트 측 디버깅하기

GWT 쉘이 자바 IDE에 있는 클라이언트 측 코드를 디버그 하도록 해주는 JVM 후크를 가진다는 사실을 언급하기 위해, 잠깐 주제에서 벗어난 얘기를 하겠다. 여러분은 웹 UI와 대화하고 클라이언트에서 실행되는 해당 JavaScript를 나타내는 자바 코드를 알 수 있다. 클라이언트 측에 생성되는 생성 JavaScript를 디버깅하는 것은 기본적으로 성공할 가망이 없기 때문에 이와 같은 기능은 상당히 중요한 기능이다.

Eclipse 디버그 태스크를 구성해 com.google.gwt.dev.GWTShell 클래스를 통해 GWT 쉘을 실행하는 것은 쉽다. 그림 4는 전송(Submit) 버튼을 클릭하는 과정 후에 validateAndSubmit() 메소드에 있는 중지점에서 정지된 Eclipse를 보여준다.


그림 4. 클라이언트 측 GWT 코드를 디버깅하는 Eclipse
 



위로


서버측 컴포넌트와의 통신

이제, Weather Reporter 애플리케이션에서 사용자 입력 정보를 수집해 정보를 확인했다. 서버에서 데이터를 꺼내오는 과정이 그 다음 단계다. 정상적인 Ajax 개발 과정에 있어, 이 단계는 JavaScript로부터 서버측 리소스를 불러오고 JavaScript 객체 표기법(JSON) 또는 XML로 코드화된 데이터를 다시 받는 과정을 수반한다. GWT는 자체의 원격 프로시저 호출 (RPC) 메커니즘 뒤에 있는 이와 같은 대화 과정을 추상화한다.

GWT 용어 측면에서 보면 클라이언트 는 웹 서버 상에서 실행되는 서비스와 대화한다. 이런 서비스를 나타내는 데 사용되는 RPC 메커니즘은 자바 RMI에서 사용하는 방식과 비슷하다. 이는 서비스 및 몇 가지 인터페이스의 서버측 구현 프로세스를 작성하기만 하면 된다는 것을 의미한다. 코드 생성 및 리플렉션 프로세스는 클라이언트 스텁 및 서버측 골격 프록시를 처리한다.

이에 따라 첫 번째 단계는 Weather Reporter 서비스에 대한 인터페이스를 정의하는 것이다. 이 인터페이스는 GWT RemoteService인터페이스를 확장시켜야 하고, GWT 클라이언트 코드에 나타나야 하는 서비스 메소드의 서명을 포함한다. GWT에서의 RPC 호출은 JavaScript 코드 및 자바 코드 사이에서 행해지기 때문에 GWT는 객체-직렬화 메커니즘을 도입해 언어 분할 (직렬화 가능 타입 사이드 바를 참조) 전체에 걸쳐 인수를 조정하고 값을 반환한다.

직렬 타입

GWT하에서 직렬 타입에 대해 간략히 요약하면 다음과 같다.:

  • 프리머티브 (int와 같은) 및 원시 래퍼 클래스(정수등)는 직렬화 가능하다.
  • 문자열(String) 및 날짜(Date)는 직렬화 가능하다.
  • 직렬화 타입의 배열은 그 자체로 직렬화 가능하다.
  • 사용자 정의 클래스는 모든 클래스의 상주 멤버가 직렬화 가능하고 GWT의 IsSerializable 인터페이스를 구현할 경우에 직렬화 가능하다.
  • Collection 클래스는 자체에서 포함된 직렬화 타입을 서술하는 Javadoc 주석과 같이 사용된다.

어쨌든 클라이언트 코드는 GWT에서 수행되는 소규모의 자바 클래스 하위 세트에 제한되어 있기 때문에 이와 같은 직렬화 가능 타입은 상당히 포괄적인 적용 범위를 제공한다.

서비스 인터페이스를 정의하면, 이 인터페이스를 GWT의 RemoteServiceServlet 클래스를 확장하는 클래스에 구현하는 과정이 그 다음 단계다. 이름에서도 암시하듯, RemoteServiceServlet 클래스는 자바 언어의 HttpServlet을 특수화시킨 것으로, 임의의 servlet 상자에서 호스팅 된다.

여기서 언급될 만한 가치가 있는 GWT의 특성 중 하나는 서비스의 원격 인터페이스는 애플리케이션의 client패키지에 반드시 있어야 한다는 것이다. JavaScript 생성 과정에서 서비스 원격 인터페이스가 들어가야 하기 때문이다. 하지만 서버측 구현 클래스는 원격 인터페이스를 기준으로 하기 때문에, 자바 컴파일 타임 의존성은 서버측 코드 및 클라이언트 코드 사이에 존재한다. 이에 대한 필자의 솔루션은 원격 인터페이스를 client의 공통 하위 패키지 안으로 넣는 것이다. 그리고 난 뒤, 필자는 자바에서 공통 하위 패키지를 포함시키고, 나머지 client 패키지를 제외시킨다. 이렇게 하면, 클래스 파일이 JavaScript로만 변환되어야만 하는 클라이언트 코드에서 생성되는 것을 방지한다. 더 좋은 솔루션은 클라이언트 측 및 서버측 코드에 관한 두 가지 소스 디렉토리 전체에 걸친 패키지 구조를 분할한 뒤, 공통 클래스를 두 디렉토리로 복사하는 것이다.

Listing 6에서는 Weather Reporter 애플리케이션: WeatherService에서 사용되는 원격 서비스 인터페이스가 나타나 있다. 이 인터페이스는 입력 정보로 ZIP 코드 및 ℃/℉ 플래그를 취하고 HTML weather 설명을 포함하는 문자열을 반환한다. Listing 6에서는 또한 YahooWeatherServiceImpl의 개요를 보여준다. 이 개요는 Yahoo! weather API를 이용해 주어진 ZIP 코드에 대한 RSS weather정보를 얻고 그 정보로부터 HTML 설명을 뽑아낸다.


Listing 6. 원격 WeatherService(weather서비스) 인터페이스 및 부분적 구현
                
public interface WeatherService extends RemoteService {

   /**
    * Return HTML description of weather
    * @param zip zipcode to fetch weather for
    * @param isCelsius true to fetch temperatures in celsius, 
    * false for fahrenheit
    * @return HTML description of weather for zipcode area
    */
   public String getWeatherHtml(String zip, boolean isCelsius) 
      throws WeatherException;
} 

public class YahooWeatherServiceImpl extends RemoteServiceServlet
   implements WeatherService {

   /**
    * Return HTML description of weather
    * @param zip zipcode to fetch weather for
    * @param isCelsius true to fetch temperatures in celsius, 
    * false for fahrenheit
    * @return HTML description of weather for zipcode area
    */
   public String getWeatherHtml(String zip, boolean isCelsius) 
      throws WeatherException {

     // Clever programming goes here
   }
}

이 시점에서 표준 RMI 방식에서 벗어나기 시작한다. JavaScript로부터의 Ajax호출은 비동기식이므로, 서비스를 호출하기 위해 클라이언트 코드에서 사용하는 비동기식 인터페이스를 정의하는 부가적인 작업이 필요하다. 비동기식 인터페이스 메소드 서명은 원격 인터페이스 서명과는 다르기 때문에 GWT는 Magical Coincidental Naming에 의존한다. 즉, 비동기식 인터페이스 및 원격 인터페이스 사이에 정적인 컴파일-타임 관계가 존재하지 않는다. 하지만 GWT는 명명 협약을 통해 컴파일-타임 관계를 인지한다. Listing 7은 WeatherService에 대한 비동기식 인터페이스를 나타내고 있다.


Listing 7. WeatherService(weather서비스)에 대한 비동기식 인터페이스
                
public interface WeatherServiceAsync {

   /**
    * Fetch HTML description of weather, pass to callback
    * @param zip zipcode to fetch weather for
    * @param isCelsius true to fetch temperatures in celsius,
    * false for fahrenheit
    * @param callback Weather HTML will be passed to this callback handler
    */
   public void getWeatherHtml(String zip, boolean isCelsius, 
      AsyncCallback callback);
}

알다시피, MyServiceAsync라 불리는 인터페이스를 생성하고, 각 메소드 서명에 대한 복사물을 제공한 뒤, 반환 (return) 타입을 제거하고, AsyncCallback타입에 관한 부가적 매개변수를 추가하는 것이 일반적인 개념이다. 비동기식 인터페이스는 원격 인터페이스와 같은 패키지에 존재해야 한다. AsyncCallback 클래스는 onSuccess() 및 onFailure() 등의 두 가지 메소드가 있다. 서비스에 관한 호출이 성공일 경우, 서비스 호출에 대한 반환 값으로 onSuccess()를 호출한다. 원격 호출이 실패할 경우, onFailure()를 호출하고, 서비스에 의해 생성되는 Throwable 타입이 통과되면서 오류 근원이 나타나게 된다.




위로


클라이언트로부터 서비스 호출하기

WeatherService 및 WeatherServiceAsync의 비동기식 인터페이스가 적절한 상태에서 필자는 이제 Weather Reporter 클라이언트를 호출해 서비스를 호출하고 서비스에서 나온 응답을 다룬다. 이에 대한 첫 번째 단계는 단순히 반복 사용 설정 코드에 관한 것이다. 반복 사용 설정 코드는 GWT.create(WeatherService.class)를 불러내고 GWT.create(WeatherService.class)에서 반환하는 객체를 다운 캐스팅 해 weather 클라이언트가 사용하는 WeatherServiceAsync의 인스턴스를 생성한다. 그 다음 단계로, WeatherServiceAsync는 ServiceDefTarget에 캐스트 해 ServiceDefTarget상에setServiceEntryPoint()를 불러올 수 있도록 해야 한다. setServiceEntryPoint()는 자체 해당 원격 서비스 구현 프로세스가 전개되는 URL에서의 WeatherServiceAsync 스텁을 가리킨다. 반복 사용 설정 코드는 효과적으로 컴파일 타임에서 하드 코드화된다. 이 코드는 웹 브라우저에서 전개되는 자바코드로 되기 때문에 런타임에서의 속성 파일에서부터 위 URL을 찾을 방법이 없어 확실히 컴파일 된 GWT 웹 애플리케이션의 이식성을 제한한다.

Listing 8에서는 WeatherServiceAsync 객체의 설정에 대해 나와 있으며 필자가 이전에 언급했던 fetchWeatherHtm()의 구현에 대해서도 나와있다. (클라이언트 측 기능 추가하기 참조)


Listing 8. RPC를 사용해 원격 서비스 호출하기
                
// Statically configure RPC service
private static WeatherServiceAsync ws = 
   (WeatherServiceAsync) GWT.create(WeatherService.class);
static {
   ((ServiceDefTarget) ws).setServiceEntryPoint("ws");
}

/**
 * Asynchronously call the weather service and display results
 */
private void fetchWeatherHtml(String zip, boolean isCelsius) {

   // Hide existing weather report
   hideHtml();

   // Call remote service and define callback behavior
   ws.getWeatherHtml(zip, isCelsius, new AsyncCallback() {
      public void onSuccess(Object result) {

         String html = (String) result;

         // Show new weather report
         displayHtml(html);
      }

      public void onFailure(Throwable caught) {
         Window.alert("Error: " + caught.getMessage());
         txBox.setEnabled(true);
       }
   });
}

서비스의 getWeatherHtml()에 대한 실질적인 호출 과정은 구현하기 쉽다. 익명 콜백 핸들러 클래스는 단순히 서버에서 나온 응답을 서비스의 getWeatherHtml()를 나타내는 메소드에 전한다.

그림 5는 Yahoo! weather API에서 가져온 날씨 정보를 나타내는 실행 중인 애플리케이션에 대해 나와있다.


그림 5. Yahoo!에서 가져온 리포트를 나타내는 Weather Reporter 애플리케이션
 



위로


서버측 확인에 관한 필요성

GWT에서 클라이언트 측 코드 및 서버측 코드를 융합하는 것은 본질적으로 위험하다. GWT의 추상화 과정으로 클라이언트/서버 분할을 감춘 상태에서 자바 언어로 모든 프로그램을 작성하기 때문에, 클라이언트 측 코드를 런타임 시 신뢰할 수 있다고 착각하기 쉽다. 하지만 그런 생각은 잘못된 것이다. 웹 브라우저에서 실행하는 임의의 코드는 악의적인 사용자에 의해 변조되거나 완전히 우회될 가능성이 있다. GWT는 이런 문제를 줄일 수 있도록 악의적인 사용자들에게 고도로 혼란을 주는 기능을 제공한다. 하지만 GWT 클라이언트 및 클라이언트 서비스 사이를 이동하는 임의의 HTTP 트래픽 등의 2차 공격 시점은 여전히 존재한다.

여기서 필자가 Weather Reporter 애플리케이션의 약점을 이용하는 공격자라고 가정해 보자. 그림 6은 Weather Reporter 클라이언트에서 서버 상에서 실행하는 WeatherService까지 전송되는 요청을 가로채는 Microsoft의 Fiddler 툴에 대해 나와있다. 일단 요청을 가로채면 Fiddler 툴은 요청의 일부를 변경하도록 한다. 그림 6에서 강조된 텍스트를 통해 필자는 필자가 지정한 ZIP 코드가 요청 내에서 코드화된 곳을 발견했다는 사실을 보여주고 있다. 이 요청을 필자가 좋아하는 코드로 변경할 수 있다. 예를 들면 "10001"에서 "XXXXX"로 변경하는 것 등이다.


그림 6. Fiddler를 이용해 클라이언트 측 확인 과정 우회하기
 

이제, YahooWeatherServiceImpl에 있는 고유 서버측 코드에서 ZIP 코드상에 있는 Integer.parseInt()를 불러온다고 가정하자. 결국에, ZIP 코드는 Weather's validateAndSubmit()로 통합된 확인 점검 기능을 우회해 통과한 것이 틀림없다. 그런가? 보다시피, 확인 점검 기능은 와해되고, NumberFormatException은 폐지된다.

이 경우 끔찍한 일이 발생되지 않고, 공격자는 클라이언트에서 에러 메시지를 받게 된다. 하지만 더 민감한 데이터를 다루는 GWT 애플리케이션에서 여전히 공격 당할 가능성은 존재한다. ZIP코드를 주문 추적 애플리케이션에서의 고객 ID번호라 가정해 보자. ZIP코드를 가로채 변경하면 다른 고객에 대한 민감한 재정 정보가 나오게 된다. 데이터베이스 쿼리에서 값을 사용하는 임의의 장소에서 이와 같은 동일한 방식을 사용하면 SQL 삽입 공격에 대한 가능성이 존재한다.

예전에 Ajax 애플리케이션으로 작업한 사람들이 이런 공격을 간단하게 하도록 내버려둬선 안된다. 서버 측의 입력 값을 재확인해 임의의 입력 값을 이중 점검하는 작업을 해야 한다, GWT 애플리케이션에서 작성하는 자바 코드는 런타임에서 본질적으로 신뢰할 만한 것이 아니라는 사실을 기억하는 것이 중요하다. 하지만 GWT는 장점도 있다. Weather Reporter 애플리케이션에서, 필자는 이미 클라이언트상에서 사용하기 위한 ZipCodeValidator를 작성해 ZipcodeValidator를 단순히 필자의 client.common 패키지로 이동시켜 서버 측에서 동일한 확인 과정을 다시 재사용했다. Listing 9에서는 YahooWeatherServiceImpl로 통합된 점검 기능에 대해 나와있다.


Listing 9. YahooWeatherServiceImpl로 통합된 ZipcodeValidator
                
public String getWeatherHtml(String zip, boolean isCelsius) 
       throws WeatherException {

   if (!new ZipCodeValidator().isValid(zip)) {
      log.warn("Invalid zipcode: "+zip);
      throw new WeatherException("Zip-code must have 5 digits");
   }




위로


JSNI로 고유 JavaScript 불러오기

웹 애플리케이션에서 시각 효과 라이브러리가 점점 인기를 얻고 있다. 이 라이브러리 효과는 미묘한 사용자-상호작용 큐를 제공하거나 단순히 폴리시 기능을 추가하기 때문이다. 필자는 Weather Reporter 애플리케이션에 몇 가지 아이-캔디 기능을 추가하고 싶다. GWT는 이와 같은 타입의 기능을 제공하지 않지만 GWT의 JavaScript Native Interface(JSNI)는 이 기능에 대한 솔루션을 제공한다. JSNI로 GWT 클라이언트 자바 코드로부터 JavaScript를 호출하게 된다. 이는 예를 들어, 필자가 Scriptaculous 라이브러리 (참고자료)나 Yahoo! 사용자 인터페이스 라이브러리로부터 효과를 이용할 수 있다는 것을 의미한다.

JSNI는 특수 주석 블록에 포함된 자바 언어의 native(고유)키워드와 JavaScript를 조합한 타입을 사용한다. 이는 아마도 예를 통해 설명할 수 있을 것이다. Listing 10에서는 Element 상에 주어진 Scriptaculous 효과를 호출하는 메소드에 대해 나와있다.


Listing 10. JSNI로 Scriptaculous 효과 호출하기
                
/**
 * Publishes HTML to the weather display pane
 */
private void displayHtml(String html) {
   weatherHtml.setHTML(html);
   applyEffect(weatherHtml.getElement(), "Appear");
}

/**
 * Applies a Scriptaculous effect to an element
 * @param element The element to reveal
 */
private native void applyEffect(Element element, String effectName) /*-{

   // Trigger named Scriptaculous effect
   $wnd.Effect[effectName](element);
}-*/;

이것은 컴파일러가 private native void applyEffect(Element element, String effectName); 만을 보기 때문에 완벽히 유효한 자바 코드이다. GWT는 주석 블록의 내용을 파싱하고 JavaScript를 출력한다. GWT는 window와 document 객체를 칭하는 $wnd 와 $doc 변수를 제공한다. 이 경우, 나는 상위 레벨에 있는 Effect 객체에 액세스 하고 JavaScript의 대괄호를 사용하여 콜러가 지정한 네임드 함수를 호출한다. Element 유형은 GWT에서 제공하는 마법과 같은 타입으로서 Widget의 기반 HTML DOM 엘리먼트를 자바와 JavaScript 코드로 나타낸다. String은 자바 코드와 JavaScript간 JSNI를 통해서 투명하게 전달될 수 있는 타입 중 하나이다.

이제 필자는 서버로부터 데이터를 반환할 시, 뚜렷이 나오는 날씨 정보를 가지게 되었다. 마지막으로 효과 종료 시 ZIP 코드인 TextBox를 재설정한다. Scriptaculous 효과는 비동기식 콜백 메커니즘을 사용해 리스너에 효과의 생명 주기에 관한 내용을 통보한다. 필자는 필자의 GWT 클라이언트 자바 코드 안으로 다시 JavaScript를 호출해야 하기 때문에, 여기서 상황이 조금 복잡해진다. JavaScript에서는 임의의 개수가 있는 인수들로 임의의 함수를 호출한다. 따라서 자바-스타일 메소드 과 부하는 존재하지 않는다. 이는 JSNI가 자바 메소드를 참조할 다루기 힘든 구문을 사용해 발생 가능한 과부하를 명확하게 해야 한다는 것을 의미한다. GWT 문서에서는 이 구문을 다음과 같이 기술한다.

[instance-expr.]@class-name::method-name(param-signature)(arguments)


instance-expr. 부분은 일종의 옵션이다. 객체 레퍼런스에 대한 필요 없이 정적 메소드를 호출해야 하기 때문이다. 또 다시, Listing 11에서 보면 예에서 설명된 효과 메소드를 알기가 가장 쉽다.


Listing 11. JSNI로 자바 코드 안으로 다시 호출하기
                
/**
 * Applies a Scriptaculous effect to an element
 * @param element The element to reveal
 */
private native void applyEffect(Element element, String effectName) /*-{

  // Keep reference to self for use inside closure
  var weather = this;

  // Trigger named Scriptaculous effect
  $wnd.Effect[effectName](element, { 
     afterFinish : function () {

     // Make call back to Weather object
     weather.@developerworks.gwt.weather.client.Weather::effectFinished()();
     } 
  });
}-*/;

/**
 * Callback triggered when a Scriptaculous effect finishes.
 * Re-enables the input textbox.
 */
private void effectFinished() {
  this.txBox.setEnabled(true);
  this.txBox.setFocus(true);
}

applyEffect() 메소드를 변경해 여분의 afterFinish 인수를 Scriptaculous 효과로 전송한다. afterFinish의 값은 효과를 실행할 때 호출되는 익명 함수다. 이 함수는 GWT의 이벤트 핸들러에 의해 사용되는 익명 내부-클래스 이디엄과 뭔가 비슷하다. 호출할 Weather 객체의 인스턴스서부터 시작해, Weather 클래스의 정식 이름, 호출할 함수의 명칭까지 지정해 실질적으로 자바 코드 안으로 다시 호출한다. 괄호의 첫 번째 공란은 인수가 없는 effectFinished()라는 명칭의 메소드를 필자가 불러들이고 싶다는 것을 의미한다. 두 번째 괄호 세트는 함수를 불러들인다.

여기서 로컬 변수인 weather에서 this reference. Because of the way JavaScript call semantics operate, the this 레퍼런스 사본을 보유하고 있다는 사실이 이상하다. JavaScript 호출 의미가 작동되는 방식 때문에 afterFinish 함수 내의 this 변수는 실제로 Scriptaculous 객체다. Scriptaculous객체에서 그 함수를 불러들이기 때문이다. 클로저 외부의 this 레퍼런스 사본을 만드는 것은 단순한 대안이다.

여기서 필자는 몇 가지 JSNI 기능에 대해 알아보았다. 필자는 Scriptaculous 효과 기능을 주문 GWT 위젯으로 요약하는 방식이 Scriptaculous 효과를 GWT로 통합하는 더 좋은 방식이라는 것을 지적하고 싶다. 이 방식이 Alexei Sokolov가 GWT 컴포넌트 라이브러리에서 행했던 바로 그 방식이다. (참고자료)

이제 필자는 Weather Reporter 애플리케이션에 대해 다 얘기했으므로, GWT로 웹 개발 시 몇 가지 장점 및 단점에 대해 설명하겠다.




위로


GWT를 사용하는 이유?

예상한 바와는 달리, GWT 애플리케이션은 이상하게도 웹과 유사하지 않다. GWT는 기본적으로 경량 GUI 애플리케이션에 관한 런타임 환경으로 브라우저를 이용하기 때문에 정상적인 웹 애플리케이션 보다는 Morfik, OpenLaszlo 또는 심지어 Flash로 개발한 것과 훨씬 비슷한 결과가 나온다. 따라서, GWT는 단일 페이지 상에 풍부한 Ajax GUI로 존재하는 웹 애플리케이션에 가장 잘 맞는다. GWT 애플리케이션이 구글 캘린더 및 스트레드시트 애플리케이션과 같은 구글의 베타 릴리스의 일부를 특성화시키기 때문에, 우연의 일치는 아닌 듯 하다. GWT 애플리케이션은 대단하지만 이를 이용해 모든 비즈니스 상황을 해결할 수는 없다. 대부분의 웹 애플리케이션은 페이지 중심 모델에 완벽히 들어맞기 때문이다. Ajax는 필요한 경우, 더 풍부한 대화형 방식을 사용한다. GWT는 전통 페이지-중심 애플리케이션과 잘 들어맞지 않는다. GWT 위젯과 정상 HTML 타입의 입력 자료를 결합시키는 게 가능하지만 GWT 위젯 상태는 나머지 페이지와는 다르다. 예를 들어, GWT Tree 위젯으로부터 선택된 값을 정규 형식의 일부로 전송하는 간단한 방법이 없다.

라이센싱

GWT의 런타임 라이브러리는 Apache 라이센스 2.0에 따라 허가된다. GWT를 자유롭게 사용해 상업적 애플리케이션을 생성한다. 하지만, GWT 툴 체인은 오로지 이진 타입으로 제공되고 툴 체인 변경은 금지된다. 툴 체인은 자바-대-JavaScript 컴파일러를 포함한다. 이는 생성된 JavaScript에서 발생한 에러는 통제 불능임을 의미한다. GWT가 사용자-에이전트 탐지 기능에 의존한다는 것도 특수한 문제다. 새 브라우저의 각 릴리스에는 지원 기능을 제공하는 GWT 툴킷에 대한 업데이트가 반드시 필요하다.

GWT를 J2EE 애플리케이션 환경에서 사용하기로 결정한 경우, GWT 디자인은 통합 기능을 상대적으로 간단해야 한다. 이 상황에서 GWT 서비스는 단순히 웹 요청을 백 엔드 상의 비즈니스-로직 호출로 위임하는 얇은 중간층인 Struts에 있는 Action와 비슷한 것으로 여겨진다. GWT 서비스는 단순히 HTTP 서블릿이기 때문에 Struts 또는 SpringMVC로 쉽게 통합되고, 인증 필터 뒤에 위치한다.

하지만, GWT는 몇 가지 중대한 결점을 가지고 있다. 우선 첫 번째는 점진적인 성능 저하에 대한 대책이 없다는 것이다. 최근의 웹 애플리케이션 개발에 있어 가장 좋은 훈련은 JavaScript 없이 작동하는 페이지를 생성하는 것이다. 장식하는 데 유용한 곳에 JavaScript를 사용한 다음 여기에 여분의 기능을 추가하는 것이다. GWT에서 JavaScript를 이용할 수 없는 경우, UI를 얻지 못하게 된다. 웹 애플리케이션의 일정 클래스의 경우에 이런 상황은 곧장 거래를 불가능하게 만드는 요건이 된다. 국제화도 GWT의 주요 문제 중 하나다. GWT 클라이언트 자바 클래스는 브라우저에서 실행되기 때문에, 이 클래스는 런타임 시 로컬화 된 문자열을 얻는 속성 또는 리소스 번들에 대한 접근 기능이 없다. 각 로케일(참고자료)에 대해 생성되는 각 클라이언트 측 클래스의 하위 클래스를 요구하는 복잡한 대안이 유용하다. 하지만 GWT 엔지니어는 좀 더 실행 가능한 솔루션에서 작업한다.

코드 생성에 관한 경우

아마도 GWT 아키텍처에서 이론의 여지가 있는 문제는 클라이언트 측에 대한 자바 언어로의 전환일 것이다. 자바 언어로 클라이언트 측을 작성하는 일이 본질적으로 JavaScript 작성보다 더 낫다고 제시하는 GWT 주창자들이 있다. 모든 사람들이 이런 관점에 다 공감하는 것은 아니다. 때로는 자바 개발 작업이 성가신 업무라 자바 언어의 가변성 및 표현성을 없애는 것을 상당히 주저하는 JavaScript 코더들이 많다. 경험이 있는 웹 개발자가 부족한 팀의 경우, 자바 코드에서 JavaScript로의 교체는 설득력을 얻는 상황이 된다. 하지만 그 팀이 Ajax 개발 프로젝트로 옮길 경우, 자바 코더들에게 의뢰해 전용 툴을 이용해 까다로운 JavaScript를 생성하는 것보다는 숙련된 JavaScript 프로그래머들을 고용하는 게 더 나을지도 모른다. GWT가 JavaScript, HTTP 및 HTML로 확장하는 추상화 과정에서의 누설로 인해 필연적으로 버그가 생성되고, 경험이 없는 웹 프로그래머들은 버그를 추적하느라 애를 먹는다. 개발자이자 블로거인 Dimitri Glazkov는 다음과 같이 말했다. "JavaScript를 다룰 수 없으면 웹 애플리케이션에 대한 코드를 작성하지 말아야 한다. HTTL, CSS 및 JavaScript는 이런 조류에 대한 필요 충분조건이다." (참고자료)

정적 타이핑 및 컴파일-타임 점검으로 인해 자바 코딩이 본질적으로 JavaScript 프로그래밍보다 오류에 덜 취약하다고 주장하는 사람들도 있다. 이는 상당히 불합리한 주장이다. 어떤 언어라도 나쁜 코드를 작성하는 일은 가능하다. 버그가 있는 자바 애플리케이션은 이를 잘 증명해주고 있다. 또한 GWT의 버그 없는 코드 생성에 의존할 수도 있다. 하지만 오프라인 구문-점검 과정 및 클라이언트 측 코드의 확인 작업은 분명 이로울 수 있다. 이와 같은 것은 Douglas Crockford의 JSLint (참고자료)의 타입으로 있는 JavaScript에서 가능하다. GWT는 단위 테스팅 기능면에서 우세하며 클라이언트 측 코드에 대한 JUnit 통합 기능을 제공한다. 단위 테스팅 지원 기능은 여전히 JavaScript에서 부족한 영역이다.

Weather Reporter 애플리케이션을 개발하는 데 있어 필자가 클라이언트 측 코드에서 가장 매력적으로 본 것은 두 층 사이에 동일한 확인 클래스를 공유하는 기능이었다. 이 기능으로 인해 분명 애플리케이션 개발 노력을 줄여준다. RPC 전체에 전송된 임의의 클래스인 경우에도 같은 상황이 적용된다. 이때, 클래스들을 코드화하기만 하면 된다. 그러면 클라이언트 측 코드 및 서버측 코드 둘 다 클래스를 이용할 수 있다. 하지만 불행히도, 이런 추상화 법칙은 새기 쉽다. 예를 들어, 필자의 ZIP 코드 밸리데이터에서, 필자는 정규식을 사용해 점검 기능을 수행했으면 했다. 하지만 GWT는String.match() 메소드를 구현하지 않는다. 심지어 구현했다 하더라도, GWT에서의 정규식은 클라이언트 및 서버 코드로 전개했을 때 구문적 차이를 보인다. 이는 호스트 환경의 기초적인 regexp 메커니즘으로 인해 생기는 것이며, 불완전한 추상화로 생기는 문제를 보여주는 예를 나타낸다.

GWT에서 얻는 한 가지 큰 장점은 자체의 RPC 메커니즘 및 자바 코드 및 JavaScript 간의 객체의 고유 직렬화다. 이로 인해 일상적인 Ajax 애플리케이션에서 보는 수많은 작업들이 줄어든다. 하지만 전례가 없었던 건 아니다. 나머지 GWT 없이 이와 같은 기능을 원한다면, 자바 코드에서 JavaScript까지 객체를 동원하는 RPC를 제공하는 Direct Web Remoting은 고려해 볼만한 가치가 있다 (참고자료)

GWT는 또한 크로스-브라우저 비호환성, DOM 이벤트 모델 및 Ajax 호출하기 등 Ajax 애플리케이션 개발에 있어 몇 가지 최하위 기능을 추상화하는 좋은 기능이 있다. 하지만 Yahoo!, UI 라이브러리, Dojo, 및 MochiKit등 최근의 JavaScript 툴킷은 전부 코드 생성에 대한 필요성 없이도 비슷한 수준의 추상화 기능을 제공한다. 게다가 이런 모든 툴킷은 오픈 소스다. 따라서 이런 툴킷을 필요에 맞게 이용하거나 발생되는 버그를 수정할 수 있다. GWT의 블랙박스에서는 이런 일이 불가능하다. (라이센싱 사이드 바 참조)




위로


결론

소셜 북마크

mar.gar.in mar.gar.in
digg Digg
del.icio.us del.icio.us
Slashdot Slashdot

GWT는 수많은 기능을 제공하는 광범위한 프레임웍이다. 하지만 GWT는 모 아니면 도 방식이 짙어 웹 애플리케이션 개발 시장에서 상당히 작은 영역에 국한되어 있다. GWT에 대한 간략한 소개의 글로 GWT의 기능 및 한계를 느꼈으리라 생각된다. 이 글이 모든 사람의 필요를 만족시킬 수는 없겠지만, GWT는 Ajax 애플리케이션을 설계할 시 엔지니어링 분야의 큰 업적이다. GWT에 관해선 필자가 설명한 것보다 훨씬 더 많은 영역이 있다. 따라서 GWT에 대해 자세한 내용을 원하면 구글 문서를 참조하거나 GWT 개발자 포럼 토론에 참석하기를 권한다. (참고자료)

기사의 원문보기





위로


다운로드 하십시오

설명이름크기다운로드 방식
GWT Weather Reporter applicationj-ajax4-gwt-weather.zip 2.1KB HTTP
다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

토론


필자소개

Philip McCarthy는 자바 및 웹 기술을 전공한 소프트웨어 개발 컨설턴트다. 그는 휴렛 팩커드 연구실 및 오랜지 사에서 디지털 미디어 및 텔레콤 분야에 종사해 왔고 현재는 영국 런던에서 재정 소프트웨어 분야에서 연구하고 있다.