|
|
|
난이도 : 중급
Philip McCarthy, Software development consultant, Independent
2006 년 10 월 17 일 2006 년 10 월 24 일 수정
Ajax 기능을 애플리케이션에 추가하기란 간단한 일이 아닙니다. 자바™ 개발자를 위한 Ajax 시리즈 세 번째 기사에서는 Direct Web Remoting (DWR)을 사용하여 JavaBeans 메소드를 JavaScript 코드에 직접 노출하고 Ajax를 자동화 하는 방법을 설명합니다.
시리즈 소개
Ajax 프로그래밍의 기초 (한글) 이해하는 것은 필수적인 일이지만, 복잡한 Ajax UI를 구현한다면, 고급 추상화 레벨에서 작업할 수 있어야 한다. Ajax for Java developers 시리즈 세 번째 글에서는 지난 달 소개했던 Ajax용 데이터 직렬화 기술 (한글)을 바탕으로, 자바 객체들을 직렬화 하는 문제를 단순화 하는 방법을 설명하겠다.
이전 글에서, JavaScript Object Notation (JSON)을 사용하여 클라이언트 상에서 JavaScript 객체로 쉽게 변환되는 포맷으로 데이터를 직렬화 하는 방법을 설명했다. 이 설정을 통해, JavaScript 코드를 사용하여 원격 서비스 호출을 호출하고, 원격 프로시저 호출과는 달리, 그에 대한 응답으로 JavaScript 객체 그래프를 받을 수 있다. 이번에는, 한 단계 더 나아가서 JavaScript 클라이언트 코드로부터, 서버 측 자바 객체에 대한 원격 프로시저 호출을 하는 기능을 규정하는 프레임웍을 사용해 보겠다.
DWR은 오픈 소스이며, 서버 측 자바 라이브러리, DWR 서블릿, JavaScript 라이브러리로 구성된 Apache 라이센스 솔루션이다. DWR이 자바 플랫폼에 사용할 수 있는 유일한 Ajax-RPC 툴킷은 아니지만, 가장 성숙하고, 많은 유용한 기능들을 제공하고 있다. 참고자료 섹션에서 DWR을 다운로드 하기 바란다.
DWR이란 무엇인가?
간단히 말해서, DWR은 서버 측 자바 객체의 메소드를 JavaScript 코드로 노출하는 엔진이다. DWR을 사용하여 애플리케이션 코드에서 Ajax 요청-응답 사이클 절차를 줄일 수 있다. 다시 말해서, 클라이언트 측 코드가 XMLHttpRequest 객체를 직접 다루거나 서버의 응답을 직접 다룰 필요가 없다는 것을 의미한다. 객체 직렬화 코드를 작성하거나 서드 파티 툴을 사용하여 객체를 XML로 전환할 필요가 없다. 서블릿 코드를 작성하여 Ajax 요청들을 자바 도메인 객체에 대한 호출로 중재할 필요도 없다.
DWR은 웹 애플리케이션에 서블릿으로서 전개된다. 블랙 박스처럼 보이는 이 서블릿은 두 가지 중요한 역할을 한다. 하나는, 각각 노출된 클래스에 대해, DWR은 JavaScript를 동적으로 생성하여 웹 페이지에 포함시킨다. 생성된 JavaScript에는 자바 클래스에 상응하는 메소드를 나타내는 스텁 함수가 포함되어 있고 막후에서 XMLHttpRequest 도 수행한다. 이러한 요청들은 DWR 서블릿으로 보내지고, 요청들을 서버 측 자바 객체에 대한 메소드 호출로 변환하고, JavaScript로 인코딩 하여 메소드의 리턴 값을 다시 클라이언트로 보낸다. 이것이 두 번째 역할이다. DWR은 일반적인 UI 태스크를 수행하는 것을 돕는 JavaScript 유틸리티 함수도 제공한다.
예제
DWR을 보다 자세히 설명하기 전에, 간단한 예제 시나리오를 소개하겠다. 이전 글에서와 마찬가지로, 온라인 스토어에 기반한 최소한의 모델을 사용하겠다. 이번에는 기본적인 제품 표현으로 구성된, 제품 아이템들을 포함시킬 수 있는 사용자의 쇼핑 카트와, 데이터 액세스 객체(DAO)를 사용하여 데이터 스토어에서 제품 상세를 검색한다. Item 클래스는 이전 글에서 사용했던 클래스이지만, 더 이상 수동 직렬화 메소드는 구현하지 않겠다. 그림 1은 설정 방법을 묘사한 것이다. 그림 1. Cart, CatalogDAO, Item 클래스를 나타내는 클래스 다이어그램
이 시나리오에서 두 개의 매우 간단한 유스 케이스를 설명하겠다. 첫 번째는 사용자가 카탈로그에서 텍스트 검색을 수행하고 매칭 아이템을 찾는 것이다. 두 번째는, 사용자가 아이템을 쇼핑 카트에 추가하고 카트에 있는 아이템들의 총 비용을 보는 것이다.
카탈로그 구현하기
DWR 애플리케이션의 시작점은 서버 측 객체 모델을 작성하는 것이다. 이 경우, DAO를 작성하여 제품 카탈로그 데이터스토어에 검색 기능을 제공한다. CatalogDAO.java 는 단순한 스테이트리스 클래스로서 인자 구조체가 없다. Listing 1은 Ajax 클라이언트로 노출 할 자바 메소드이다: Listing 1. DWR을 통해 노출 할 CatalogDAO 메소드
/**
* Returns a list of items in the catalog that have
* names or descriptions matching the search expression
* @param expression Text to search for in item names
* and descriptions
* @return list of all matching items
*/
public List<Item> findItems(String expression);
/**
* Returns the Item corresponding to a given Item ID
* @param id The ID code of the item
* @return the matching Item
*/
public Item getItem(String id);
|
다음에는, DWR을 설정하여, Ajax 클라이언트가 CatalogDAO 를 구현하고 이러한 메소드를 호출하도록 할 것이다. Listing 2의 dwr.xml config 파일을 사용했다. Listing 2. CatalogDAO 메소드를 노출하는 설정
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
<allow>
<create creator="new" javascript="catalog">
<param name="class"
value="developerworks.ajax.store.CatalogDAO"/>
<include method="getItem"/>
<include method="findItems"/>
</create>
<convert converter="bean"
match="developerworks.ajax.store.Item">
<param name="include"
value="id,name,description,formattedPrice"/>
</convert>
</allow>
</dwr>
|
dwr.xml 문서의 루트 엘리먼트는 dwr 이다. 이 엘리먼트 안에는 allow 엘리먼트가 있는데, 이것은 DWR이 원격 조정할 클래스들을 지정한다. allow 의 두 개의 자식 엘리먼트들은 create 와 convert 이다.
Creat 엘리먼트
Create 엘리먼트는 DWR에게 서버 측 클래스가 Ajax 요청으로 노출되도록 명령하고, DWR이 그 클래스의 인스턴스를 얻는 방법을 정의한다. creator 애트리뷰트에는 new 값이 설정되고, 이는 DWR이 클래스의 디폴트 컨스트럭터를 호출하여 인스턴스를 얻어야 한다는 것을 의미한다. 다른 기능들은 Bean Scripting Framework (BSF)를 사용하여 스크립트 조각을 통해 인스턴스를 만들거나, IOC 컨테이너인 Spring과의 통합을 통해 인스턴스를 얻는 것이다. 기본적으로, DWR에 대한 Ajax 요청이 creator 를 호출하면, 인스턴스화 된 객체가 페이지 범위에 놓이고, 요청이 완료된 후에는 더 이상 사용할 수 없다. 스테이트리스 CatalogDAO 의 경우, 이것은 괜찮다.
Create 의 javascript 애트리뷰트는 JavaScript 코드에서 액세스 될 객체 이름을 지정한다. create 엘리먼트 내에 중첩된 param 엘리먼트는 creator 가 만들 자바 클래스를 지정한다. 마지막으로, include 엘리먼트는 노출되어야 하는 메소드의 이름을 지정한다. 노출될 메소드를 명확히 드러내는 것은 잠재적으로 발생할 수 있는 위험들을 피하기에 좋다. 엘리먼트가 생략된다면 모든 클래스의 메소드들은 원격 호출에 노출될 것이다. exclude 엘리먼트를 사용하여 접근을 방지하고 싶은 메소드만 지정할 수 있다.
Convert 엘리먼트
creator 가 클래스를 노출하는 것과, 웹 리모팅 방식과 연관이 있는 반면, convertor 는 그러한 메소드의 매개변수와 리턴 유형과 연관이 있다. convert 엘리먼트의 역할은 서버 측 자바 객체 구현과 직렬화된 JavaScript 구현 사이에서 데이터 유형을 변환하는 방법을 DWR에게 명령하는 것이다.
DWR은 자바와 JavaScript 구현들 간 데이터 유형들을 자동으로 중재한다. 이 유형에는 자바 프리머티브와 각각의 클래스 구현, String과 Date, 어레이, 컬렉션 유형들이 포함된다. DWR은 또한 JavaBeans를 JavaScript 구현으로 변환하고, 보안 때문에, 명확한 설정이 필요하다.
Listing 2의 convert 엘리먼트는 DWR에게 리플렉션 기반 빈 convertor를 CatalogDAO 의 노출된 메소드에 의해 리턴된 Item 에 사용하고 직렬화에 포함될 Item 의 멤버를 지정한다. 멤버들은 JavaBean 네이밍 규약을 사용하여 지정되어, DWR은 상응하는 get 메소드를 호출할 것이다. 이 경우, 숫자 price 필드를 생략하고, 대신 통화로 포맷된 formattedPrice 필드를 포함시켰다.
이 시점에서, dwr.xml을 나의 웹 애플리케이션의 WEB-INF 디렉토리에 전개할 준비가 되고, 이 곳에서 DWR 서블릿이 집어낸다. 진행하기 전에, 모든 것이 생각한 대로 잘 작동하는지를 확인해보는 것이 좋다.
전개 테스팅
DWRServlet 의 web.xml 정의에서 init-param debug 를 true 로 설정했다면 DWR의 가장 유용한 테스트 모드가 실행된 것이다. /{your-web-app}/dwr/ 을 검색하면 DWR이 설정했던 클래스 리스트가 나타날 것이다. 해당 클래스에 대해 상태 스크린을 클릭한다. CatalogDAO 용 DWR 테스트 페이지는 그림 2에 나타나 있다. script 태그를 웹 페이지에 붙일 뿐만 아니라, 클래스용으로 생성된 DWR의 JavaScript를 가리키면서, 이 스크린 역시 클래스의 메소드 리스트를 제공한다. 이 리스트에는 클래스의 상위 유형에서 상속된 메소드가 포함되어 있고, dwr.xml 에서 원격용으로 명확하게 지정한 메소드도 액세스 가능한 것으로 표시된다.
그림 2. CatalogDAO용 DWR 테스트 페이지
매개변수 값을 액세스 가능한 메소드 옆 텍스트 박스에 입력하고 Execute 버튼을 눌러 이들을 호출할 수 있다. 단순한 값이 아니라면, 서버의 응답은 경고 박스에 JSON 공지를 사용하여 디스플레이 될 것이다. 이 경우 메소드를 따라서 한 줄로 디스플레이 된다. 이러한 테스트 페이지들은 매우 유용하다. 어떤 클래스와 메소드가 원격으로 노출되어있는지 쉽게 검사할 수 있고 각 메소드가 기대한 대로 작동하는지를 테스트 할 수 있다.
일단 원격 메소드가 올바르게 실행되었다면 DWR의 JavaScript 스텁을 사용하여 클라이언트 측 코드에서 서버 측 객체들을 호출할 수 있다.
원격 객체 호출하기
원격 자바 객체 메소드와 이에 상응하는 JavaScript 스텁 함수들간 매핑은 간단하다. 일반적인 폼은 JavaScriptName.methodName(methodParams ..., callBack) 인데, 여기에서 JavaScriptName 은 creator 의 javascript 애트리뷰트로 지정된 이름이고, methodParams 는 자바 메소드의 n 매개변수이며, callback 은 자바 메소드의 리턴 값과 함께 호출 될 JavaScript 함수이다. Ajax를 잘 알고 있다면 이 콜백 메커니즘이 XMLHttpRequest 의 비동기화에 유용한 접근 방식이라는 것을 알 수 있을 것이다.
예제 시나리오에서, Listing 3의 JavaScript 함수를 사용하여 검색 결과로 UI를 검색 및 업데이트 한다. 이 리스팅은 DWR의 util.js 에서 편리한 함수를 사용한다. $() 라는 JavaScript 함수에 주목하라. 이것은 document.getElementById() 의 다른 버전이라고 생각하면 된다. 타이핑 하기 쉽다. 프로토타입 JavaScript 라이브러리를 사용했다면, 이 함수가 익숙할 것이다. Listing 3. 클라이언트에서 원격 findItems() 메소드 호출하기
/*
* Handles submission of the search form
*/
function searchFormSubmitHandler() {
// Obtain the search expression from the search field
var searchexp = $("searchbox").value;
// Call remoted DAO method, and specify callback function
catalog.findItems(searchexp, displayItems);
// Return false to suppress form submission
return false;
}
/*
* Displays a list of catalog items
*/
function displayItems(items) {
// Remove the currently displayed search results
DWRUtil.removeAllRows("items");
if (items.length == 0) {
alert("No matching products found");
$("catalog").style.visibility = "hidden";
} else {
DWRUtil.addRows("items",items,cellFunctions);
$("catalog").style.visibility = "visible";
}
} |
위 searchFormSubmitHandler() 함수에서, 재미있는 코드는 무엇보다도 catalog.findItems(searchexp, displayItems); 이다. 이 한 줄의 코드는 네트워크를 통해 XMLHttpRequest 를 DWR 서블릿으로 보내고 원격 객체의 응답과 함께 displayItems() 함수를 호출할 때 필요하다.
displayItems() 콜백 그 자체는 Item 의 어레이와 함께 호출된다. 이 어레이는 DWRUtil.addRows() 함수로 전달되고, 전개할 테이블의 아이디와 함수의 어레이가 함께 전달된다. 각 테이블 행(row)에는 셀들이 있기 때문에 이 어레이에는 많은 함수들이 있다. 각 함수는 어레이에서 Item 과 함께 호출되고 상응하는 셀에 전개될 콘텐트를 리턴해야 한다.
이 경우, 이 아이템 테이블의 각 행이 아이템의 이름, 디스크립션, 가격은 물론, 마지막 칼럼에는 아이템용 Add to Cart 버튼을 디스플레이 하도록 해야 한다. Listing 4는 이를 수행하는 셀 함수 어레이 모습이다. Listing 4. 아이템 테이블을 전개하는 셀 함수 어레이
/*
* Array of functions to populate a row of the items table
* using DWRUtil's addRows function
*/
var cellFunctions = [
function(item) { return item.name; },
function(item) { return item.description; },
function(item) { return item.formattedPrice; },
function(item) {
var btn = document.createElement("button");
btn.innerHTML = "Add to cart";
btn.itemId = item.id;
btn.onclick = addToCartButtonHandler;
return btn;
}
];
|
처음 세 개의 함수들은 dwr.xml의 Item 의 convertor 에 포함된 필드의 콘텐트를 리턴한다. 마지막 함수는 버튼을 만들고, Item 의 아이디를 여기에 붙이고, addToCartButtonHandler 함수가 버튼이 클릭될 때 호출되도록 지정한다. 이 함수는 두 번째 유스 케이스에 대한 엔트리 포인트이다. Item 을 쇼핑 카트에 추가한다.
쇼핑 카트 구현하기
|
DWR의 보안
DWR은 보안도 염두 해 두었다. dwr.xml을 사용하여 원격화 할 클래스와 메소드만 리스팅하면 악용될 수 있는 기능의 노출을 피할 수 있다. 게다가, 디버그 Test Mode를 사용하면 웹에 노출된 모든 클래스와 메소드를 검사할 수 있다.
DWR은 또한 역할 기반 보안을 지원한다. creator 설정을 통해 특정 빈에 액세스 하기 위해 사용자가 갖춰야 하는 J2EE 역할을 지정할 수 있다. URL 보안이 된 DWRServlet 인스턴스와 dwr.xml config 파일을 전개하여 다양한 사용자들에게 다양한 원격 기능들을 제공할 수 있다. | |
사용자 쇼핑 카트의 자바 구현은 Map 에 기반하고 있다. Item 이 카트에 추가될 때, Item 은 키로서 Map 으로 삽입된다. 그 Map 에서 상응하는 값은 카트에 있는 Item 의 양을 나타내는 Integer 이다. 따라서 Cart.java 는 Map<Item,Integer> 로 선언된 contents 라는 필드를 갖고 있다.
해시(hash) 키로서 복합 유형을 사용하면 DWR에 문제가 생긴다. JavaScript에서, 어레이 키는 리터럴이어야 한다. 결과적으로, contents Map 은 DWR에 의해 그 자체로 변환될 수 없다. 하지만, 쇼핑 카트 UI의 목적 상, 모든 사용자는 카트에 있는 각 아이템의 이름과 양을 봐야 한다. 따라서 getSimpleContents() 메소드를 Cart 에 추가하여, contents Map 을 취해 단순화된 Map<String,Integer> 를 구현하여, 각 Item 의 이름과 양만 나타내도록 하였다. 이러한 스트링 키 map 구현은 DWR의 빌트인 컨버터에 의해서 JavaScript로 변환될 수 있다.
클라이언트가 관심을 갖고 있는 Cart 의 다른 필드는 totalPrice 이다. 이것은 쇼핑 카트에 있는 모든 것의 총합을 나타낸다. Item 과 마찬가지로, 숫자로 된 총합을 사전 포맷 된 String 으로 나타낸 formattedTotalPrice 을 제공했다.
카트 변환하기
콘텐트를 얻기 위해서 그리고 최종 가격을 알기 위해, 클라이언트 코드가 Cart 로 두 번의 호출을 하도록 하는 대신, 이 모든 데이터를 클라이언트로 한번에 보낸다. 이를 위해서, 이상하게 보이는 메소드를 추가했다. (Listing 5): Listing 5. Cart.getCart() 메소드
/**
* Returns the cart itself - for DWR
* @return the cart
*/
public Cart getCart() {
return this;
}
|
이 메소드는 정상적인 자바 코드에서는 과잉이지만(메소드를 호출하면 Cart 에 대한 참조가 이미 존재한다.) DWR 클라이언트가 Cart 가 스스로를 JavaScript로 직렬화 시킬 수 있도록 한다.
getCart() 외에, 원격화 될 다른 메소드는 addItemToCart() 이다. 이 메소드는 카탈로그 아이템의 아이디의 String 구현을 취해서, 이 아이템을 Cart 에 추가하고, 총 합을 업데이트 한다. 이 메소드는 또한 Cart 를 리턴하여, 클라이언트 코드가 Cart 콘텐트를 업데이트 하고 하나의 연산으로 새로운 상태를 받을 수 있다.
Listing 6은 확장된 dwr.xml config 파일로서, Cart 클래스를 원격화 하는데 필요한 여분의 config 정보가 포함되어 있다: Listing 6. Cart 클래스를 통합하는 변경된 dwr.xml
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
<allow>
<create creator="new" javascript="catalog">
<param name="class"
value="developerworks.ajax.store.CatalogDAO"/>
<include method="getItem"/>
<include method="findItems"/>
</create>
<convert converter="bean"
match="developerworks.ajax.store.Item">
<param name="include"
value="id,name,description,formattedPrice"/>
</convert>
<create creator="new" scope="session" javascript="Cart">
<param name="class"
value="developerworks.ajax.store.Cart"/>
<include method="addItemToCart"/>
<include method="getCart"/>
</create>
<convert converter="bean"
match="developerworks.ajax.store.Cart">
<param name="include"
value="simpleContents,formattedTotalPrice"/>
</convert>
</allow>
</dwr> |
이 버전의 dwr.xml 에서, Cart 에 creator 와 convertor 를 추가했다. create 엘리먼트는 addItemToCart() 와 getCart() 메소드가 원격화 되고, 생성된 Cart 인스턴트가 사용자 세션에 놓이도록 지정한다. 결과적으로, 카트의 콘텐트는 사용자 요청 사이에 되풀이 된다.
Cart 용 convert 엘리먼트는 원격 Cart 메소드가 Cart 자체를 리턴하기 때문에 필요하다. 직렬화 된 JavaScript에 나타나야 하는 Cart 의 멤버는 simpleContents 맵과 formattedTotalPrice 스트링이다.
약간 혼란스럽다면, create 엘리먼트는 DWR 클라이언트에 의해 호출될 수 있는 Cart 에 대한 서버 측 메소드를 지정하고, convert 엘리먼트는 Cart 의 JavaScript 직렬화에 포함될 멤버를 지정한다는 것을 기억하면 된다.
이제, 원격 Cart 메소드를 호출하는 클라이언트 측 코드를 구현할 수 있다.
원격 Cart 메소드 호출하기
무엇보다도, 스토어 웹 페이지가 처음 로딩되면, 세션에 저장된 Cart 의 상태를 검사하고 싶다. 사용자가 아이템들을 Cart 에 추가하고 페이지를 리프레쉬 하거나 다시 검색할 수 있기 때문에 이는 필요하다. 이러한 상황에서, 재 로딩된 페이지는 세션에서 Cart 데이터와 연결되어야 한다. 나는 이 페이지의 함수에 수행된 호출을 사용했다: Cart.getCart(displayCart) . displayCart() 는 서버에서 Cart 응답 데이터와 함께 호출된 콜백 함수이다.
Cart 가 이미 세션에 있다면 creator 는 이를 가져오고 이것의 getCart() 메소드를 호출할 것이다. 어떤 Cart 도 세션에 없다면 creator 는 새로운 것을 인스턴스화 하여, 이것을 세션에 두고, getCart() 메소드를 호출한다.
Listing 7은 아이템의 Add to Cart 버튼이 클릭될 때 호출되는 addToCartButtonHandler() 함수의 구현 모습이다: Listing 7. addToCartButtonHandler() 구현
/*
* Handles a click on an Item's "Add to Cart" button
*/
function addToCartButtonHandler() {
// 'this' is the button that was clicked.
// Obtain the item ID that was set on it, and
// add to the cart.
Cart.addItemToCart(this.itemId,displayCart);
}
|
모든 통신을 책임지고 있는 DWR을 사용하여 클라이언트에서의 카트에 추가하기 작동은 문자 그대로 한 줄의 함수이다. Listing 8은 최종 가격이다. Cart 의 상태로 UI를 업데이트 하는 displayCart() 콜백의 구현이다: Listing 8. displayCart() 구현
/*
* Displays the contents of the user's shopping cart
*/
function displayCart(cart) {
// Clear existing content of cart UI
var contentsUL = $("contents");
contentsUL.innerHTML="";
// Loop over cart items
for (var item in cart.simpleContents) {
// Add a list element with the name and quantity of item
var li = document.createElement("li");
li.appendChild(document.createTextNode(
cart.simpleContents[item] + " x " + item
));
contentsUL.appendChild(li);
}
// Update cart total
var totalSpan = $("totalprice");
totalSpan.innerHTML = cart.formattedTotalPrice;
}
|
simpleContents 는 String 을 숫자로 매핑하는 JavaScript 어레이라는 것을 기억하라. 각 스트링은 아이템의 이름이고, 연관 어레이의 상응하는 숫자는 카트에 있는 아이템의 수량이다. 따라서 cart.simpleContents[item] + " x " + item 식은 "2 x Oolong 128MB CF Card "로 계산된다.
DWR Store 애플리케이션
그림 3은 DWR 기반의 Ajax 애플리케이션 실행 모습이다. 오른쪽에 사용자 쇼핑 카트와 검색된 아이템들을 디스플레이 하고 있다: 그림 3. DWR 기반의 Ajax 스토어 애플리케이션 실행 모습
DWR의 장단점
|
함수의 일괄처리
DWR에서, 여러 원격 호출이 하나의 HTTP 요청과 함께 서버로 보내질 수 있다. DWREngine.beginBatch() 를 호출하면 DWR에게 후속 원격 호출들을 바로 보내지 말라고 명령하는 것이다. 이들을 하나의 일괄 요청으로 묶지 않는다. DWREngine.endBatch() 을 호출하면, 일괄 요청이 서버로 보내진다. 원격 호출은 서버 측에서 순서대로 실행되고, 각 JavaScript 콜백이 실행된다.
일괄 작업은 두 가지 방식으로 레이턴시를 줄일 수 있다. 우선, XMLHttpRequest 객체를 만들고, 각 호출에 대해 HTTP 연결을 만드는 오버헤드를 피할 수 있다. 또한, 실행 환경에서, 웹 서버는 많은 동시 HTTP 요청들을 다룰 필요가 없기 때문에 응답 시간이 빨라진다. | |
DWR을 사용하여 Ajax 애플리케이션을 구현하기가 얼마나 쉬운지를 배웠다. 이 샘플 시나리오는 단순하고 유스 케이스를 구현하는데 매우 단순한 접근 방식을 취했지만, DWR의 역할을 과소 평가해서는 안된다. 이전 글에서, Ajax 요청과 응답을 직접 설정하고 자바 객체 그래프를 JSON으로 변환하는 과정을 설명했지만, 여기에서는 DWR이 이 모든 작업을 수행했다. 나는 50줄 미만의 JavaScript를 작성하여 클라이언트를 구현했고, 서버 측에서도 내가 했던 일은, 나의 JavaBean에 두 개의 추가 메소드만 추가했을 뿐이다.
물론, 모든 기술에는 단점도 있기 마련이다. RPC 메커니즘과 마찬가지로, DWR에서도 원격 객체로 하는 호출이 로컬 함수 호출보다 훨씬 더 비싸다는 것을 쉽게 잊는다. DWR은 Ajax를 숨기는 일은 잘 하지만, 네트워크는 투명하지 않다는 것을 기억해야 한다. DWR 호출에는 레이턴시가 있고, 애플리케이션은 대단위 원격 메소드가 되도록 설계되어야 한다. 이러한 이유로 addItemToCart() 가 Cart 자체를 리턴한다. addItemToCart() 를 유효 메소드로 만드는 것이 더 자연스럽지만, 각 DWR 호출 다음에는 getCart() 가 호출되어 변경된 Cart 상태를 검색한다.
DWR은 호출 일괄처리 시 레이턴시 문제에 대한 자체 해결책을 갖고 있다. (함수 일괄처리 사이드 바 참조) 애플리케이션에 맞는 대단위 Ajax 인터페이스를 줄 수 없다면 호출 일괄처리를 사용하여 여러 원격 호출들을 하나의 HTTP 요청으로 묶는다.
영역의 분리
본질상, DWR은 클라이언트 측과 서버 측 코드간 강결합을 만든다. 우선, 원격 메소드의 API 변경은 DWR 스텁을 호출하는 JavaScript로 반영되어야 한다. 두 번째(더 중요한 것은), 이 커플링이 클라이언트 측 영역이 서버 측 코드로 누수 되어야 한다. 예를 들어, 모든 자바 유형들이 JavaScript로 변환될 수 있는 것은 아니기 때문에 자바 객체에 추가 메소드를 추가하여 보다 쉽게 원격화 될 수 있도록 한다. 예제 시나리오에서, Cart 에 getSimpleContents() 메소드를 추가하여 이 문제를 해결했다. 또한 getCart() 메소드를 추가했는데, 이것은 DWR 시나리오에서는 유용하지만 완전한 과잉이다. 원격 객체에 대한 대단위 API가 필요하고, 특정 자바 유형을 JavaScript로 변환하는 문제 때문에, 원격화된 JavaBean이 Ajax 클라이언트에만 유용한 메소드로 인해 어떻게 오염되는지를 볼 수 있다.
이를 해결하기 위해, 래퍼 클래스를 사용하여 여분의 DWR 스팩의 메소드를 JavaBean에 추가한다. JavaBean 클래스의 자바 클라이언트는 원격화와 관련된 과잉이 더 이상 없고, 원격화된 메소드에 보다 친숙한 이름을 줄 수 있다. 예를 들어, getFormattedPrice() 대신 getPrice() 로 한다. 그림 4는 Cart 를 래핑하여 여분의 DWR 기능을 추가하는 RemoteCart 클래스 모습이다: 그림 4. 원격 기능을 위해 Cart를 래핑하는 RemoteCart
마지막으로, DWR Ajax 호출은 비동기식이고, 이들이 파견된 순서대로 리턴 되리라고 기대해서는 안된다. 예제 코드에서 작은 문제를 무시했지만, 이 시리즈의 첫 번째 글에서는 순서 없이 도착하는 데이터에 대한 방어책으로서 응답에 타임스탬프를 실행하는 방법을 설명했다.
맺음말
DWR은 많은 기능을 한다. 서버 측 도메인 객체에 대한 인터페이스를 빠르고 간단하게 만든다. 서블릿 코드, 객체 직렬화 코드, 클라이언트-측 XMLHttpRequest 코드를 작성할 필요가 없다. DWR을 사용하면 웹 애플리케이션으로의 전개도 매우 간단하고, DWR의 보안 기능은 J2EE 역할 기반 인증 시스템과 통합될 수 있다. DWR이 모든 애플리케이션 아키텍처에 적용되는 것은 아니지만 도메인 객체의 API 디자인에 고려해볼 만한 가치가 있다.
Ajax와 DWR의 장단점에 대해 보다 자세히 알고 싶다면 직접 다운로드 하여 시험해 보기 바란다. 여기에서 미처 다루지 못한 DWR의 많은 기능들이 있지만, 이 글에 소개된 소스 코드는 DWR을 시작할 수 있는 좋은 출발점이 된다. 참고자료 섹션에는 Ajax, DWR, 관련 기술들이 보다 자세하게 설명되어 있다.
가장 중요한 포인트는 Ajax 애플리케이션에는 정해진 하나의 솔루션이 없다는 것이다. Ajax는 새로운 기술들을 사용하는 개발 분야이다. 이 시리즈를 통해서 Ajax 애플리케이션의 웹 티어에서 자바 기술을 활용하는 방법에 초점을 맞췄다. XMLHttpRequest -기반 방식에 객체 직렬화 프레임웍을 선택하든, 아니면 DWR의 고급 추상화를 사용했든 상관은 없다. 다음 달, Ajax for Java developers 시리즈를 기대해주기 바란다.
기사의 원문보기
다운로드 하십시오
설명 |
이름 |
크기 |
다운로드 방식 |
DWR source code |
j-ajax3dwr.zip |
301 KB |
FTP |
참고자료 교육
제품 및 기술 얻기
토론
필자소개
|
|
|
Philip McCarthy는 자바와 웹 기술 전문 소프트웨어 개발 컨설턴트이다. 현재는 Hewlett Packard 연구소와 Orange에서 디지털 미디어 및 텔레콤 프로젝트에 참여하고 있으며, City of London에서 금융 소프트웨어 관련 작업을 하고 있다. philmccarthy@gmail.com | |