IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  웹 개발  >

전문가다운 Ajax 애플리케이션 개발, Part 3: DWR, 자바, Dojo 툴킷을 사용하여 자바와 자바스크립트 통합하기

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

샘플 코드

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Michael Galpin, 개발자, eBay

옮긴이: 장동수 dwkorea@kr.ibm.com

2008 년 10 월 21 일

지금 바로 이름이 떠오르는 자바(Java™) 웹 개발 프레임워크, 라이브러리, 툴킷만 해도 엄청나게 많습니다. 뭐가 뭔지, 어떤 것이 문제 해결에 도움이 될지 알아내는 것만 해도 버거운 일이죠. 그러나 Ajax 개발을 하고 있다면 반드시 알아두어야 할 라이브러리가 바로 DWR(Direct Web Remoting)입니다. 이 라이브러리는 자바 언어와 자바의 웹 기술을 사용하여 Ajax 개발을 더 쉽게 해줍니다. 이 라이브러리는 Ajax와 자바 웹 애플리케이션을 긴밀하게 통합하는 방법에 있어 기준을 제시합니다. DWR은 Ajax 기술들의 광범위한 연합인 Dojo 재단에 합류했습니다. 이 기사에서는 DWR을 사용하면 Ajax가 얼마나 간단해지는지 알아보겠습니다.

이 글은 Ajax 애플리케이션 제작에 사용할 수 있는 인기있는 자바스크립트 라이브러리들에 대해 알아보는 연재의 세 번째이자 마지막 기사다. Part 1에서는 Prototype 라이브러리를 사용하여 노래를 관리하는 웹 애플리케이션을 만들어 보았다. Part 2에서는 script.aculo.us를 사용하여 사진을 관리하는 웹 애플리케이션을 만들어 보았다. 이번에는 DWR을 사용하면 Ajax가 얼마나 간단해지는지 알아보자.

이 기사에서는 DWR 2.0을 사용한다. 예제 코드는 제네릭스(generics)와 어노테이션(annotation)을 사용하므로 자바 5 이상이 필요하다. 예제에서는 MySQL 5.12와 톰캣(Tomcat) 6.0.14를 사용하고 있지만, 쉽게 교체할 수 있을 것이다. 데이터베이스 접근을 위해 JPA(Java Persistence API)를 사용하며, JPA 구현체는 OpenJPA 1.0을 사용한다. 하이버네이트(Hibernate)나 Kodo 등의 다른 JPA 구현체로 바꿔도 된다. 이 기사에서는 Ajax 디버깅을 위한 멋진 도구인 파이어버그(Firebug: 파이어폭스용 플러그인)를 사용한다. 이 도구들에 대한 링크는 참고자료에 나와 있다.

DWR 소개

Ajax 애플리케이션이 처음 등장했을 때는 마법처럼 보였지만, 다행히도 개발하는 과정은 직관적이다. 모든 Ajax 상호작용은 서버 측 엔드포인트(endpoint: 웹 서비스에서 빌려온 용어)와 그 엔드포인트를 호출하는 클라이언트 측 코드가 필요하다. 또한 클라이언트와 서버가 주고받는 데이터를 직렬화(serialize)하는 코드도 필요하다. 서버 측 엔드포인트는 일반적인 서비스(RESTful이든 아니든)면 되지만, 클라이언트 측에는 종종 요구에 부합하는 좀 더 구체적인 코드가 필요하다. 강한 결합(tight coupling)을 피해야 할 때도 있지만, 필요할 때도 있다. 후자라면 DWR이 준비된 해결책이다. DWR은 선언적인 방법으로(declaratively) 서버 측 코드를 Ajax 엔드포인트로 만들 수 있고, 모든 부가적인 설비도 자동으로 만들어 준다. 지금부터 구체적인 예제를 통해 DWR이 어떻게 동작하는지 알아보자.




위로


예제 애플리케이션: Ajax 쪽지 게시판

여기서 만들어 볼 예제는 간단한 쪽지 게시판이다. DWR을 통한 Ajax에 집중하기 위해, 데이터 모델을 극도로 단순하게 했다. DWR을 사용하여 애플리케이션에 Ajax를 도입하는 방법을 알아보기 위해 먼저 예제의 백엔드부터 살펴보자.




위로


백엔드 설정하기

쪽지 게시판을 위한 데이터베이스 테이블부터 만들어보자. 이 테이블을 생성하는 SQL 스크립트가 Listing 1이다.


Listing 1. Messages 테이블 SQL 스크립트

