출처 : http://www.javajigi.net/pages/viewpage.action?pageId=2028

AJAX 와 DWR

 

Table of Contents

Introduction

 

AJAX

AJAX는 DHTML, CSS, XML, XMLHttpRequest, javascript 등의 기술이 합쳐진 기술의 집합체를 의미한다. 즉, AJAX를 사용한다는 것은 비동기적인 통신을 통한 정보의 전달과 화면의 부분적 전환 등으로 구체화 된다. 이 두가지 기술 즉, 정보의 전달과 화면 전환에 필요한 라이브러리를 소개한다.

Ajax 의 핵심을 이루는 XMLHttpRequest 는 인터넷 익스플로러 5.0에서부터 이미 액티브X 오프젝트 형태로 제공되었고 그 후 모질라 1.0에서부터 윈도우 오브젝트 형태로 XMLHttpRequest 객체가 지원되기 시작하였다. 그 외에 다양한 브라우저에서도 지원하고 있다. 하지만 IE 와 다른 브라우저들 간의 XMLHttpRequest 객체를 가져오는 방법이 다르고 브라우저 간에 인터페이스나 구현의 차이로 인해 미묘한 차이점을 보이는 문제가 아직 존재한다.

 

사용목적

웹에서 액티브X, 플래시 또는 자바 애플릿 등에 의존했던 사용성, 접근성, 응답성의 향상이다. 네트워크 인프라가 발전하여 네트워크 속도가 아무리 빨라진다고 하더라도 새로운 컨덴츠를 보여주기 위하여 웹 페이지 전체가 매번 리로딩되는 것은 사용성, 응답성의 한계로 이어질 수 밖에 없다.

장점

AJAX 는 기존 웹 어플리케이션의 비효율성, 동기적인 액션의 단점을 해결할 수 있는 웹페이지의 부분적인 변화 적용과 비동기성에 있다. 하지만 존재하는 Active x, 자바 애플릿,플래시도 대안이 될 수 있다.
하지만 AJAX는 플러그인을 사용하지 않고 브라우저만으로 바로 이용할 수 있다는 것,
새로운 것을 배우지 않고 DHTML,자바스크립트,CSS 등의 기존 웹기술을 그대로 이용할 수 있다는 는 것으로도 충분한 대안이 될 수 있다.

사용방법

XMLHttpRequest 객체 얻어오기

 
- 인터넷 익스플로러
var xmlHttp =new ActiveXObject("Microsoft.XMLHTTP");
- 그외 
var xmlHttp = new XMLHttpRequest();
Useful Information

익스플로러 7.0부터는 다른 브라우저와 같은 방식으로 XMLHttpRequest객체를 생성할수 있다고 합니다.
참고 블로그

XMLHttpRequest 의 이용
XMLHttpRequest 는 동기적, 비동기적 등 두가지 방법으로 이용할 수 있는데 ajax 에서는 비동기적 방법을 주로 이용하게 된다.비동기 모드로 XMLHttpRequest 를 사용할 경우 send()가 호출된 후 웹서버로부터 응답이 오기를 기다리며 브라우저가 멈춰있지 않고 웹 서버로부터 결과를 모두 받아낸 이후 콜백 핸들러에 정의된 코드가 실행되기 때문에 사용자는 계속 브라우저를 이용할 수 있게 된다.

XMLHttpRequest 를 동기적으로 이용할 때에는 XMLHttpRequest 의 send()를 호출한 뒤 response의 status code 를 체크하고 responseText에서 결과를 꺼내오게 된다.
만일 동기적으로 이용할 때는 XMLHttpRequest의 open()메쏘드의 3번째 파라미터 값을 false 로 줘야 한다. Open()의 3번째 파라미터는 비동기적으로 이용할 것인지를 타나내는 Boolean 값이 들어가고 default 로 true 이며 생략 가능하다.

비동기적으로 이용할 때는 send()를 호출하기 전에 XMLHttpRequest 의 onready Ststate change 프로퍼티에 XMLHttpRequest 의 상태가 변할 때마다 실행될 핸들러를 function reference 형태로 줘야 한다. 상태는 5개의 상태를 가지지만 보통 가장 마지막 상태인 완료상태를 체크해서 처리해 주면 된다.

 
- 동기적
xmlhttp.open("GET","test.html",false);
xmlhttp.send(null);
if(xmlhttp.status == 200){
   alert(xmlhttp.responseText);
}