CREATE TABLE 'messages' (
  'id' int(11) NOT NULL auto_increment,
  'title' varchar(40) NOT NULL,
  'body' text,
  'created_at' timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  'author' varchar(40) NOT NULL default 'anonymous',
  PRIMARY KEY  ('id')
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

여기서 알아야 할 것은 쪽지 게시판의 쪽지를 위해 제목(title), 내용(body), 글쓴이(author)가 필요하다는 것뿐이다. SQL 스크립트가 나머지를 처리한다. 이 스크립트는 MySQL 용이지만, 다른 RDBMS에도 쉽게 적용할 수 있다. 데이터베이스 접근을 위해 JPA를 사용한다. 테이블에 대응하는 자바 클래스가 Listing 2다.


Listing 2. Message 자바 클래스

package org.developerworks.msgb;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Column;

import org.directwebremoting.annotations.DataTransferObject;

@DataTransferObject
@Entity
@Table(schema="msgb", name = "messages")
public class Message {
     @Id
     @GeneratedValue(strategy=IDENTITY)
     private int id;
     
     @Column(name="title")
     private String title;
     
     @Column(name="author")
     private String author;
     
     @Column(name="body")
     private String body;
     
     @Column(name="created_at")
     private Date createdAt;

     public int getId() {
          return id;
     }

     public void setId(int id) {
          this.id = id;
     }

     public String getTitle() {
          return title;
     }

     public void setTitle(String title) {
          this.title = title;
     }

     public String getAuthor() {
          return author;
     }

     public void setAuthor(String author) {
          this.author = author;
     }

     public String getBody() {
          return body;
     }

     public void setBody(String body) {
          this.body = body;
     }

     public Date getCreatedAt() {
          return createdAt;
     }

     public void setCreatedAt(Date createdAt) {
          this.createdAt = createdAt;
     }
}

대부분 뻔한 코드(boilerplate code)다. getter와 setter를 가진 몇 개의 필드뿐이다. 어노테이션은 대부분 자바 필드와 데이터베이스 컬럼을 매핑하기 위한 JPA 표준 어노테이션이다. 알아두어야 할 특이한 어노테이션이 하나 있는데, 바로 @DataTransferObject 어노테이션이다. 이 DWR 어노테이션은 이 클래스가 Ajax 응답의 일부로 자동으로 직렬화(marshaling)될 수 있음을 DWR에 알려준다. DWR은 자바 타입을 직렬화하기 위한 여러 가지 변환기(converter)를 포함하고 있다. 어노테이션을 사용하여 특정 필드가 직렬화에 포함되는지도 지정할 수 있다. 데이터베이스 접근에 대한 작업이 끝났으면, 서비스를 만들어 보자.




위로


원격 서비스 만들기

예제는 단순한 두 가지 기능밖에 없다. 쪽지 게시판에 올라온 모든 쪽지 목록을 보여주고, 사용자가 새로운 쪽지를 올릴 수 있다. 이 점을 염두에 두고, 예제의 서비스 코드인 Listing 3을 살펴 보자.


Listing 3. MessageService 클래스

package org.developerworks.msgb;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;

@RemoteProxy
public class MessageService {
     private EntityManagerFactory factory;
     private EntityManager em;
     
     public MessageService(){
          factory = Persistence.createEntityManagerFactory("msgb");
          em = factory.createEntityManager();
     }
     
     @RemoteMethod
     @SuppressWarnings("unchecked") // thanks type erasure
     public List<Message> getMessages(){
          List<Message> messages = em.createQuery("select m from 
		                                  Message m").getResultList();
          return messages;
     }
     
     @RemoteMethod
     public void saveMessage(Message msg){
          em.getTransaction().begin();
          em.persist(msg);
          em.getTransaction().commit();
     }
}

이 코드는 쪽지를 조회하고 생성하는 표준 JPA 코드다. 앞의 코드와 마찬가지로 주의깊게 볼 부분은 어노테이션이다. 클래스에 RemoteProxy 어노테이션이 붙어 있다. 이 어노테이션은 원격 클라이언트가 이 클래스의 메서드를 호출할 수 있음을 DWR에 알려준다. 물론, 그 원격 호출은 DWR을 통한 Ajax 호출이다. 각 메서드는 RemoteMethod 어노테이션을 사용하여 명시적으로 공개된다. 공개하고 싶지 않은 메서드가 있다면 어노테이션을 빼기만 하면 된다. 지금까지 작성한 코드는 일반적인 백엔드 코드와 추가적인 몇 가지 어노테이션뿐이다. DWR을 사용하기 위해 해야 할 작업이 하나 더 있다.




위로


DWR 서블릿

DWR은 Ajax 요청을 받고 응답하기 위해 자바 서블릿을 사용한다. 이 서블릿은 DWR 라이브러리에 포함되어 있으므로 웹 애플리케이션의 web.xml 파일만 바꿔주면 된다. 예제에서 사용하는 web.xml이 Listing 4다.


Listing 4. 쪽지 게시판을 위한 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://java.sun.com/xml/ns/javaee"
     xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
     id="msgb" version="2.5">
     <display-name>MessageBoard</display-name>
     <welcome-file-list>
          <welcome-file>index.html</welcome-file>
     </welcome-file-list>
     <servlet>
          <display-name>DWR Servlet</display-name>
          <servlet-name>dwr-invoker</servlet-name>
          <servlet-class>
               org.directwebremoting.servlet.DwrServlet
          </servlet-class>
          <init-param>
               <param-name>debug</param-name>
               <param-value>true</param-value>
          </init-param>
          <init-param>
               <param-name>classes</param-name>
               <param-value>org.developerworks.msgb.MessageService, 
			   org.developerworks.msgb.Message</param-value>
          </init-param>
     </servlet>
     <servlet-mapping>
          <servlet-name>dwr-invoker</servlet-name>
          <url-pattern>/dwr/*</url-pattern>
     </servlet-mapping>
</web-app>

DWR 서블릿이 /dwr/로 시작하는 모든 요청을 처리하도록 설정했다. 주목해 볼 만한 것은 init-params들이다. classes 매개변수에는 DWR 어노테이션을 가진 모든 클래스를 쉼표로 구분하여 나열하면 된다. 어노테이션을 좋아하지 않는다면 XML 파일을 사용할 수도 있다. 이것으로 백엔드 작업은 모두 끝났다. 이제부터 프론트엔드를 만들어 보자.




위로


프론트엔드 만들기

DWR을 애플리케이션의 백엔드에 적용하는 것은 무척 쉽다. 몇 가지 선언과 DWR 서블릿을 설정하는 것이 전부다. 그렇다면 프론트엔드는 어떨까? 자바스크립트 툴킷은 대부분 개발자들이 사용할 라이브러리를 포함하고 있다. 개발자는 해당 라이브러리를 웹 페이지에 포함시키고, 문서를 보면서 사용법을 익힌다. DWR은 다르다.

DWR은 자바스크립트 코드를 동적으로 생성한다. DWR을 사용하면 그 자바스크립트를 사용하는 방법도 쉽게 알 수 있다. Listing 4의 web.xml 파일을 주의깊게 살펴보자. debug라는 init-param이 보이는가? 이 매개변수를 사용하면 DWR이 생성한 동적인 자바스크립트를 자체 검사할(introspect) 수 있다. 웹 애플리케이션을 배포하고, http://<root>/<web_app_name>/dwr을 방문하면 그림 1과 같은 페이지를 볼 수 있다.


그림 1. DWR 디버그 화면
DWR 디버그 화면

이 페이지는 @RemoteProxy 어노테이션이 붙은 모든 자바 클래스의 목록을 표시한다. 클래스를 클릭하면 그림 2와 같은 더 자세한 정보를 볼 수 있다.


그림 2. MessageService 자바스크립트 정보
MessageService 자바스크립트 정보

이 페이지는 동적인 자바스크립트를 참조하는 방법을 보여준다. 반드시 포함시켜야 하는 핵심 라이브러리(engine.js)와 애플리케이션에 따라 달라지는 동적인 라이브러리(MessageService.js)가 있다. 여러 가지 도우미 함수를 제공하는 선택적인 유틸리티 라이브러리도 있다.

이제 DWR 자바스크립트를 참조하는 방법을 알았으니 사용하는 법을 알아보자. MessageBoard 페이지를 사용하여 시험해 볼 수 있다(그림 3).


그림 3. DWR Ajax 테스팅
DWR Ajax 테스팅

이 화면은 getMessages()를 호출하는 Ajax 요청의 결과를 보여준다. 데이터는 자바스크립트 객체(JSON)의 배열로 표시된다. API의 반환 값을 알았으니 호출하는 방법도 알아보자. Listing 5는 페이지 소스다.


Listing 5. MessageBoard 테스트 페이지 소스

<li>
  getMessages(  );
  <input class='ibutton' type='button' onclick='MessageService.getMessages(reply0);'
     value='Execute'  title='Calls MessageService.getMessages(). 
     View source for details.'/>
  <script type='text/javascript'>
    var reply0 = function(data)
    {
      if (data != null && typeof data == 'object') 
	            alert(dwr.util.toDescriptiveString(data, 2));
      else dwr.util.setValue('d0', dwr.util.toDescriptiveString(data, 1));
    }
  </script>
  <span id='d0' class='reply'></span>
</li>
<li>
  saveMessage(    <input class='itext' type='text' size='10' value='' 
    id='p10' title='Will be converted to: org.developerworks.msgb.Message'/>  );
        <input class='ibutton' type='button' onclick='MessageService.saveMessage
       (objectEval($("p10").value), reply1);' value='Execute'  title='Calls 
	    MessageService.saveMessage(). View source for details.'/>
  <script type='text/javascript'>
    var reply1 = function(data)
    {
      if (data != null && typeof data == 'object') alert(dwr.util.
	                                                 toDescriptiveString(data, 2));
      else dwr.util.setValue('d1', dwr.util.toDescriptiveString(data, 1));
    }
  </script>

  <span id='d1' class='reply'></span>
</li>

예제에서는 getMessages를 호출하기 위해 MessageService.getMessages(callbackFunction)을 사용했다. callbackFunction에 전달된 값은 앞에서 메서드를 시험하면서 살펴 보았다. 이제 웹 페이지를 만들 수 있는 준비가 끝났다. Listing 6을 살펴 보자.


Listing 6. MessageBoard 웹 페이지

 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN"
 "http://www.w3.org/TR/html4/strict.dtd">
 <html>
<head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>The developerWorks Message Board</title>
      <script type="text/javascript" src="/MessageBoard/dwr/interface/
                                      MessageService.js"></script>
      <script type="text/javascript" src="/MessageBoard/dwr/
                                      engine.js"></script>
      <script type="text/javascript" src="/MessageBoard/dwr/
                                      util.js"></script>
      <script type="text/javascript">
           var columns = ["title", "author", "body", "createdAt"];
           function loadData(){
                MessageService.getMessages(addRows);
         }
           // messages should be an array of Message hashes
           function addRows(messages){
                var cellfuncs = [];
                for each (var prop in columns){
                     cellfuncs.push(createFunction(prop));
                }
                dwr.util.addRows("messageTableBody", messages, cellfuncs);     
           }
           function createFunction(prop){
                return function(msg) { return msg[prop]; };
           }
           function sendMsg(){
                var msg = {};
                msg.title = dwr.util.getValue("title");
                msg.author = dwr.util.getValue("author");
                msg.body = dwr.util.getValue("body");
                MessageService.saveMessage(msg);
                msg.createdAt = Date();
                var messages = [msg];
                addRows(messages);
                clearForm();
           }
           function clearForm(){
                dwr.util.setValue("title","");
                dwr.util.setValue("author","");
                dwr.util.setValue("body","");
           }
      </script>     
      <style type="text/css">
           label {
               float: left;
               text-align: right;
               margin-right: 15px;
               width: 100px;
          }
          #messageTableHead {
               font-weight: 900;
               color:navy;
          }
          body {
               color:MidnightBlue;
               background-color:PaleTurquoise;
               margin:20px;
               padding:0px;
               font:11px verdana, arial, helvetica, sans-serif;
          }
          .pageTitle {
               margin:0px 0px 15px 0px;
               padding:0px;
               font-size:28px;
               font-weight:900;
               color:#aaa;
          }
          #formDiv{
               padding-top:12px;
               color:Indigo;
          }
      </style>
</head>
<body onLoad="loadData()">
     <div class="pageTitle">The developerWorks Message Board</div>
     <div class="pageContent">
          <table id="messageTable" border="1" cellpadding="4">
               <thead id="messageTableHead">
                    <tr>
                         <td>Title</td>
                         <td>Author</td>
                         <td>Message</td>
                         <td>Posted</td>
                    </tr>
               </thead>
               <tbody id="messageTableBody">
               </tbody>
          </table>
     </div>
     <div id="formDiv">
          <form id="messageForm">
               <label>Message Title:</label><input type="text" 
	                                name="title" id="title"/><br/>
               <label>Your Name:</label><input type="text"
	                              name="author" id="author"/><br/>
               <textarea name="body" cols="80" rows="15"
	                              id="body"></textarea><br/>
               <input type="button" id="msgBtn" value="Add Message"
	                              onClick="sendMsg()"/>
          </form>
     </div>
</body>
</html>

코드를 분석해 보자. 먼저 이 페이지는 JSP나 JSF 페이지가 아니라 정적인 HTML과 자바스크립트, CSS로 이루어진 단순한 HTML 파일이다. 페이지를 불러올 때 loadData() 함수를 호출한다. 이 함수는 앞에서 배웠던 MessageService.getMessages()를 호출한다. 그러면 DWR이 알아서 Ajax 요청을 보내고 그 응답을 addRows 콜백 함수에 전달한다.

addRows() 함수는 서버로부터 데이터를 받아서, DWR이 제공하는 dwr.util.addRows 함수를 호출한다. 이 함수는 지정한 ID를 가진 HTML 테이블(table), 테이블 헤더(header), 테이블 푸터(footer), 테이블 본체(body)에 행을 추가한다. 데이터를 추출해 테이블의 셀로 변환하기 위해 함수의 배열(코드에서는 cellfuncs)을 사용하고 있다. 결과적으로 테이블의 (i,j) 번째 셀의 데이터는 cellfuncs[j](messages[i])가 될 것이다. 이것은 데이터를 테이블로 매핑하는 매우 간결한 방법이다. 페이지를 브라우져로 열어보면 그림 4와 같은 화면을 볼 수 있다.


그림 4. MessageBoard 페이지
MessageBoard 페이지

이 페이지는 DWR을 통한 Ajax를 사용하여 데이터를 비동기로 가져온다. 쪽지 목록은 표시했지만, 새로운 쪽지는 어떻게 올려야 할까? 페이지의 아래쪽에 있는 폼을 통해 그 방법을 알 수 있다. Add Message를 클릭하면 DWR의 유틸리티 함수인 dwr.util.getValue를 호출하여 폼에서 데이터를 가져온다. 이 함수는 div, 텍스트 입력, 체크박스, 선택 목록 등 어떤 HTML 태그에도 사용할 수 있다. 폼에서 가져온 데이터를 자바스크립트 객체에 넣은 다음, MessageService.saveMessage()를 호출한다. 이 경우에는 응답이 없기 때문에(void) 핸들러를 지정하지 않았다. 그 대신, 앞에서 테이블에 새로운 행을 추가하기 위해 사용했던 addRows 함수를 재사용했다. 이렇게 하면 UI의 반응이 빨라진다.

폼에 값을 채우고 Add Message를 눌러서 시험해 볼 수 있다. 파이어버그 같은 도구를 사용하여 HTTP 통신 내용을 살펴보면 도움이 된다. Listing 7은 saveMessage를 호출할 때 서버로 보내진 데이터다.


Listing 7. saveMessage 호출

callCount=1
page=/MessageBoard/
httpSessionId=
scriptSessionId=B88B0681A9BB674C14786B7DCA3EA6E3153
c0-scriptName=MessageService
c0-methodName=saveMessage
c0-id=0
c0-e1=string:The%20One%20I%20Love
c0-e2=string:Michael
c0-e3=string:This%20goes%20out%20to%20the%20one%20I%20love.
                  %20This%20one%20goes%20out%20to%20the%20one
%20I%20left%20behind.
c0-param0=Object_Object:{title:reference:c0-e1, 
                  author:reference:c0-e2, body:reference:c0-e3}
batchId=1

Listing 7은 DWR이 주고 받는 데이터 형식을 보여준다. DWR 같은 프레임워크의 관점에서는 별로 중요하지도 않고, 신경 쓸 필요도 없지만, 어떻게 동작하는지 알아보는 일도 흥미롭다.




위로


요약

Ajax 개발의 다양한 부분을 더 쉽게 만들어주는 다양한 Ajax 툴킷이 있다. DWR은 자바 웹 애플리케이션을 위한 일석이조 툴킷이다. DWR을 사용하면 서버 측과 클라이언트 측 둘 다 쉽게 만들 수 있다. 서버 측에서는 몇 개의 자바 어노테이션만 붙이면 어떤 서비스라도 Ajax 서비스로 만들 수 있다. 클라이언트 측에서는 서버에 있는 서비스와 똑같은 API를 자동으로 만들어주므로, 콜백 함수만 추가하면 된다. 이보다 더 간단할 수 없다.





위로


다운로드 하십시오

설명이름크기다운로드 방식
Part 3 예제 코드wa-aj-ajaxpro3.zip1087KBHTTP
다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기
  • DWR 2.0을 사용하면 더 편리하게 브라우저에서 자바스크립트로 서버의 자바와 통신하고, 결과를 이용해 웹 페이지를 조작할 수 있다.

  • 파이어폭스용 파이어버그 확장 다운로드

  • 아파치의 톰캣 6.0.14 다운로드

  • MySQL 5.0.41 다운로드

  • OpenJPA 1.0.2 다운로드


필자소개

Michael Galpin 사진

Michael Galpin은 1998년부터 전문적으로 자바 소프트웨어를 개발하고 있으며 이베이에서 근무중이다. 캘리포니아 공대에서 수학을 전공하였다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의