- 비동기적 
xmlhttp.open("GET","test.html",true);
xmlhttp.onreadystatechange=function(){
 if(xmlhttp.readyState == 4){
   if(xmlhttp.status == 200){
	 alert(xmlhttp.responseText);
     }
   }
}
    
xmlhttp.send(null);

– 메소드 설명

메소드 설명
void abort() 현재 진행 중인 요청을 취소한다.
String getAllresponseHeaders() 응답 헤더(response header)를 하나의 문자열 형태로 반환한다.
String getResponseHeader(String key) headername 에 해당하는 응답 헤더의 값을 반환한다.
void open(String method,String URL,
asynchronous boolean async,String은 true 이다
HTTP요청에 사용될 URL과 메소드,asynchronous 모드 등을 지정한다. async는 모드의 사용여부를 true/false로 지정하고 default 값 인증이 필요한 경우 username,String password)username 과 password를 지정하면 되고 생략가능하다.
void send(String body) 요청을 보낸다.
void setRequestHeader(String key,HTTPString value 응답 헤더를 키/값으로 지정한다.

XMLHttprequest 를 이용해 받은 결과 화면에 적용하기
XMLHttpRequest 를 이용해서 서버와 통신 후 결과는 XMLHttpRequest의 responseHTML프로퍼티에 저장된다. 통신 결과가 평범한 HTML이고 이 결과를 화면에 바로 적용하는 것이라면 DOM 인터페이스를 사용하여 적용하고 싶은 HTML 요소를 찾은 뒤 innerHTML 프로퍼티에 responseHTML의 값을 복사해 주는 방식으로 결과를 적용하면 된다.

 
xmlhttp.onreadystatechange=function(){
 if(xmlhttp.readyState == 4){
   if(xmlhttp.status == 200){
	 Document.getElementById("result").innerHTML = xmlhttp.responseText;
   }
  }
}

– 프로퍼티 설명

프로퍼티 설명
onreadystatechange XMLHttpRequest 객체의 상태가 변할 때 실행할 핸들러를 지정한다.
readyState XMLHttpRequest 객체의 상태가 변할 때 각 상태 값을 반환한다.
0 = uninitialized
1 = loading
2 = loaded
3 = interactive
4 = complete
responseText HTTP 요청결과를 문자열 형태로 반환한다.
responseXML HTTP 요청 결과를 XMLDocument 오브젝트로 반환한다.
status HTTP 응답 코드를 반환한다. 성공인 경우 200
statusText HTTP 응답 문자열을 반환한다. 성공인 경우 OK

사용예제

select box를 선택하는 예제이다. 제품을 선택하면 상위버전이 조회,상위버전을 선택하면 하위버전이 선택, 하위버전을 선택하면 모듈 List 가 조회된다.

호출하는 JSP

<table width="779" border="0" cellspacing="0" cellpadding="0" style="border:#E4E4E4 1px solid">
  <tr align="center" valign="top">
    <td style="padding:7 0 7 0">

      <table width="746" border="0" cellspacing="0" cellpadding="0">
        <tr height="25" bgcolor="#F6F6F6">
          <td width="25%" class="ta_tit" style="padding-left:10px">제품</td>
          <td width="25%" class="ta_tit" style="padding-left:10px">버전</td>
          <td width="50%" class="ta_tit" style="padding-left:10px">모듈</td>
        </tr>
        <tr valign="top">
          <td style="padding:5 0 5 10">
<!-- 이부분에서 호출을 한다.-->
            <select name="productCode" class="select" size="4" style="width:150px;" onchange="setConditionByProductPK(this.value);">
              <c:if test="${not empty requestScope.products}">
                <c:forEach var="product" items="${requestScope.products}">
                  <option value="<c:out value="${product.productCode}"/>"><c:out value="${product.productName}"/></option>
                </c:forEach>
              </c:if>
            </select>
          </td>
          <td style="padding:5 0 5 10">
            <SELECT name="versionCode" class="select" size="4" style="width:150px" multiple>
            </SELECT>
          </td>
          <td style="padding:5 0 5 10">
            <SELECT name="moduleCode" class="select" size="4" style="width:150px" multiple>
            </SELECT>
          </td>
        </tr>
      </table>

    </td>
  </tr>
</table>

Script

 function makeXmlHttpObject() {
   var xmlHttp = null;
   if (window.XMLHttpRequest) {
     xmlHttp = new XMLHttpRequest();
   } else if (window.ActiveXObject) {
     isIE = true;
     xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
   } else {
     alert("Your browser is not supported");
     return;
   }
  return xmlHttp;
}


function openXmlHttp(xmlHttp, method, url, flag, message) {
  xmlHttp.open(method, url, true);
  if (message == null) 
    xmlHttp.send(null);
  else 
    xmlHttp.send(message);
}


var moduleXmlHttp;
var versionXmlHttp;
var subVersionXmlHttp;

function setConditionByProductPK(productCode, isOpened) {
  var moduleBox = document.getElementById("moduleCode");
  var mainVersionBox = document.getElementById("mainVersionCode");
  var subVersionBox = document.getElementById("subVersionCode");
  if (isNull(productCode)) {
    resetComboBox(moduleBox);
    resetComboBox(mainVersionBox);
    resetComboBox(subVersionBox);
    return;
  }
  *setModuleBox(productCode);*
  }

function setModuleBox(productCode) {
  moduleXmlHttp = makeXmlHttpObject();
  moduleXmlHttp.onreadystatechange = processMinumum;
  openXmlHttp(moduleXmlHttp, "GET", context + "/bug/xml/moduleList.do?productCode=" + productCode, true, null);  
}

function processMinumum() {
  if (moduleXmlHttp.readyState == 4) {
    if (moduleXmlHttp.status == 200) {
      var f = document.SearchForm;
      var moduleBox = f.moduleCode;
      
      while (moduleBox.length > 1) {
        moduleBox.remove(1);
      }
      var names = getNodeList(moduleXmlHttp, "name"); 
      var codes = getNodeList(moduleXmlHttp, "code"); 
    
      for (var i=0; i<names.length; i++)  {
        appendToSelect(moduleBox, codes[i].firstChild.nodeValue, document.createTextNode(names[i].firstChild.nodeValue));
      } 
    } else {
      alert("There was a problem retrieving the XML data:\n" + req.statusText);
    }
  }
}

return file

<%@ page contentType="text/xml" %><%@ taglib uri="jstl-c" prefix="c" %>
<?xml version="1.0" encoding="KSC5601" ?>
<document>
  <c:forEach var="module" items="${requestScope.modules}">
    <code><c:out value="${module.moduleCode}"/></code>
    <name><c:out value="${module.moduleName}"/></name>
  </c:forEach>
</document>
 

DWR

DWR은 쉬운 방법으로 AJAX 와 XMLHttpRequest를 사용하길 원하는 개발자를 위한 오픈 소스 솔루션이다.
DWR 은 Direct Web Remoting 의 약자로 클라이언트 쪽의 AJAX 관련 자바스크립트 라이브러리 뿐만 아니라 서버쪽의 웹 어플리케이션 라이브러리 까지 포함하고 있다.

DWR은 크게 두 개의 파트로 구성되어 있다.

  • Javascript를 이용하여 AJAX의 구조를 이용하여 web-server기반의 서블릿으로 부터 data 검색이 가능하도록 하는 Code
  • 웹개발자들로 하여금 검색된 data를 이용하여 동적으로 웹 페이지를 update하는 것을 용이하게 하기 위한 JavaScript library
Useful Information

DWR웹 어플리케이션은 크게 모든 요청을 처리하는 컨트롤러 역할을 하는 DWRServlet 과 클라이언트의 요청을 처리할 빈들로 구성되어 있다.DWRServlet 은 웹 어플리케이션의 /WEB-INF/web.xml 에서 설정이 가능하고 요청들을 처리할 빈( bean) 객체들은 /WEB-INF/dwr.xml 에서 설정할 수 있다.

설정방법 및 샘플

아래 샘플파일은 참고자료에서 다운받은 것을 기준으로 작성되었다.

  1. dwr.jar 를 다운로드 받은 후 WEB-INF/lib 아래 둔다.
  2. web.xml에 DWRServlet 관련 설정을 한다.
  3. WEB-INF/dwr.xml 파일을 작성한다.
  4. 호출하는 클라이언트 프로그램을 작성한다.(여기선 jsp)

web.xml

<servlet>
	<servlet-name>dwr-invoker</servlet-name>
	<display-name>DWR Servlet</display-name>
	<description>Direct Web Remoter Servlet</description>
	<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
	<init-param>
	     <param-name>debug</param-name>
	     <param-value>true</param-value>
	</init-param>
</servlet>
	
<servlet-mapping>
	<servlet-name>dwr-invoker</servlet-name>
	<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

보면 DWR 웹 어플리케이션에서는 모든 요청을 일단 DWRServlet 이 받아서 처리하게 되는데 그 요청을 처리할 객체나 데이터 타입의 맵핑, 보안등의 설정은 모두 dwr.xml 의 설정을 바탕으로 한다

dwr.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN" "http://www.getahead.ltd.uk/dwr/dwr10.dtd">

<dwr>
  <allow>
        <convert converter="bean" match="com.tmax.spring.domain.Apartment"/>
        <create creator="new" javascript="ApartmentDAO" >
          <param name="class" value="com.tmax.spring.ibatis.ApartmentDAO"/>
	   <include method="findApartments"/>
            <include method="countApartments"/>
        </create>
  </allow>
</dwr>

DWR에서 요청을 처리할 빈(bean) 객체를 지정하는 부분이 <create />이다.
create 요소에는 creator,javascript,scope의 3가지 속성이 지정될 수 있다 creator 속성은 요청을 처리할 빈 객체가 생성되는 방식을 지정하는데 직접 클래스의 이름을 지정하여 생성할 수도 있고 빈 셸 스크립트를 이용하거나 Spring framework 에 객체 생성을 위임할 수도 있다. 기본적으로 가장 많이 사용되는 create 속성인 new 는 지정된 클래스의 인스턴스를 생성하여 클라이언트 쪽에서 오는 자바스크립트 요청과 지정된 인스턴스의 메소드를 맵핑해준다.
Scope 속성 은 creator 로 지정된 객체의 라이프싸이클 범위를 지정하는 것으로 J2EE 서블릿 스펙에서 정의된 scope 와 동일하게 application, page, request,session 중 하나의 값을 가질 수 있다.
creator 는 include 혹은 exclude 요소를 가질 수 있는데 DWR 서비스 빈 객체에서 호출할 수 있는 메소드를 제한하는데 사용할 수 있다.

ApartmentDAO : 쿼리를 가져오는 실제 메소드가 들어있는 클래스
Apartment : id,bedrooms,bathrooms,price,address,city,province를 가진 도메인 객체

search.jsp

 <script src='dwr/interface/ApartmentDAO.js'></script>
 <script src='dwr/engine.js'></script>
 <script src='dwr/util.js'></script>
.
.
.

 <script language="javascript">
 function updateTotal() {
    $("resultTable").style.display = 'none';
    var bedrooms = document.getElementById("bedrooms").value;
    var bathrooms = document.getElementById("bathrooms").value;
    var price = document.getElementById("price").value;
    ApartmentDAO.countApartments(loadTotal, bedrooms, bathrooms, price);
 }

 function loadTotal(data) {
    document.getElementById("totalRecords").innerHTML = data;
 }
</script>
.
.
.

DWR은 클라이언트가 서버단의 서비스 객체를 호출하기 위한 일종의 스텁 스크립트를 자동으로 생성해준다.
dwr/interface/*는 서버단의 서비스 객체를 호출하기 위한 자바스크립트를 만들어주는 URL이다. 그렇기 때문에 ApartmentDAO객체를 클라이언트 쪽에서 사용하기 위해서는 미리 ApartmentDAO가 정의 되어 있는 자바스크립트를 소스에 포함시켜야 한다.
<script src='dwr/interface/ApartmentDAO.js'></script>
그러면 ApartmentDAO의 메소드를 스크립트 안에서 사용할 수 있다.

ApartmentDAO.countApartments(loadTotal, bedrooms, bathrooms, price);
parameter 중 맨 앞은 countApartment가 수행한 뒤 호출되야 하는 callback 메소드, 그 뒤는 countApartment 메소드의 parameter 이다.
callback 메소드 에서 처리를 할 수 있다.

document.getElementById("totalRecords").innerHTML = data
countApartments 메소드를 수행하고 나온 결과를 totalRecords 라는 태그에 innerHTML로 설정한다.

사용예제 참고사항

  • 사용한 샘플을 돌릴때 에러가 발생하였다.
    다운로드 받은 jw-0620-dwr.war안에 dwr.xml 이
    <dwr>
        <allow>
            <convert converter="bean" match="dwr.sample.Apartment"/>
            <create creator="new" javascript="ApartmentDAO" class="dwr.sample.ApartmentDAO">
                <include method="findApartments"/>
                <include method="countApartments"/>
            </create>
      </allow>
    </dwr>
    

로 되어있다. 하지만 <create/>는 class 를 attribute 로 가지지 않는다. 그래서 에러 발생..

<create creator="new" javascript="ApartmentDAO" class="dwr.sample.ApartmentDAO"> 를
<create creator="new" javascript="ApartmentDAO" >
<param name="class" value="com.tmax.spring.ibatis.ApartmentDAO"/>
</create>

으로 변경한 뒤 정상적으로 수행이 되었다.

수행한 결과

  • 사용예제는 렌트할 아파트를 구하는 예제이다.

원하는 방 갯수, 화장실 갯수, 가격 대를 선택하면 조건에 맞는 아파트의 수가 나오고 show results 라는 버튼을 클릭하면 주소,방갯수,화장 실갯수,가격등의 정보가 조회된다.


만일 예제와 같은 결과를 얻기 위해 DWR 를 이용하지 않았다면 XMLHttpRequest 객체를 얻는 과정부터 소스를 작성했어야 할 것이다.
하지만 DWR을 사용함으로써 이전의 과정은 모두 DWR 에 맡기고 실제 작동하는 서버 클래스 작성, dwr.xml 작성에만 신경을 쓸 수 있게 되었다.

역시 DWR 최고!!!!!!!!!!!!

AjaxTag

AjaxTag 는 jsp 에서 AJAX 의 사용을 간단하게 해주기 위해서 제공해주는 tag library
다. AJAX 를 이용한 어떠한 기능을 구현하기 위해 jsp에서 많은 스크립트를 작성하는걸 방지해주기 위해 만들어진 태그 라이브러리 이다.

AjaxTag 사이트를 보면 제공하는 tag가 점점 증가하고 있다. 또 online demo 등을 통해 확인 할 수 있고 소스도 제공을 한다.

아래 예제는 autocomplete 태그를 이용하는 것으로 입력창에 글자를 입력하면 그것과 매치하는 정보를 서버에서 가져오는 기능을 수행한다.
naver 의 검색글자 입력창 참조..

설정방법

  • WEB-INF/lib 에 ajaxtags.jar 파일을 복사한다.
  • prototype-1.3.1.js,ajaxtags-1.1.5.js을 js 위치에 맞게 복사한다.
  • web.xml에 <taglib></taglib> 를 추가한다.
    <taglib>
      <uri>http://ajaxtags.org/tags/ajax</uri>
      <location>/WEB-INF/ajaxtags.tld</location>
    </taglib>
    

web.xml 에 설정하지 않고 호출되는 jsp 에 직접 <%@ taglib uri="http://ajaxtags.org/tags/ajax" prefix="ajax" %> 를 작성해도 된다.

  • 서버단의 서블릿을 작성한다.작성한 후 web.xml 에 추가한다.
  • jsp 파일을 작성한다.

사용예제 설명

AutocompleteServlet.java

public class AutocompleteServlet extends BaseAjaxServlet {

  /**
   * @see org.ajaxtags.demo.servlet.BaseAjaxServlet#getXmlContent(javax.servlet.http.HttpServletRequest,
   *      javax.servlet.http.HttpServletResponse)
   */
  public String getXmlContent(HttpServletRequest request, HttpServletResponse response)
      throws Exception {
    String model = request.getParameter("model");
    
    List list = getModelsByName(model);

    // Create xml schema
    return new AjaxXmlBuilder().addItems(list, "model", "make").toString();
  }
  
  public List getModelsByName(String name) {
	    List l = new ArrayList();
	    for (Iterator iter = cars.iterator(); iter.hasNext();) {
	      Car car = (Car) iter.next();
	      if (car.getModel().toLowerCase().startsWith(name.toLowerCase())) {
	        l.add(car);
	      }
	    }
	    return l;
	  }
  
  static final List cars = new ArrayList();

  static {
    cars.add(new Car("Ford", "Escape"));
    cars.add(new Car("Ford", "Expedition"));
    cars.add(new Car("Ford", "Explorer"));
    cars.add(new Car("Ford", "Focus"));
    cars.add(new Car("Ford", "Mustang"));
    cars.add(new Car("Ford", "Thunderbird"));

    cars.add(new Car("Honda", "Accord"));
    cars.add(new Car("Honda", "Civic"));
    cars.add(new Car("Honda", "Element"));
    cars.add(new Car("Honda", "Ridgeline"));

    cars.add(new Car("Mazda", "Mazda 3"));
    cars.add(new Car("Mazda", "Mazda 6"));
    cars.add(new Car("Mazda", "RX-8"));
  }
}
  • BaseAjaxServlet 을 상속받도록 한다. 상속 후 getXmlContent(HttpServletRequest request, HttpServletResponse response) 메소드를 구현하도록 한다. AjaxXmlBuilder().addItems(list, "model", "make")을 통해서 만들어진 결과를 String으로 변환한 결과를 리턴한다.
    자동으로 결과를 xml 형태로 만들어준다.

위의 예제를 그대로 할려면 Car 객체도 함께 생성해 주어야 한다.

web.xml

 <servlet>
    <servlet-name>autocomplete</servlet-name>
    <servlet-class>com.tmax.spring.servlet.AutocompleteServlet</servlet-class>
    <load-on-startup>3</load-on-startup>
</servlet>
	
<servlet-mapping>
    <servlet-name>autocomplete</servlet-name>
    <url-pattern>/autocomplete.view</url-pattern>
 </servlet-mapping>

index.jsp

<%@ page contentType="text/html; charset=euc-kr" errorPage="/jsp/error/jspError.jsp" %>
<%@ include file="/jsp/include/taglibs.jsp" %>

<html>
  <script type="text/javascript" src="${ctx}/js/prototype-1.3.1.js"></script>
  <script type="text/javascript" src="${ctx}/js/ajaxtags-1.1.5.js"></script>
  <link type="text/css" rel="stylesheet" href="${ctx}/css/ajax.css" />

<script language="javascript">
</script>  
 
<form id="carForm">
  <fieldset>
    <legend>Enter Car Model</legend>
    <p>Available values start with letters: 'A', 'C', 'E', 'F', 'M', 'R', 'T'</p>

    <label for="model">Name:</label>
    <input id="model" name="model" type="text" size="30" class="form-autocomplete" />

    <label for="make">Make:</label>
    <input id="make" name="make" type="text" size="30" />
  </fieldset>
</form>

<ajax:autocomplete
  source="model"
  target="make"
  baseUrl="autocomplete.view"
  className="autocomplete"
  parameters="model={model}"
  progressStyle="throbbing"
  minimumCharacters="1" />
 
</html>

<script type="text/javascript" src="/js/prototype-1.3.1.js"></script>
<script type="text/javascript" src="/js/ajaxtags-1.1.5.js"></script>
는 반드시 추가해야 한다.

참고문헌

  • 마이크로소프트웨어 2005년 7월,8월에 개제된 AJAX 관련 기사에서 많이 참고하였습니다.

참고 사이트

http://blog.naver.com/stpoo?Redirect=Log&logNo=88390173

http://www.ibm.com/developerworks/kr/library/j-ajax3/

+ Recent posts