달력

5

« 2024/5 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2007. 1. 29. 14:49

JSTL 기초, Part 2: 핵심에 접근하기 그거/Java2007. 1. 29. 14:49

커스텀 태그를 이용한 플로우 제어와 URL 관리

developerWorks

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



난이도 : 초급

Mark Kolb, 소프트웨어 엔지니어

2003 년 3 월 18 일
2003 년 8 월 08 일 수정

SP Standard Tag Library (JSTL) core 라이브러리는 이름이 말해주듯이, 범위(scoped) 변수를 관리하고 URL과 인터랙팅하는 등의 기본 기능과, 반복과 조건화 같은 근본적인 작동에 필요한 커스텀 태그를 제공한다. 이러한 태그들은 페이지 작성자가 직접 사용하기도 하지만 다른 JSTL 라이브러리와 함께 복잡한 표현 로직에 대한 토대를 제공한다.

이 시리즈의 첫 번째 글에서 JSTL을 처음 보았을 것이다. 거기에서 데이터에 액세스 하고 작동하기 위해 expression language (EL)의 사용법을 설명했다. 여러분이 배운 것 처럼 EL은 JSTL 커스텀 태그의 애트리뷰트에 동적 값을 할당하는데 사용된다. 따라서 빌트인 액션과 기타 커스텀 태그 라이브러리용 요청 시간 애트리뷰트 값을 지정하는 JSP 식과 같은 역할을 한다.

EL의 사용법을 설명하기 위해, core 라이브러리에서 세 개의 태그 (<c:set>, <c:remove>, <c:out>)를 소개했었다. <c:set><c:remove>는 범위 변수를 관리하는데 사용된다. <c:out>은 특별히 EL을 사용하여 계산된 값인 데이터 디스플레이에 사용된다. 기초 학습을 토대로 이제는 core 라이브러리의 나머지 태그를 볼 것이다. 두 가지 주요 카테고리로 나뉜다: 플로우 제어(플로우 제어)와 URL 관리.

예제 애플리케이션

JSTL 태그를 설명하기 위해서 실제 애플리케이션 예제를 사용할 것이다. 대중성과 인지도가 높아졌기 때문에 간단한 자바 기반의 Weblog를 사용할 것이다. (참고자료에서 JSP 페이지와 소스 코드를 다운로드 한다.) Weblog (blog)은 웹 기반 저널로서 Weblog의 작성자가 흥미를 가질만한 주제들에 대한 짧은 주석이다. 일반적으로 웹 상의 아티클이나 토의가 있는 곳 어디든 연결된다. 그림 1은 실행 중인 애플리케이션의 모습이다.



그림 1. Weblog 애플리케이션
Screenshot of the Weblog 예제 애플리케이션

스무 개 정도의 자바 클래스가 전체 구현에 필요하지만 Weblog 애플리케이션 클래스에서는 단 두개(EntryUserBean)만이 프리젠테이션 레이어에 사용된다. JSTL 예제를 이해하려면 이들 두 개의 클래스가 필요하다. 그림 2는 EntryUserBean의 클래스 다이어그램이다.



그림 2. Weblog 애플리케이션(클래스 다이어그램)
Class diagram for Weblog 예제 애플리케이션

Entry 클래스는 Weblog 내의 날짜가 나와있는 엔트리를 나타낸다. 이것의 id 애트리뷰트는 데이터베이스 내의 엔트리를 저장하고 검색하는데 사용된다. 반면 titletext 애트리뷰트는 엔트리의 실제 콘텐트를 나타낸다. 자바 Date 클래스 중 두 개의 인스턴스는 createdlastModified 애트리뷰트에 의해 레퍼런스되며 엔트리가 처음으로 만들어지고 마지막으로 편집 될 때 나타난다. author 애트리뷰트는 UserBean 인스턴스를 참조하면서 엔트리를 만든 사람을 나타낸다.

The UserBean 클래스는 애플리케이션의 권한이 있는 사용자 정보(사용자 이름, 성명, 이메일 주소)를 저장한다. 이 클래스에는 관련 데이터베이스와 인터랙팅하기 위한 id 애트리뷰트도 포함되어 있다. 마지막 애트리뷰트인 rolesString 값 리스트를 참조하면서 애플리케이션 역할과 이에 상응하는 사용자를 구분한다. Weblog 애플리케이션의 경우 일반적인 역할은 "User" (모든 일반적인 애플리케이션 사용자 역할)와 "Author" (Weblog 엔트리를 만들고 편집할 수 있는 사용자를 지정하는 역할) 이다.

본 시리즈의 다른 주제들

Part 1, "expression language" (2003년 2월) Part 3, "프리젠테이션이 모든 것이다!" (2003년 4월) Part 4, "SQL과 XML 콘텐트에 접근하기" (2003년 5월)




위로


플로우 제어(Flow control)

동적 애트리뷰트 값을 지정하는데 JSP 식 대용으로 EL이 사용될 수 있기 때문에 스크립팅 엘리먼트를 사용할 필요가 줄어들었다. 스크립팅 엘리먼트는 JSP 페이지에서 중요한 소스가 될 수 있기 때문에 간단한 대안을 제공한다는 것은 JSTL에 있어서 큰 이점이다.

EL은 JSP 컨테이너에서 데이터를 검색하고 객체 계층을 오가며 간단한 작동을 수행한다. 데이터에 접근하여 조작하는 것 외에도 JSP 스크립팅 엘리먼트의 또 다른 사용 측면은 플로우 제어이다. 특히, 페이지 작성자가 반복되거나 조건적인 콘텐트를 구현하기 위해서 스크립틀릿을 의존한다는 것은 일반적인 일이다. 하지만 그와 같은 작동은 EL의 기능을 넘어서기 때문에, core 라이브러리는 반복, 조건화, 예외 처리 등의 형태로 플로우 제어를 관리 할 다양한 사용자 액션을 제공한다.

반복

웹 애플리케이션의 측면에서, 반복(iteration)은 데이터의 모음을 가져다가 디스플레이 하는데 주로 사용된다. 주로 테이블에 리스트나 열(row) 시퀀스의 형태로 나타난다. 반복 콘텐트를 구현하는 JSTL의 기본 액션은 <c:forEach> 커스텀 태그이다. 이 태그는 두 개의 다른 유형의 반복을 지원한다: 정수 범위내의 반복(이를 테면, 자바의 for 문)과 컬렉션 내의 반복(자바의 IteratorEnumeration 클래스).

정수 범위 내에서 반복하려면 <c:forEach>(Listing 1)의 커스텀 태그의 신택스가 사용된다. beginend 애트리뷰트는 정적 정수 값 또는 정수 값을 계산하는 수식이 되어야한다. 이들은 각각 반복을 위한 인덱스의 초기 값과 반복이 멈추는 지점의 인덱스 값을 지정한다. <c:forEach>를 사용하여 정수 범위에서 반복할 때, 이 두개의 애트리뷰트가 필요하며 다른 모든 것들은 선택사항이다.



Listing 1. <c:forEach> 액션을 통한 반복 신택스
<c:forEach var="name" varStatus="name"
begin="expression" end="expression" step="expression">
body content
</c:forEach>

		

step 애트리뷰트 또한 정수 값을 갖고 있어야한다. 매번 반복한 후에 인덱스에 추가될 양(amount)을 정한다. 따라서 반복 인덱스는 begin 애트리뷰트 값에서 시작하고 step 애트리뷰트의 값에 의해 증가하며 end 애트리뷰트의 값을 초과할 때 정지한다. step 애트리뷰트가 생략되면 step 크기는 1로 초기화된다.

var 애트리뷰트가 지정되면 지정된 이름을 가진 범위 변수가 만들어지고 인덱스의 현재 값으로 할당된다. 이 범위 변수는 <c:forEach> 태그의 바디 내에서 액세스 될 수 있다. Listing 2는 <c:forEach> 액션의 예제이다.



Listing 2. <c:forEach> 태그
<table>
<tr><th>Value</th>
    <th>Square</th></tr>
<c:forEach var="x" begin="0" end="10" step="2">
  <tr><td><c:out value="${x}"/></td>
      <td><c:out value="${x * x}"/></td></tr>
</c:forEach>
</table>

		

이 예제 코드는 다섯 개 짝수의 제곱을 테이블로 만들었다. 그림 3이 그 결과이다 .



그림 3. Listing 2의 결과
Output of Listing 2

컬렉션의 멤버들 사이를 반복할 때 <c:forEach> 태그의 추가 애트리뷰트인 items 애트리뷰트가 사용된다. (Listing 3). 이러한 형식의 <c:forEach> 태그를 사용할 때, items 애트리뷰트는 유일하게 필요한 애트리뷰트이다.



>Listing 3. <c:forEach> 액션을 통한 반복 신택스
<c:forEach var="name" items="expression" varStatus="name"
 begin="expression" end="expression" step="expression">
  body content
</c:forEach>			
		

자바 플랫폼에서 제공되는 표준 컬렉션 타입은 <c:forEach> 태그에 의해 지원된다. 어레이 엘리먼트를 통해 반복할 때 이 액션을 사용할 수 있다. 표 1은 items 애트리뷰트에 의해 지원되는 값들의 리스트이다. 테이블의 마지막 열이 표시될 때, JSTL은 이것의 인터페이스(javax.servlet.jsp.jstl.sql.Result)를 정의한다.

표 1. <c:forEach> 태그의 items 애트리뷰트에서 지원되는 컬렉션

items item 값의 결과
java.util.Collection 호출에서 iterator()까지의 엘리먼트
java.util.Map java.util.Map.Entry의 인스턴스
java.util.Iterator Iterator 엘리먼트
java.util.Enumeration Enumeration 엘리먼트
Object 인스턴스 어레이 Array 엘리먼트
초기 값들의 어레이 래핑된 어레이 엘리먼트
콤마로 나뉘어진 String 서브스트링
javax.servlet.jsp.jstl.sql.Result SQL 쿼리의 열(row)

Listing 4는 컬렉션을 통한 반복에 사용되는 <c:forEach> 태그이다. entryList 라는 범위 변수가 Entry 객체의 리스트로 설정되었다. <c:forEach> 태그가 이 리스트의 각 엘리먼트를 처리한다. blogEntry 라는 범위 변수로 이것을 할당하고 두 개의 테이블 열을 만든다. 하나는 Weblog 엔트리의 title 이고 다른 하나는 이것의 text이다. 이 속성들은 한 쌍의 <c:out> 액션과 이에 상응하는 EL 식을 통해 blogEntry 변수에서 검색된다. Weblog 엔트리의 타이틀과 텍스트에 HTML이 포함되어있기 때문에 <c:out>escapeXml 애트리뷰트는 false로 설정된다. (그림 4).



Listing 4. <c:forEach> 태그를 사용하여 Weblog 엔트리 디스플레이 하기
<table>
  <c:forEach items="${entryList}" var="blogEntry">
    <tr><td align="left" class="blogTitle">
      <c:out value="${blogEntry.title}" escapeXml="false"/>
    </td></tr>
    <tr><td align="left" class="blogText">
      <c:out value="${blogEntry.text}" escapeXml="false"/>
    </td></tr>
  </c:forEach>
</table>
		



그림 4. Listing 4의 결과
Output of Listing 4

남아있는 <c:forEach> 애트리뷰트인 varStatus는 정수 범위의 반복이나 컬렉션 범위의 반복에서 똑같은 역할을 한다. var 애트리뷰트와 마찬가지로, varStatus는 범위 변수를 만드는데 사용된다. 현재 인덱스 값이나 현재 엘리먼트를 저장하는 대신에 이 변수는 javax.servlet.jsp.jstl.core.LoopTagStatus 의 인스턴스로 할당된다. 이 클래스는 일련의 속성을 정의한다. (표 2).

표 2. LoopTagStatus 객체의 속성

속성 Getter Description
current getCurrent() 현재 반복 라운드 아이템
index getIndex() 현재 반복 라운드의 제로 기반(zero-based) 인덱스
count getCount() 현재 반복 라운드의 1 기반(one-based) 인덱스
first isFirst() 현재 라운드가 반복을 통한 첫 번째 패스임을 나타내는 플래그
last isLast() 반복현재 라운드가 반복을 통한 마지막 패스임을 나타내는 플래그
begin getBegin() begin 애트리뷰트의 값
end getEnd() end 애트리뷰트의 값
step getStep() step 애트리뷰트의 값

Listing 5는 varStatus 애트리뷰트가 사용되는 방법을 나타낸 예제이다. Listing 4의 코드를 수정하여 Weblog 엔트리의 숫자세기를 타이틀을 디스플레이하는 테이블 열에 추가한다. 이것은 varStatus 애트리뷰트의 값을 지정하고 결과 범위 변수의 카운트 속성에 액세스 하면 된다. 결과는 그림 5 이다.



Listing 5. varStatus 애트리뷰트를 사용하여 Weblog 엔트리의 카운트 디스플레이하기
<table>
  <c:forEach items=
    "${entryList}" var="blogEntry" varStatus="status">
    <tr><td align="left" class="blogTitle">
      <c:out value="${status.count}"/>.
      <c:out value="${blogEntry.title}" escapeXml="false"/>
    </td></tr>
    <tr><td align="left" class="blogText">
      <c:out value="${blogEntry.text}" escapeXml="false"/>
    </td></tr>
  </c:forEach>
</table>

		



그림 5. Listing 5의 결과
Output of Listing 5

<c:forEach> 이외에도, core 라이브러리는 두 번째 반복 태그인 <c:forTokens>를 제공한다. 이것의 액션은 자바의 StringTokenizer 클래스의 JSTL 이다. <c:forTokens> 태그(Listing 6)는 컬렉션 지향 버전의 <c:forEach>와 같은 애트리뷰트를 갖고 있다. <c:forTokens>의 경우 토큰화 될 스트링은 items 애트리뷰트를 통해 지정되는 반면 토큰을 만드는데 사용되는 지정자(deliniter)는 delims 애트리뷰트를 통해 제공된다. <c:forEach> 경우와 마찬가지로, begin, end, step애트리뷰트를 사용하여 토큰이 상응하는 인덱스 값들과 매칭되는 것에 프로세스 되도록 제한 할 수 있다.



Listing 6. <c:forTokens> 액션
<c:forTokens var="name" items="expression"
    delims="expression" varStatus="name"
    begin="expression" end="expression" step="expression">
  body content
</c:forTokens>

		

조건화

동적 콘텐트를 포함하고 있는 웹 페이지라면 다양한 형식의 콘텐트를 볼 수 있는 다양한 사용자 카테고리가 필요할 것이다. Weblog에서 방문자들은 엔트리를 읽고 피드백을 제출 할 뿐만 아니라 권한을 받은 사용자는 새로운 엔트리를 게시하거나 기존 콘텐트를 편집할 수 있어야 한다.

JSP 페이지 내에 그러한 기능을 구현하고 리퀘스트 기반으로 디스플레이 하고자하는 것을 제어하도록 조건 로직을 사용함으로서 가용성과 소프트웨어 관리는 향상된다. core 라이브러리는 두 개의 다른 조건화 태그인 <c:if><c:choose>를 제공하는데 다음의 기능들을 구현한다.

이 두 가지 액션 중 좀더 단순한 <c:if>는 간단한 테스트 식을 계산한 다음 식이 true로 되었을 때만 바디 콘텐트를 처리한다. 그렇지 않다면 태그의 바디 콘텐트는 무시된다. Listing 7에서 보듯, <c:if>는 테스트의 결과를 varscope 애트리뷰트를 통해 범위 변수로 할당할 수 있다. 이 기능은 테스트 비용이 비쌀 경우 유용하다. 결과는 범위 변수에 캐시되고 <c:if>나 다른 JSTL 태그로의 연속 호출시에 검색된다.



Listing 7. <c:if> 조건 액션 신택스
<c:if test="expression" var="name" scope="scope">
  body content
</c:if>

		

Listing 8은 <c:forEach> 태그의 LoopTagStatus 객체의 first 속성으로 사용된 <c:if> 를 보여준다. 이 경우, 그림 6 에서 보듯, Weblog 엔트리의 구현 날짜는 첫 번째 엔트리 위에 디스플레이 된다. 하지만 다른 엔트리 앞에 반복되지 않는다.



Listing 8. <c:if>를 사용하여 Weblog 엔트리 날짜 디스플레이 하기
<table>
<c:forEach items=
"${entryList}" var="blogEntry" varStatus="status">
<c:if test="${status.first}">
<tr><td align="left" class="blogDate">
<c:out value="${blogEntry.created}"/>
</td></tr>
</c:if>
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
 </td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>

		



그림 6. Listing 8의 결과
Output of Listing 8

Listing 8 처럼, <c:if> 태그는 조건화된 콘텐트에 대해 매우 간략한 노트를 제공한다. 디스플레이 되어야하는 콘텐트가 무엇인지를 결정해야하는 중립적인 테스트가 필요할 경우, JSTL core 라이브러리는 <c:choose> 액션을 제공한다. <c:choose> 신택스는 Listing 9와 같다.



Listing 9. <c:choose> 액션 신택스
<c:choose>
  <c:when test="expression">
    body content
  </c:when>
  ...
  <c:otherwise>
    body content
  </c:otherwise>
</c:choose>

		

테스트 되는 각 조건은 상응하는 <c:when> 태그에 의해 나타난다. testtrue로 평가된 첫 번째 <c:when> 태그의 콘텐트만 프로세스된다. 어떤 <c:when> 테스트도 true로 리턴되지 않으면 <c:otherwise> 태그의 바디 콘텐트가 프로세스 된다. <c:otherwise> 태그가 선택적이라는 것을 주목하라. <c:choose> 태그는 최대 한 개의 중첩 <c:otherwise> 태그를 가질 수 있다. 모든 <c:when> 테스트가 false가 되고 어떤 <c:otherwise> 액션도 나타나지 않으면 <c:choose> 바디 콘텐트는 프로세스 되지 않는다.

Listing 10은 <c:choose> 태그의 실행 예제이다. 여기에서 프로토콜 정보는 리퀘스트 객체에서 검색되고 간단한 스트링 비교를 사용하여 테스트된다. 테스트 결과에 따라 상응하는 텍스트 메시지가 디스플레이된다.



Listing 10. <c:choose>를 이용한 콘텐트 조건화
<c:choose>
  <c:when test="${pageContext.request.scheme eq 'http'}">
    This is an insecure Web session.
  </c:when>
  <c:when test="${pageContext.request.scheme eq 'https'}">
    This is a secure Web session.
  </c:when>
  <c:otherwise>
    You are using an unrecognized Web protocol. How did this happen?!
  </c:otherwise>
</c:choose>
		

예외 처리

마지막 플로우 제어 태그는 <c:catch>이다. 이것은 JSP 페이지 내에서 기본적인 예외처리를 담당한다. 좀더 구체적으로 말하면 이 태그의 바디 콘텐트 내에서 발생하는 모든 예외가 잡히면 무시된다. 하지만 예외가 발생하고 <c:catch> 태그의 선택적인 var 애트리뷰트가 지정되면 예외는 지정된 변수로 할당되어 페이지 자체 내에서 에러 처리를 할 수 있다. Listing 11은 <c:catch>의 신택스이다. (예제는 Listing 18이다)



Listing 11. <c:catch> 실행 신택스
<c:catch var="name">
  body content
</c:catch>
		




위로


URL 작동

JSTL core 라이브러리의 나머지 태그는 URL에 초점을 맞춘다. 이 중 첫 번째는 <c:url> 태그인데 URL 생성에 사용된다. 특히, <c:url>은 J2EE 웹 애플리케이션용 URL을 구현할 때 중요하게 쓰이는 세 가지 엘리먼트를 제공한다:

  • 현재 서블릿 콘텍스트 이름이 됨
  • 세션 관리를 위한 URL 재작성
  • 요청 매개변수 이름과 값의 URL 인코딩

value 애트리뷰트가 사용되었다. 기본 URL을 지정하기 위해서. 태그는 필요할 경우 변형한다. 이 기본 URL이 포워드 슬래시로 시작하면 서블릿 콘텍스트 이름이 만들어진다. 구체적인 콘텍스트 이름은 context 애트리뷰트를 사용하여 제공될 수 있다. 이 애트리뷰트가 생략되면 현재 서블릿 콘텍스트 이름이 사용된다. 서블릿 콘텍스트 이름이 개발 보다는 전개 시에 결정될 때 유용하다.



isting 12. <c:url> 작동 신택스
<c:url value="expression" context="expression"
    var="name" scope="scope">
  <c:param name="expression" value="expression"/>
  ...
</c:url>

		

URL 재작성은 <c:url> 작동에 의해 자동적으로 수행된다. JSP 컨테이너가 사용자의 현재 세션 아이디를 저장하고 있는 쿠키를 검사하면 재작성은 필요없다. 쿠키가 존재하지 않으면 <c:url>로 만들어진 모든 URL은 재작성되어 세션 아이디를 인코딩한다. 계속되는 요청에도 적절한 쿠키가 존재하지 않으면 <c:url>은 이 아이디를 포함하기 위한 URL 재작성을 멈춘다.

var 애트리뷰트를 위해 값이 제공되면 생성된 URL은 특정 범위 변수의 값으로 할당된다. 그렇지 않다면 결과 URL은 현제 JspWriter를 사용하여 아웃풋이 된다. 결과를 직접 산출하는 기능은 <c:url> 태그가 값으로서 나타날 수 있도록 한다. 예를들어 HTML의 <a> 태그의 href 애트리뷰트와 같다.



Listing 13. HTML 태그용 애트리뷰트 값으로 URL 생성하기
<a href="<c:url value='/content/sitemap.jsp'/>">View sitemap</a>

		

마지막으로 모든 요청 매개변수가 중첩된 <c:param> 태그를 통해 지정되면 그들의 이름과 값은 HTTP GET 요청용 표준 표기법을 사용하여 생성된 URL에 붙여진다. 또한 URL 인코딩이 수행된다. 유효 URL을 만들어내기 위해 변형되어야하는 매개변수의 이름 또는 값에 나타나는 모든 문자는 적절히 변환된다.



Listing 14. 요청 매개변수를 가진 URL 만들기
<c:url value="/content/search.jsp">
  <c:param name="keyword" value="${searchTerm}"/>
  <c:param name="month" value="02/2003"/>
</c:url>

		

Listing 14의 JSP 코드는 blog이라는 이름의 서블릿 콘텍스트에 전개되었다. 범위 변수 searchTerm의 값은 "core library"로 설정되었다. 세션 쿠키가 탐지되면 Listing 14에서 만들어진 URL은 Listing 15와 같다.



Listing 15. 세션 쿠키가 있는 상태에서 만들어진 URL
/blog/content/search.jsp?keyword=foo+bar&month=02%2F2003

		

어떤 세션 쿠키도 없으면 Listing 16의 URL이 그 결과이다. 서블릿 콘텍스트가 프리펜드 되었고 URL 인코딩이된 요청 매개변수가 붙었다.



Listing 16. 세션 쿠키 없이 만들어진 URL
/blog/content/search.jsp;jsessionid=233379C7CD2D0ED2E9F3963906DB4290
  ?keyword=foo+bar&month=02%2F2003
		




위로


중요한 콘텐트

JSP는 두 개의 빌트인 메커니즘을 갖고 있어 다른 URL에서 온 콘텐트를 JSP 페이지로 만든다. 그것이 바로 include 지시문과 <jsp:include> 작동이다. 두 경우 모두, 포함되어야 하는 콘텐트가 페이지로서 같은 웹 애플리케이션의 부분이 되어야 한다. 두 가지 태그의 주요 차이점은 include 지시문은 페이지가 컴파일하는 동안 포함된 콘텐트를 결합하고 <jsp:include> 액션은 JSP 페이지의 요청 프로세스 동안 작동한다는 것이다.

core 라이브러리의 <c:import> 액션은 더욱 일반적이면서 강력한 버전의 <jsp:include>라 할 수 있다. <jsp:include> 처럼, <c:import>는 요청 시간 작동이고 기본 태스크는 다른 웹 리소스의 콘텐트를 JSP 페이지에 삽입하는 것이다.



>Listing 17. <c:import>작동 신택스
<c:import url="expression" context="expression"
    charEncoding="expression" var="name" scope="scope">
  <c:param name="expression" value="expression"/>
  ...
</c:import>

		

임포트 되어야하는 콘텐트용 URL은 url 애트리뷰트를 통해 지정된다. 관련 URL이 허용되고 현재 페이지의 URL에 대비하여 분해된다. url 애트리뷰트의 값은 포워드 슬래시로 시작한다. 하지만 로컬 JSP 컨테이너 내에서는 절대 URL로서 인터프리팅된다. context 애트리뷰트 값 없이는 그와 같은 절대 URL은 현재 서블릿 콘텍스트에서 리소스를 참조하는 것으로 간주된다. 명확한 콘텍스트가 context 애트리뷰트를 통해 지정되면 절대(로컬) URL은 이름을 가진 서블릿 콘텍스트에 대해 분해된다.

<c:import> 액션은 로컬 콘텐트 접근에만 제한되지 않는다. 프로토콜과 호스트 이름을 포함한 전체 URI는 url 애트리뷰트의 값으로 지정될 수 있다. 사실 프로토콜은 HTTP로 제한되지 않는다. java.net.URL 클래스로 지원되는 모든 프로토콜은 <c:import>url 애트리뷰트용 값에서 사용된다. (Listing 18).

<c:import> 액션은 FTP 프로토콜을 통해 액세스 되는 문서의 콘텐트를 포함하는데 사용된다. <c:catch> 액션은 FTP 파일 전송 동안 발생하는 모든 에러를 처리하기 위해 사용된다. <c:catch>var 변수를 사용하여 예외용 범위 변수를 지정하고 <c:if>를 사용하여 값을 검사하면 된다. 예외가 발생하면 범위 변수로의 할당이 발생한다.



Listing 18. <c:import>와 <c:catch>의 결합 예제
<c:catch var="exception">
  <c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
  Sorry, the remote content is not currently available.
</c:if>
		

<c:import> 액션의 마지막 두 개의 애트리뷰트는 varscope이다. var 애트리뷰트는 지정된 유알엘에서 가져온 콘텐트가 현재의 JSP 페이지에 포함되도록 하는 것이 아니라 변수에 저장되도록 한다. scope 애트리뷰트는 이 변수의 범위 지정을 제어하고 페이지 범위를 초기화한다.




위로


요청 리다이렉션(redirection)

마지막 core 라이브러리 태그는 <c:redirect>이다. 이 액션은 HTTP 리다이렉트 응답을 사용자 브라우저로 보내는데 사용되며, JSTL의 javax.servlet.http.HttpServletResponsesendRedirect() 메소드와 같다. 이 태그의 urlcontext 애트리뷰트 (Listing 19) 작동은 <c:import>urlcontext 애트리뷰트와 같다.



Listing 19. <c:redirect>action
<c:redirect url="expression" context="expression">
  <c:param name="expression" value="expression"/>
  ...
</c:redirect>

		

Listing 20은 <c:redirect> 작동 모습이다. Listing 18의 에러 메시지를 지정된 에러 페이지 리다이렉트로 대체한다. 이 예제에서 <c:redirect> 태그는 표준 <jsp:forward> 작동과 비슷한 방식으로 사용된다. 요청 디스패쳐를 통한 포워딩은 서버쪽에서 구현되지만 리다이렉트는 브라우저에서 수행된다. 개발자의 관점에서 보면 포워딩은 리다이렉팅보다 효율적이다. 하지만 <c:redirect> 액션이 좀더 유연하다. <jsp:forward>는 현재 서블릿 콘텍스트 내에서 다른 JSP 페이지로만 디스패치 할 수 있기 때문이다.



Listing 20. 예외에 대한 응답으로 리다이렉팅
<c:catch var="exception">
  <c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
  <c:redirect url="/errors/remote.jsp"/>
</c:if>

		

사용자 관점과의 주요 차이점은 리다이렉트가 브라우저로 디스플레이된 URL을 업데이트하고 북마크 설정에 영향을 미친다는 것이다. 반면 포워딩은 엔드유저에게 투명하다. <c:redirect><jsp:forward> 중의 선택은 사용자 경험에 따라 달라진다.




위로


참고자료




위로


필자소개

Mark Kolb는소프트웨어 엔지니어이며 Web Development with JavaServer Pages, 2nd Edition의 공동 저자이다.

:
Posted by 뽀기
2007. 1. 29. 14:48

JSTL 입문 Part 1: Expression Language 그거/Java2007. 1. 29. 14:48

JSP 애플리케이션용 MA 단순화하기

developerWorks

 

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


난이도 : 초급

Mark A. Kolb, 소프트웨어 엔지니어

2003 년 2 월 11 일
2003 년 11 월 18 일 수정

JSP Standard Tag Library (JSTL)은 일반적인 웹 애플리케이션 기능(반복(iteration)과 조건, 데이터 관리 포맷, XML 조작, 데이터베이스 액세스)을 구현하는 커스텀 태그 라이브러리 모음이다. 소프트웨어 엔지니어인 Mark Kolb은 JSTL 태그의 사용방법을 설명한다. 표현층(presentation layer)에서 소스 코드를 제거하여 소프트웨어 관리를 단순화시키는 방법도 설명한다. 이외에도 JSTL의 단순화된 Expression Language에 대한 설명도 포함되어 있다.

JavaServer Pages (JSP)는 J2EE 플랫폼을 위한 표준 표현 레이어(presentation-layer) 이다. JSP는 페이지 콘텐트를 동적으로 생성할 수 있는 전산을 수행 할 수 있는 스크립팅 엘리먼트와 액션을 제공한다. 스크립팅 엘리먼트는 프로그램 소스 코드가 JSP 코드에 포함될 수 있도록 한다. 페이지가 사용자 요청에 대한 응답으로 렌더링 될 때 실행할 목적이다. 액션(actions)은 전산 작동을 JSP 페이지의 템플릿 텍스트를 구성하고 있는 HTML 이나 XML과 거의 흡사하게하는 태그로 인캡슐한다. JSP 스팩에 표준으로 정의된 몇 가지의 액션들이 있다. 하지만 JSP 1.1 부터 개발자들은 커스텀 태그 라이브러리 형태로 자신만의 액션들을 만들 수 있다.

JSP Standard Tag Library (JSTL)는 JSP 1.2 커스텀 태그 라이브러리 모음으로서 광범위한 서버측 자바 애플리케이션에 일반적으로 쓰이는 기본 기능들을 구현한다. JSTL은 데이터 포맷, 반복 콘텐트 또는 조건 콘텐트 같은 전형적인 표현 레이어를 위한 표준 구현을 제공하기 때문에, JSP 작성자들이 애플리케이션 개발에 집중하는데 도움이 된다.

물론, 스크립틀릿, 익스프레션, 선언 같은 JSP 스크립팅 엘리먼트를 사용하는 태스크를 구현할 수 있다. 예를 들어 조건 콘텐트(conditional content)는 세 개의 스크립틀릿(Listing 1의 하이라이트 부분)을 사용하여 구현될 수 있다. 페이지 내에 프로그램 소스 코드를 임베딩하는 것에 의존하기 때문에 스크립팅 엘리먼트가 소프트웨어 관리 태스크를 매우 복잡하게 하는 경향이있더라도 JSP 페이지는 그들을 사용한다. Listing 1의 스크립틀릿 예제는 브레이스들의 적절한 매칭에 매우 의존한다. 조건화된 콘텐트 내에 추가 스크립틀릿을 중첩하는 것은 신택스 에러가 갑자기 일어났다면 페이지가 JSP 콘테이너에 의해 컴파일 될 때 결과 에러 메시지를 합리화하는 것은 도전이 될 수 있다.


Listing 1. 스크립틀릿을 통해 조건 콘텐트 구현하기

  <% if (user.getRole() == "member")) { %>
    <p>Welcome, member!</p>
<% } else { %>
    <p>Welcome, guest!</p>
<% } %>

그와 같은 프로그램을 해결하는데에는 프로그래밍 경험이 많이 필요하다. JSP 페이지의 마크업이 페이지 레이아웃과 그래픽 디자인에 익숙한 디자이너에 의해 개발 및 관리되는데 반해 그와 같은 페이지 내의 스크립팅 엘리먼트는 문제가 생길 때 프로그래머가 개입해야한다. 하나의 파일안에 있는 코드에 대한 책임을 공유하는 것은 JSP 페이지의 개발, 디버깅, 향상을 성가신일로 만든다. JSTL은 일반적인 기능을 커스텀 태그 라이브러리의 표준 세트로 패키징했기 때문에 JSP 작성자들이 스크립팅 엘리먼트에 대한 필요를 줄이고 관련된 관리 비용을 피할 수 있도록 한다.

JSTL 시리즈

Part 2, "JSTL 기초, Part 2 : 핵심에 접근하기" (2003년 3월) Part 3, "Presentation is everything" (2003년 4월) Part 4, "Accessing SQL and XML content" (2003년 5월)




위로


JSTL 1.0

002년 6월에 릴리스된 JSTL 1.0은 네 개의 커스텀 태그 라이브러리(core, format, xml, sql)와 두 개의 범용 태그 라이브러리 밸리데이터(ScriptFreeTLV & PermittedTaglibsTLV)로 구성되어 있다. core 태그 라이브러리는 커스텀 액션을 제공하여 범위 변수를 통해 데이터를 관리할 수 있도록하며 페이지 콘텐트의 반복과 조건화를 수행할 수 있도록 한다. 또한 URL에서 생성 및 작동할 수 있는 태그도 제공한다. format 태그 라이브러리는 이름이 시사하는 바와 같이 데이터 특히 숫자와 날짜를 포맷하는 액션을 정의한다. 국지화 된 리소스 번들을 사용하여 JSP 페이지의 국제화도 지원한다. xml 라이브러리에는 XML을 통해 표현된 데이터를 조작할 수 있는 테그가 포함되어 있다. sql 라이브러리는 관계형 데이터베이스를 쿼리하는 액션을 정의한다.

두 개의 JSTL 태그 라이브러리 밸리데이터는 개발자들이 JSP 애플리케이션 내에서 표준을 코딩하도록 한다. ScriptFreeTLV 밸리데이터를 설정하여 JSP 페이지 내에 있는 다양한 스크립팅 엘리먼트(스크립틀릿, 표현, 선언)의 다양한 유형을 사용하는 것을 막는다. 이와 비슷하게 PermittedTaglibsTLV 밸리데이터는 애플리케이션의 JSP 페이지들에 의해 액세스된 커스텀 태그 라이브러리(JSTL 태그 라이브러리 포함)을 제한한다.

JSTL은 J2EE 플랫폼에 필요한 컴포넌트가 될 것이지만 적은 수의 애플리케이션 서버들만이 이를 포함하고 있는 것이 현실이다. JSTL 1.0의 레퍼런스 구현은 Apache Software Foundation의 Jakarta Taglibs 프로젝트의 일부로서 사용할 수 있다. (참고자료). 레퍼런스 구현에 있는 커스텀 태그 라이브러리는 JSTL 지원을 추가하기 위해 JSP 1.2와 Servlet 2.3 이상 스팩을 지원하는 모든 애플리케이션 서버에 통합될 수 있다.




위로


Expression language

JSP 1.2에서 JSP 액션의 애트리뷰트는 정적 캐릭터 스트링이나 익스프레션을 사용하여 지정된다. 예를 들어 Listing 2의 경우 정적 값들은 <jsp:setProperty> 액션의 nameproperty 애트리뷰트를 위해 지정된다. 반면 익스프레션은 이것의 값 애트리뷰트를 지정하는데 사용된다. 이 액션은 요청 매개변수의 현재 값을 이름이 붙여진 빈 속성으로 할당하는 효과를 갖고 있다. 이러한 방식으로 사용된 익스프레션은 request-time attribute values이라 일컬어지며 애트리뷰트 값을 동적으로 지정하기위한 JSP 스팩에 내장된 유일한 메커니즘이다.


Listing 2. request-time attribute value을 결합하는 JSP 액션

<jsp:setProperty name="user" property="timezonePref"
          value='<%= request.getParameter("timezone") %>'/>


request-time attribute values가 익스프레션을 사용하여 지정되기 때문에 다른 스크립팅 엘리먼트와 같은 소프트웨어 관리 문제가 일어날 수 있다. 이런 이유로 인해 JSTL 커스텀 태그는 동적 애트리뷰트 값을 지정하기 위한 대안 메커니즘을 지원한다. JSP 익스프레션을 사용하는 것 보다 JSTL 액션용 애트리뷰트 값이 단순화 된 expression language (EL)를 사용하여 지정될 수 있다. EL은 JSP 컨테이너에 있는 데이터를 검색 및 조작할 식별자, 접근자, 연산자를 제공한다. EL은 EcmaScript(참고자료)와 XML Path Language (XPath)에 약간 의존하기 때문에 신택스는 페이지 디자이너와 프로그래머 모두 에게 익숙하다. EL은 객체와 속성들을 검색하면서 간단한 작동을 수행한다. 이것은 프로그래밍 언어도 스크립팅 언어도 아니다. JSTL 태그와 결합하면 간단하고 편리한 표기를 사용하여 복잡한 작동이 표현될 수 있다. EL 익스프레션은 달러 표시($)와 중괄호 ({})를 앞에 붙여 사용하여 범위를 정한다.(Listing 3)


Listing 3. JSTL 액션: EL 익스프레션 범위 지정

<c:out value="${user.firstName}"/>


여러개의 익스프레션들과 정적 텍스트를 결합하여 스트링 연결을 통해 동적 애트리뷰트 값을 만들 수 있다.(Listing 4). 개별 익스프레션들은 식별자, 접근자, 리터럴, 연산자로 구성되어 있다. 식별자는 데이터 센터에 저장된 데이터 객체를 참조하는데 사용된다. EL은 11 개의 식별자를 보유하고 있다. 11 개의 EL 내장 객체에 상응하는 것들이다. 다른 모든 식별자들은 범위 변수를 참조하는 것으로 간주된다. 접근자는 객체의 속성 또는 컬렉션의 엘리먼트를 검색하는데 사용된다. 리터럴은 고정된 값들(숫자, 문자, 스트링, 부울, null)을 나타낸다. 연산자는 데이터와 리터럴이 결합 및 비교될 수 있도록 한다.


Listing 4. 정적 텍스트와 여러 EL 익스프레션을 결합하여 동적 애트리뷰트 값 지정하기

<c:out value="Hello ${user.firstName} ${user.lastName}"/>





위로


범위 변수(Scoped variables)

<jsp:useBean> 액션을 통한 JSP 에이피아이는 데이터가 저장될 수 있도록 하며 JSP 컨테이너 내에 네 개의 다른 범위에서 데이터가 검색될 수 있도록 한다. JSTL은 이러한 범위 내에 객체를 할당하고 제거할 추가 액션을 제공한다. 더욱이, EL은 범위 변수 같은 객체들을 검색하는 빌트인 지원을 제공한다. 특히 EL의 내장 객체 중 하나라도 상응하지 않는 EL 익스프레션에 있는 식별자는 네 개의 JSP 스콥 중 하나에 저장된 객체를 참조하는 것으로 자동 간주된다:

  • 페이지 범위
  • 요청 범위
  • 세션 범위
  • 애플리케이션 범위

페이지 범위에 저장된 객체들은 특정 요청에 대한 페이지가 프로세스 되는 동안 검색될 수 있다. 요청 범위 내에 저장된 객체들은 요청 프로세스에 참여한 모든 페이지들이 프로세스 하는 동안 검색될 수 있다. 객체가 세션 범위에 저장되어있다면 웹 애플리케이션과의 단일 인터랙트브 세션 동안 사용자가 액세스 한 페이지로 검색될 수 있다. 웹 애플리케이션이 언로드(unload) 될 때 까지 애플리케이션 범위에 저장된 객체는 모든 페이지에서 접근가능하며 모든 사용자들이 접근할 수 있다.

캐릭터 스트링을 희망하는 범위에 있는 객체로 매핑하여 범위안에 객체를 저장할 수 있다. 이러한 경우에는 같은 캐릭터 스트링을 제공하여 범위에서 객체를 검색할 수도 있다. 스트링은 범위 매핑 중 검색되고 매핑된 객체는 리턴된다. Servlet API 내에서 그와 같은 객체들은 상응하는 범위의 애트리뷰트로서 언급된다. EL의 경우 애트리뷰트와 관련된 캐릭터 스트링은 변수 이름으로 간주될 수도 있다.

EL에서 내장 객체들과 관련이 없는 식별자들은 JSP 범위에 저장된 객체들을 명명하는 것으로 간주된다. 그와 같은 식별자는 페이지 범위를 검사하고 그 다음에는 요청 범위, 세션 범위, 애플리케이션 범위 순으로 검사한다. 식별자의 이름이 그 범위에 저장된 객체 이름과 매칭되는지의 여부가 테스트된다. 첫 번째 매치는 EL 식별자의 값으로 리턴된다. EL 식별자는 범위 변수를 참조하는 것으로 간주될 수 있다.

기술적인 관점에서 보면 내장 객체로 매핑하지 않는 식별자는 PageContext 인스턴스의 findAttribute() 메소드를 사용하여 평가되면서 현재 핸들되는 요청에 대해 익스프레션이 발생하는 페이지의 프로세싱을 나타낸다. 식별자의 이름은 이 메소드에 대한 인자로서 전달된다. 이것은 같은 이름을 가진 애트리뷰트에 대한 네 개의 범위를 검색한다. 발견된 첫 번째 매치는 findAttribute() 메소드 값으로 리턴된다. 그와 같은 애트리뷰트가 네 개의 범위 중에 없으면 null이 리턴된다.

궁극적으로 범위 변수는 네 개의 EL 식별자로서 사용될 수 있는 이름을 가진 JSP 범위의 에트리뷰트라고 할 수 있다. 영숫자 이름으로 할당되는 한 범위 변수는 JSP 에 존재하는 모든 메커니즘으로 만들어져 애트리뷰트를 설정할 수 있다. 여기에는 빌트인 <jsp:useBean> 액션은 물론 setAttribute() 메소드가 포함된다. 게다가 네 개의 JSTL 라이브러리에서 정의된 많은 커스텀 태그들은 스스로 범위 변수로서 애트리뷰트 값을 설정할 수 있다.




위로


내장 객체(Implicit objects)

11 개의 EL 내장 객체용 식별자는 표 1과 같다. JSP 내장 객체와 혼동하지 말것!

표 1. EL 내장 객체

Category 식별자 설명
JSP pageContext 현재 페이지의 프로세싱과 상응하는 PageContext 인스턴스
범위 pageScope 페이지 범위 애트리뷰트 이름과 값과 관련된 Map
requestScope 요청 범위 애트리뷰트 이름과 값과 관련된 Map
sessionScope 세션 범위 애트리뷰트 이름과 값과 관련된 Map
applicationScope 애플리케이션 범위 애트리뷰트 이름과 값과 관련된 Map
요청 매개변수 param 요청 매개변수의 기본 값을 이름으로 저장하는 Map
paramValues 요청 매개변수의 모든 값을 String 어레이로서 저장하는 Map
요청 헤더 header 요청 헤더의 기본 값을 이름으로 저장하는 Map
headerValues 요청 헤더의 모든 값을 String 어레이로서 저장하는 Map
쿠키 cookie 요청에 수반되는 쿠키들을 이름으로 저장하는 Map
초기화 매개변수 initParam 웹 애플리케이션의 콘텍스트 초기화 매개변수를 이릉으로 저장하는 Map

JSP와 EL 내장 객체가 일반적인 하나의 객체를 갖는 반면(pageContext) 다른 JSP 내장 객체는 EL에서 접근 가능하다. 페이지콘텍스트가 다른 8 개의 JSP 내장 객체 모두에 액세스 할 수 있는 속성을 갖고 있기 때문이다.

남아있는 모든 EL 내장 객체들은 맵(map)이다. 이름에 상응하는 객체들을 탐색한다. 첫 번째 네 개의 맵은 이전에 언급한 다양한 애트리뷰트 범위를 나타낸다. 특정 범위 내의 식별자들을 검색하는데 사용될 수 있다. EL이 기본적으로 사용하는 순차적인 탐색 프로세스에 의존하지 않는다.

다음 네 개의 맵은 요청 매개변수와 헤더의 값을 반입하는 용도이다. HPPT 프로토콜이 요청 매개변수와 헤더가 다중 값을 가질 수 있도록 하기 때문에 각각 한 쌍의 맵이 있다. 각 쌍 중에서 첫 번째 맵은 요청 매개변수 또는 헤더에 대한 기본 값을 리턴한다. 실제 요청 시 첫 번째로 지정된 값이 무엇이든 상관없다. 두 번째 맵은 매개변수나 헤더의 값 모두 검색될 수 있도록 한다. 이 맵의 핵심은 매개변수 또는 헤더의 이름이다. 값들은 String 객체의 어레이이다.

쿠키 내장 객체는 요청으로 설정된 쿠키에 대한 접근을 제공한다. 이 객체는 요청과 관련된 모든 쿠키들의 이름을 Cookie 객체들로 매핑하면서 쿠키들의 속성을 나타낸다.

마지막 EL 내장 객체인 initParam은 웹 애플리케이션과 관련된 모든 콘텍스트 초기와 매개변수의 이름과 값을 저장하는 맵이다. 초기화 매개변수들은애플리케이션의 WEB-INF 디렉토리에 있는 web.xml 전개 디스크립터 파일을 통해 정의된다.




위로


접근자(Accessors)

EL 식별자는 내장 객체 또는 범위 변수로서 설명될 수 있기 때문에 자바 객체로 평가해야한다. EL은 상응하는 자바 클래스에서 프리머티브를 래핑/언래핑한다. 하지만 대부분의 경우 식별자들은 자바 객체에 대한 포인터가 된다.

결과적으로 이러한 객체들의 속성이나, 어레이와 컬렉션의 경우 그들의 엘리먼트에 액세스하는 것이 바람직하다. 이를 위해 EL은 두 개의 다른 접근자를 제공한다. 닷(dot) 오퍼레이터(.)와 브래킷 오퍼레이터([])이다. 이들은 속성과 엘리먼트들이 EL을 통해 연산될 수 있도록 한다.

닷 연산자는 객체의 프로퍼티에 접근하는데 사용된다. ${user.firstName} 익스프레션에서 닷 연산자는 user 식별자에 의해 참조된 객체 중 firstName이라는 이름을 가진 속성에 액세스 한다. EL은 자바 빈 규정을 사용하여 객체 속성에 접근하기 때문에 이 속성에 대한 게터(일반적으로 getFirstName())는 이 익스프레션이 정확히 계산하기 위해서 반드시 정의되어야 한다. 액세스되는 속성이 객체일 때 닷 연산자는 재귀적으로 적용될 수 있다. 예를 들어 가상의 user 객체가 자바 객체로서 구현된 address 속성을 갖고 있다면 닷 연산자는 이 객체의 속성에 액세스 하기 위해 사용될 수도 있다. ${user.address.city} 익스프레션은 이 address 객체 중 중첩된 city 속성을 리턴한다.

브래킷 연산자는 어레이와 컬렉션의 엘리먼트를 검색하는데 사용된다. 어레이와 컬렉션(java.util.List를 구현하는 컬렉션)의 경우 검색될 엘리먼트 인덱스는 브래킷 안에 나타난다. 예를 들어 ${urls[3]} 익스프레션은 이 urls 식별자에 의해 참조된 어레이 또는 컬렉션의 네 번째 엘리먼트를 리턴한다.

java.util.Map 인터페이스를 구현하는 컬렉션의 경우 브래킷 연산자는 관련 키를 사용하여 맵에 저장된 값을 찾는다. 이 키는 브래킷 내에서 지정되고 상응하는 값은 익스프레션 값으로 리턴된다. 예를 들어 ${commands["dir"]} 익스프레션은 commands 식별자에 의해 참조된 Map"dir" 키와 관련된 값을 리턴한다.

익스프레션이 브래킷안에 나타날 수 있다. 중첩된 익스프레션의 계산 결과는 컬렉션이나 어레이의 적절한 엘리먼트를 검색하는 인덱스 또는 키로 작용한다. 닷 연산자가 true라면, 브래킷 연산자도 재귀적으로 적용될 수 있다. 이는 EL이 다차원 어레이, 중첩 컬렉션, 또는 둘의 결합에서 엘리먼트를 검색 할 수 있도록 한다. 더욱이 닷 연산자와 브래킷 연산자는 상호운용성이 있다. 예를들어 한 어레이의 엘리먼트가 객체라면 브래킷 연산자는 그 어레이의 엘리먼트를 검색하는데 사용될 수 있고 닷 연산자와 결합하여 엘리먼트 속성 중 하나를 검색할 수 있다. (예를 들어 ${urls[3].protocol}).

EL이 동적 애트리뷰트 값을 정의하는 간한한 언어로서 작용한다고 볼 때, 자바 접근자와는 다른 EL 접근자의 재미있는 특성 중 하나는 null에 적용될 때 예외를 던지지 않는다는 점이다. EL 접근자가 적용되는 객체(예를 들어 ${foo.bar}${foo["bar"]}foo 식별자)가 null이면 접근자 적용 결과 역시 null이다. 이는 대부분의 경우, 도움이 되는 일이다.

마지막으로 닷 연산자와 브래킷 연산자는 상호 교환될 수 있다. 예를 들어 ${user["firstName"]}user 객체의 firstName 속성을 검색하는데 사용될 수 있다. ${commands.dir}commands 맵에서 "dir" 키와 관련된 값을 반입하는데 사용될 수 있는것과 같은 이치이다.




위로


연산자(Operators)

식별자와 접근자를 사용하여 EL은 애플리케이션 데이터(범위 변수를 통해 노출) 또는 환경 관련 정보(EL 내장 객체를 통해 노출)를 포함하고 있는 객체 계층을 트래버스 할 수 있다. 그와 같은 데이터에 간단히 접근하는 것은 많은 JSP 애플리케이션에 필요한 표현 로직을 구현하는데 종종 부적합하다.

EL에는 EL 익스프레션으로 접근된 데이터를 조작 및 비교할 여러 연산자를 포함하고 있다. 이러한 연산자들을 표 2에 요약했다.

표 2. EL 연산자

Category 연산자
산술 +, -, *, / (or div), % (or mod)
관계형 == (or eq), != (or ne), < (or lt), > (or gt), <= (or le), >= (or ge)
논리 && (or and), || (or or), ! (or not)
타당성검사 empty

산술 연산자는 더하기, 빼기, 나누기를 지원한다. 다른 연산자들도 제공된다. 나누기와 나머지 연산자들은 비 상징 이름들이라는 대안을 갖고 있다. 산술 연산자의 사용법을 설명하는 예제 익스프레션은 Listing 5에 설명되어 있다. 산술 연산자를 한 쌍의 EL 익스프레션에 적용한 결과는 그러한 익스프레션에 의해 리턴된 숫자 값에 대한 연산자에 적용한 결과이다.


Listing 5. 산술 연산자를 사용하는 EL 익스프레션

${item.price * (1 + taxRate[user.address.zipcode])}

관계형 연산자는 숫자 또는 텍스트 데이터를 비교할 수 있도록 한다. 비교 결과는 부울 값으로서 리턴된다. 논리적 연산자는 부울 값이 결합될 수 있도록 하며 새로운 부울 값을 리턴한다. EL 논리적 연산자는 중첩된 관계형 연산자 또는 논리적 연산자의 결과에 적용될 수 있다. (Listing 6).


Listing 6. 관계형 연산자 및 논리적 연산자를 사용하는 EL 익스프레션

${(x >= min) && (x <= max)}

EL 연산자는 empty 이다. 데이터의 타당성 검사에 특히 유용하다. empty 연산자는 하나의 익스프레션을 인자로 취한다.(${empty input}). 그리고 익스프레션이 empty 값으로 계산했는지의 여부를 나타내는 부울 값을 리턴한다. null로 계산한 익스프레션은 empty로 간주된다. 어떤 엘리먼트도 없는 컬렉션이나 어레이와 같다. empty 연산자는 인자가 길이가 0인 String으로 계산했다면 true로 리턴한다.

EL 연산자의 우선순위는 표 3에 정리되어 있다. Listing 5와 6에 제안된 것 처럼 괄호는 그룹 익스프레션에 사용되고 일반적인 우선순위를 따른다.

표 3. EL 연산자 우선순위 (위->아래, 왼쪽->오른쪽)

[], .
()
unary -, not, !, empty
*, /, div, %, mod
+, binary -
() <, >, <=, >=, lt, gt, le, ge
==, !=, eq, ne
&&, and
||, or




위로


리터럴(Literals)

숫자, 캐릭터 스트링, 부울, null은 EL 익스프레션에서 리터럴 값으로 지정될 수 있다. 캐릭터 스트링은 싱글 쿼트 또는 더블 쿼트로 범위가 지정된다. 부울 값은 truefalse로 계산된다.




위로


Taglib 지시문

앞서 언급했지만 JSTL 1.0에는 네 개의 커스텀 태그 라이브러리가 포함되어 있다. 익스프레션 언어로 JSTL 태그의 인터랙션을 설명하기 위해 JSTL core 라이브러리에서 여러 태그들을 검토할 것이다. 모든 JSP 커스텀 태그 라이브러리로 true가 된다면 taglib 지시문은 이 라이브러리 태그를 사용할 수 있는 페이지에 포함되어야한다. 이 특정 라이브러리에 대한 지시문은 Listing 7에 나타나있다.


Listing 7. JSTL core 라이브러리의 EL 버전용 테그립 지시문

<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>

실제로 JSTL core 라이브러리에 상응하는 두 개의 Taglib 지시문이 있다. JSTL텐에서 EL은 옵션이기 때문이다. JSTL 1.0 의 네 개의 커스텀 태그 라이브러리들은 동적 애트리뷰트 값을 지정할 때 EL 대신 JSP 익스프레션을 사용하는 대안 버전을 갖고있다. 이러한 대안 라이브러리는 JSP의 전통적인 요청시간 애트리뷰트 값에 의존하기 때문에 RT 라이브러리로 일컬어진다. 반면 익스프레션 언어를 사용하는 것은 EL 라이브러리라고 한다. 개발자들은 대안 Taglib 지시문을 사용하는 각각의 라이브러리의 버전들을 구별한다. RT 버전의 코어 라이브러리를 사용하기 위한 지시문은 Listing 8에 나와있다. 하지만 지금은 EL에 집중해야 하기 때문에 지금 필요한 것은 이 지시문들 중 첫 번째 것이다.


Listing 8. RT 버전의 JSTL core 라이브러리용 태그립 지시문

<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c_rt" %>




위로


변수 태그

첫 번째 JSTL 커스텀 태그는 <c:set> 액션이다. 이미 언급했듯이 범위 변수는 JSTL에서 핵심적인 역할을 하고 <c:set> 액션은 태그 기반의 매커니즘을 제공하여 범위 변수의 생성 및 설정에 쓰인다. 이 액션의 신택스는 Listing 9와 같다. var 애트리뷰트는 범위 변수 이름을 정하고 scope 애트리뷰트는 변수가 머물게 될 범위를 나타내고, value 애트리뷰트는 변수가 될 값을 지정한다. 지정된 변수가 이미 존재하면 지시된 값으로 할당된다. 그렇지 않다면 새로운 범위 변수가 만들어지고 그 값으로 초기화된다.


Listing 9. <c:set> 액션 신택스

<c:set var="name" scope="scope" value="expression"/>


scope 애트리뷰트는 선택적이며 page로 기본 설정되어 있다.

<c:set>의 두 예제는 Lisitng 10에 설명되어 있다. 첫 번째 예제에서 세션 범위 변수는 String 값으로 설정된다. 두 번째에서는 익스프레션은 숫자 값을 설정하는데 사용된다. square라는 페이지 범위 변수는 x 라는 요청 매개변수 값을 배가시킨 결과로 할당된다.


Listing 10. <c:set> 액션 예제

<c:set var="timezone" scope="session" value="CST"/>
<c:set var="square" value="${param['x'] * param['x']}"/>



애트리뷰트를 사용하는 대신 범위 변수용 값을 <c:set> 액션의 바디 콘텐트로 설정할 수 있다. 이러한 접근방식을 사용하여 Listing 10의 첫 번째 예제를 Listing 11과 같이 재작성할 수 있다. 더욱이 <c:set> 태그의 바디 콘텐트가 커스텀 태그를 적용하는 것도 가능하다. <c:set>의 바디 안에서 만들어진 모든 콘텐트는 String 값 같이 지정된 변수에 할당된다..


Listing 11. 바디 콘텐트를 통해 <c:set> 액션용 값 지정하기

<c:set var="timezone" scope="session">CST</c:set>

JSTL core 라이브러리에는 범위 변수를 관리하는 두 번째 태그(<c:remove>)가 포함되어 있다. 이름에서 시사되는 바와 같이 <c:remove> 액션은 범위 변수를 지우는데 사용되고 두 개의 애트리뷰트를 취한다. var 애트리뷰트는 제거될 변수를 명명하고 선택적인 scope 애트리뷰트는 제거되어야 할 범위를 나타낸다. (Listing 12).


Listing 12. <c:remove> 액션 예제

<c:remove var="timezone" scope="session"/>




위로


아웃풋

<c:set> 액션은 익스프레션의 결과가 범위 변수로 할당될 수 있도록 하는 반면 개발자들은 익스프레션 값을 저장하는 대신 간단히 디스플레이하기를 원한다. 이는 JSTL의 <c:out> 커스텀 태그의 몫이다. (Listing 13). 이 태그는 value 애트리뷰트에서 지정된 익스프레션을 계산한다. 그런다음 결과를 프린트한다. 선택적 default 애트리뷰트가 지정되면 value 애트리뷰트의 익스프레션이 null 또는 비어있는 String으로 계산될 때 <c:out> 액션은 값을 프린트한다.


Listing 13. <c:out> 액션 신택스

<c:out value="expression" default="expression" escapeXml="boolean"/>

escapeXml 애트리뷰트 또한 선택사항이다. "<", ">", "&" 같은 캐릭터가 <c:out> 태그에 의해 아웃풋 될 때 종료되는지의 여부를 제어한다. escapeXml이 true로 설정되어 있다면 이 캐릭터들은 상응하는 XML 인터티(<, >, &)로 바뀐다.

예를 들어, user라는 세션 범위 변수가 있다고 가정해보자. 이것은 사용자에 대한 usernamecompany라는 두 개의 속성들을 정의하는 클래스의 인스턴스이다. 이 객체는 사용자가 사이트에 접근할 때마다 세션에 할당된다. 하지만 이 두 개의 속성들은 사용자가 실제로 로그인하기 전까지 설정되지 않는다. (Listing 14). 일단 사용자가 로그인하면 "Hello"가 디스플레이 되고 뒤따라서 사용자 이름과 감탄부호가 나온다. 사용자가 로그인하기 전에 여기에서 생긴 콘텐트는 "Hello Guest!" 라는 구(phrase)가 된다. 이 경우 username 속성이 초기화되지 않았기 때문에 <c:out> 태그는 default 애트리뷰트 값을 프린트한다.


Listing 14. <c:out> 액션 예제 (디폴트 콘텐트)

Hello <c:out value="${user.username}" default=="Guest"/>!


<c:out> 태그의 escapeXml 애트리뷰트를 사용하는 Listing 15를 보자. company 속성이 자바 String 값인 "Flynn & Sons"으로 설정되었다면 이 액션에서 생긴 콘텐트는 Flynn & Sons이 된다. 이 액션이 HTML 또는 XML 콘텐트를 만드는 JSP 페이지의 일부라면 이 캐릭터의 스트링 중간에 있는 앰퍼샌트 부호는 HTML 또는 XML이 문자를 제어하고 이 콘텐트의 렌더링 또는 파싱을 방해하는것으로 해석하고 끝난다. escapeXml 애트리뷰트의 값이 true로 설정되면 생성된 콘텐트는 Flynn & Sons이 된다. 이 콘텐트를 만나는 브라우저 또는 파서는 인터프리테이션에 아무 문제가 없다. HTML과 XML이 JSP 애플리케이션에서 가장 일반적인 콘텐트 유형이라면 escapeXml 애트리뷰트의 디폴트 값이 true라는 것은 놀라운 일이 아니다.


Listing 15. <c:out> 액션 예제)

<c:out value="${user.company}" escapeXml=="false"/>





위로


디폴트 값으로 변수 설정하기

동적 데이터를 단순하게 하는 것 외에도 디폴트 값을 지정하는 <c:out>의 기능은 <c:set>을 통해 변수 값을 설정할 때에도 유용하다. 범위 변수에 할당된 값이 <c:set> 태그의 바디 콘텐트로 지정될수 있고 value 애트리뷰트로서도 가능하다. <c:out> 액션을 <c:set> 태그의 바디 콘텐트에 중첩하여 변수 할당은 이것의 디폴트 값을 이용할 수 있다. (Listing 11).

이러한 접근 방식은 Listing 16에도 설명되어 있다. 외부 <c:set> 태그의 작동은 단순하다.


Listing 16. <c:set>과 <c:out> 결합: 디폴트 변수 값 제공

<c:set var="timezone" scope=="session">
   <c:out value="${cookie['tzPref'].value}" default=="CST"/>
</c:set>

요청에 제공된 tzPref 라는 이름의 쿠키가 없다. 내장 객체를 사용한 검색은 null이 된다는 것을 의미한다. 익스프레션은 전체적으로 null을 리턴한다. value 애트리뷰트를 계산한 값이 null 이기 때문에 <c:out> 태그는 default 애트리뷰트를 계산한 결과를 아웃풋한다.




위로


참고자료




위로


필자소개

Mark Kolb는 소프트웨어 엔지니어이며 Web Development with JavaServer Pages, 2nd Edition의 공동저자이다.

:
Posted by 뽀기
2007. 1. 29. 14:47

커스텀 태그로 JSP 페이지 제어하기 그거/Java2007. 1. 29. 14:47

커스텀 태그 라이브러리 통신의 모든 것

developerWorks

 

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




난이도 : 초급

Jeff K. Wilson, E-비즈니스 설계자, IBM

2002 년 1 월 01 일

JavaServer Pages 기술은 웹 개발자들에게 중요한 기능을 제공하지만, 많은 개발자들이 이 기술의 전 능력을 활용하지 못하고 있다. e-비즈니스 설계자인 Jeff Wilson (IBM의 존경받는 DragonSlayers 팀의 멤버이기도 함)은 이 기술에서 더 많은 것을 얻어 내기 위하여 JSP 태그를 커스터마이즈하는 방법을 보여준다. 이 글에서 그가 상세히 설명한 기법을 이용해서 여러분은 JSP에 보다 복잡한 로직을 추가하고 데이터 화면 출력을 더 엄격하게 제어하고 태그 간에 데이터를 공유시킬 수 있는데, 일선 웹 개발자들에게 자바 코드 작성법을 가르치지 않고도 이 모든 것을 할 수 있다.

여러분이 웹 개발에 관여하고 있다면 여러분은 오늘날의 웹 기반 애플리케이션이 보다 동적으로 생성되는 컨텐츠와 개인화된 데이터를 그 어느 때보다 더 많이 요구한다는 사실을 잘 알고 있을 것이다. 사용자에게 친근한 인터페이스를 설계한다는 것은 일선 개발자들이 숙련된 시각적 설계 스킬을 가지고 있어야 할 뿐 아니라 컨텐츠의 흐름을 관리하고 활용하는데도 능숙해야 함을 의미한다.

사용자 정의 (커스텀) JSP 태그는 이러한 일선 개발자들에게 JSP 페이지에 아무런 자바 코드를 작성하지 않고도 백엔드 자바 컴포넌트에서 데이터가 처리되는 방식을 제어할 수 있는 수단을 제공한다.

이 글에서 우리는 커스텀 태그가 서로 어떻게 통신하고 이들을 결합시키면 재사용성과 유연성이 어떻게 높아지는지에 포커스를 맞출 것이다. 또한 몇 가지 예도 살펴 보겠다.

커스텀 태그 개요

커스텀 태그는 <jsp:useBean .../><jsp:getProperty .../> 같은 일반적인 JSP 태그보다 더 진보되고 유연하다. 커스텀 태그가 일반 JSP 태그에 비해 가지는 주요 장점 중 하나는 커스텀 태그를 사용하면 JSP 개발자가 데이터를 태그 속성이나 시작과 끝 태그 태그 사이에 위치시킴으로써 입력 사항을 전달할 수 있다는 점이다.

커스텀 JSP 태그는 다음 세 부분으로 구성되어 있다.:

  • 태그가 사용되는 JSP 페이지
  • 태그를 처리하는 자바 클래스인 태그 핸들러 (tag handler)
  • 태그들을 하나의 "라이브러리"로 묶고, 속성, 태그 처리명, 단축명 등 각 태그의 세목을 기술하는 XML 파일인 태그 라이브러리 디스크립터 (tag library descriptor) .

커스텀 태그는 태그의 속성으로 입력된 사항에 기반하여 프로세스를 실행시키도록 구성될 수 있고, JSP 페이지 내에 임베디드된 자바 코드를 둘 필요가 없다.

예제를 살펴 보자. Listing 1은 사용자가 선택한 모든 제품에 대해 테이블의 한 행을 만드는 쇼핑 카트 태그이다.:



Listing 1. 쇼핑 카트 태그
<table width="100%" border="0">
  <user:getUserShoppingList userId="13">
  <tr>
    <td>
    <a href="/servlet/productDataServlet?prodID=$_productId">$_productName</a>
    </td>
    <td>$_productDescription</td>
  </tr>
  </user:getUserShoppingList>
</table>

이 경우, 태그 처리자 클래스는 HTML과 사용자 ID가 결과로 나오는 출력 (이 경우에는 테이블의 한 행)에 대한 템플릿으로 전달되기를 기대한다. 다양한 $_product...참조는 태그 핸들러에서 반복 수행되는 제품들의 실제 값으로 교체될 것이며, 이것이 태그를 구현하는 자바 클래스이다.

이것은 커스텀 태그의 또 다른 주요 장점을 보여 준다. 자바 프로그래머들은 데이터를 클라이언트에게 재전송하기 전까지 데이터 포맷을 어떻게 할지 알 필요가 없다. 또한 내용은 유사한데 포맷이 다른 데이터를 요구하는 새로운 페이지가 개발되거나 현재 페이지의 모습이 바뀔 때 자바 컴포넌트를 업데이트할 필요가 없을 것이다.




위로


유형 뿐 아니라 화면 로직도 제어하기

커스텀 태그의 또 다른 아주 중요한 장점은 같은 페이지 상의 다른 태그와 통신할 수 있다는 점이다. 기존의 JSP 태그에서는 개발자들이 JavaBean 컴포넌트의 행동을 제어하기 위해 특성을 설정할 수 있었지만, 빈은 그 자신이 할 수 있는 것만 할 뿐이다.

프로세스를 보다 작은 컴포넌트들로 나눔으로써 JSP 개발자들은 커스텀 태그들을 섞고 맞출 수 있으며, 이를 통해 동적인 컨텐츠에 대한 제어력을 높일 수 있도록 보다 복잡한 프로세스를 구축할 수 있다.

한 커스텀 태그의 출력을 다른 태그에 대한 입력으로 사용하면 태그의 재사용성이 높아진다. 예를 들어, 위의 예에서 사용자 쇼핑 카트 태그는 구매한 제품에 대한 모든 정보를 보유하고 있다. 사용자 태그는 제품 ID만을 가지고 다른 태그 (제품 태그)가 제품 데이터를 필요에 따라 관리하도록 하면 더 휼륭한 설계가 될 것이다. 일단 제품 상세 사항을 모으는 프로세스를 분리시키면 여러분은 이런 목적으로 다른 태그와 함께 사용될 수 있는 제품 태그를 가지게 된다.

이는 검색된 동적인 데이터를 제어하기 위한 객체지향적 방식을 만들어내고, 일선 개발자들은 프로그래밍에 대해 알지 못해도 이 방식을 따라갈 수 있다.

Listing 2의 코드는 실행 중인 이 방식을 보여준다. 또한 사용자의 쇼핑 목록 태그는 error:setErrorTemplate이라는 다른 태그를 구현한다는 점에 주의한다. 이 예에서 어떤 제품도 발견되지 않을 경우 우리는 에러 메시지가 나타나기를 원한다. 우리의 제품 목록에 필요한 2열로 된 테이블이 에러 메시지에 그리 적절하지 않을 것임을 쉽게 알 수 있다.



Listing 2. 객체 지향적 방식
<table width="100%" border="0">
  <tr class="headerRow">
    <td>Product Name</td>
    <td>Product Description</td><tr>
  </tr>
  <user:getUserShoppingList userId="13">
    <products:getProductData productId="$_productId"/>
    <tr>
      <td>
        <a href="/servlet/productDataServlet?$_productId">$_productName</a>
      </td>
      <td>$_productDescription</td>
    </tr>
    <error:setErrorTemplate>
    <tr class="errorRow">
        <td colspan="2">You have nothing in your shopping cart...</td>
    </tr>
    </error:setErrorTemplate>
  </user:getUserShoppingList>
</table>

태그 핸들러 클래스는 사전 정의된 특정 환경 하에서 변경된 포맷을 처리하도록 설계될 수 있다. 이것은 JSP 개발자가 JSP 페이지 내에 if 절이나 다른 자바 코드를 사용하지 않고도 어떻게 데이터의 논리적 흐름을 제어할 수 있는지를 보여주는 좋은 예이다. 커스텀 태그를 사용해 JSP 개발자들은 어떻게 표시되는지 뿐 아니라 무엇이 표시되는지를 판별하는 방법도 정할 수 있다.

다른 상황에서, 동일한 제품 데이터 태그가 제품 목록을 가진 다른 태그에 재사용될 수 있다. Listing 3에서 다른 HTML이 태그로 전달



Listing 3. 제품 목록과 사용되는 제품 데이터 태그
<table width="100%" border="0">
  <products:getProductList category="fitness">
    <products:getProductData productId="$_productId"/>
    <tr>
      <td rowspan="2">
        <a href="/servlet/productDataServlet?$_productId"><img 
        src="$_productImage" border="0"></a></td>
      <td>
      <a href="/servlet/productDataServlet?$_productId">$_productName</a>
      </td>
    </tr>
    <tr>
      <td>$_productDescription: $_productPrice</td>
    </tr>
    <error:setErrorTemplate>
    <tr>
      <td colspan="2" class="errorRow">Sorry, no products in this category...</td>
    </tr>
    </error:setErrorTemplate>
  </user:getUserShoppingList>
</table>




위로


태그 통신 메소드 : 장점 및 예제

커스텀 태그가 서로를 참조하고 데이터를 공유하는 몇 가지 방법이 있다. 적절한 메소드가 무엇이냐는 물론 상황에 달려 있을 것이다.




위로


중첩 태그

한 태그가 다른 태그에 의해 완전히 둘러싸여져 있을 때 태그가 중첩되어 있다고 말한다. :



<outer:tag><inner:tag/></outer:tag>

한 태그를 다른 태그 내에 두기 위해 특별한 설정이나 코딩이 필요하지는 않다. 한 태그가 한 곳에 중첩될 수 있고 그 자체로 다른 곳에 중첩될 수 있다. 물론 일부 태그는 다른 태그들 내에 중첩되도록 설계되겠지만, 태그가 중첩 가능하다고 선언하기 위해 특별히 필요한 것은 없다.

여러분은 HTML 테이블, 테이블의 행과 테이블의 셀 태그를 중첩 태그로 생각할 수 있다. 테이블 태그에 공유된 데이터의 예로 테이블의 배경 색 (bgcolor 속성)을 들 수 있다. 배경이 테이블 태그 내에 <table bgcolor="blue">...</table>이라고 설정되면, bgcolor 속성이 개별적인 태그 (예 : <table bgcolor="blue">...<td bgcolor="red">...</td>...</table>)에 의해 오버라이드되지 않는 이상 모든 행과 셀이 파란색으로 설정될 것이다.

가장 기본적인 구현에서, 평가된 내부 태그는 간단히 외부 태그의 body 입력이 될 수 있다. 그러나 중첩된 태그는 또한 자신을 둘러싸고 있는 태그를 참조할 수 있고 (부모 태그 혹은 조부모 태그 등) 연결된 클래스가 서로의 메소드와 특성을 호출할 수 있도록 한다. 이런 방법으로 자식 태그와 부모 태그가 데이터를 공유할 수 있다.

중첩 태그는 다음 두 메소드 중 하나를 사용하여 조상 태그를 참조할 수 있다.:

  • TagSupport.getParent(): 부모 태그 (즉 태그를 바로 둘러싸고 있는 태그)를 반환한다.

  • TagSupport.findAncestorWithClass(from,class): 태그의 특정 계층이 알려지지 않거나 반드시 사전 설정된 것은 아닐 때 사용된다. findAncestorWithClass(from,class)의 인자는 어떤 클래스로부터 시작해야 하는지와 어떤 클래스를 찾아야 하는지를 각각 알려준다. 예를 들어, HTML 테이블 태그 계층에서 테이블 태그에 접근하는 한 테이블 셀 태그는 다음과 같은 모습일 수 있다.:



TableTag table = (TableTag)findAncestorWithClass(this, TableTag.class);

현재의 태그가 지정된 클래스(TableTag.class, in this case)를 태그 핸들러로 가진 태그에 중첩되지 않았거나 getParent()가 호출되었는데 부모 태그가 없는 경우, 두 메소드 모두 null 값을 반환할 것이다.




위로


태그를 ID로 참조하기

데이터를 공유하는 또 다른 방법은 클래스를 ID로 등록하여 다른 태그의 핸들러 클래스에 의해 검색될 수 있도록 하는 것이다. 이 방법을 사용하면 JSP 개발자는 태그가 이를 받아들이도록 특수하게 프로그래밍되지 않은 경우 ID를 어떤 커스텀 태그에도 설정할 수 없다.

ID 특성은 이런 목적으로 이미 TagSupport 내에 선언되었고, 어떤 태그 핸들러 클래스에서도 사용 가능하다. 그러나 다른 태그가 접근할 수 있도록 ID 특성을 사용하여 객체를 저장하려면 두 단계를 거쳐야 한다.:

  1. ID 특성이 태그 라이브러리 디스크립터에 지정되어야 한다. (필요한 노드는 참이나 거짓 중 하나로 설정될 수 있다.)

  2. 태그 핸들러는 pageContext의 속성으로 자신을 명확하게 설정해야 한다.

태그가 쉽게 중첩되지 않는 경우 태그 객체를 등록된 ID와 공유하는 것이 필요할 수 있다.

우리의 예제인 사용자 쇼핑 카트를 다시 언급해 보자. 아래 Listing 4의 <user:getUserShoppingList .../>getProductIds()라는 제품 ID 목록을 반환하는 메소드를 포함해 쇼핑 목록에 대한 다양한 정보를 포함하고 있다고 생각해보자. JSP 페이지의 다른 어딘가에서 <products:getProductData> ... </products:getProductData> 태그가 제품 ID 목록을 취해 각 제품에 대한 상세 사항을 검색한 후 이들의 포맷을 정하여 클라이언트에게 재전송 할 것이다.

user:getUserShoppingList 태그가 실행되어 userShoppingList라는 이름의 pageContext의 한 속성으로 자신을 저장할 것이다. product:getProductData 태그는 속성의 값을 검색하고 getUserShoppingList 객체로부터 getProductIds()메소드를 호출할 것이다.



Listing 4. getUserShoppingList
    <user:getUserShoppingList userId="13" id="userShoppingList"/>
    ...
    <products:getProductData productData="userShoppingList">
       <!-- Some formatting template -->
      ...
    </products:getProductData>

getUserShoppingList 태그에 대한 태그 라이브러리 디스크립터는 Listing 5와 같은 모습일 것이다.:



Listing 5. getUserShoppingList에 대한 라이브러리 디스크립터
  <tag>
       <name>getUserShoppingList</name>
       <tagclass>com.taglib.UserShoppingListTag</tagclass>
        <bodycontent>JSP</bodycontent>
        <attribute>
            <name>userId</name>
            <required>true</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
           <name>id</name>
           <required>false</required>
           <rtexprvalue>false</rtexprvalue>
        </attribute>
    </tag>


태그가 자신의 모든 처리를 완료하고 나면 (아마도 doEndTag()메소드로) getShoppingList 태그 핸들러는 다음 행을 포함할 것이다.:



pageContext.setAttribute(getId(),this);

getId()는 태그 (userShoppingList) 내의 ID 세트를 검색하고, this는 전체 클래스 자체를 참조한다.

일단 userShoppingList 태그가 pageContext에 저장되고 나면 getProductData 태그 핸들러 클래스가 productData 속성과 함께 자신에게 전달된 ID를 사용하여 여기에 접근할 수 있다. getProductData 태그의 핸들러 클래스는 그 ID (getProductData() 메소드로 검색된)를 사용하여 pageContext 에서 속성을 발견하고 이를 UserShoppingListTag 객체로 되돌려줄 것이다. Listing 6의 코드는 productList라는 List를 선언하고 제품 목록을 추출하기 위해 getProductIds()라는 사용자 클래스 메소드를 호출한다.



Listing 6. 제품 목록 추출하기
UserShoppingListTag userShoppingList = 
        (UserShoppingListTag)pageContext.getAttribute(getProductData());
    List productList = (List)userShoppingList.getProductIds();




위로


페이지와 세션 context에서 태그 참조하기

전체 태그 객체를 저장하면 그 안의 특성과 메소드를 다른 태그가 자유롭게 사용할 수 있도록 하는데 유용하다. 그러나 여러분은 또한 객체의 일부만을 공개하기로 결정할 수도 있다. 사실 위 예제의 한계는 제품 태그가 제품 목록이UserShoppingListTag 객체에서 올 것으로 예상하고 있다는 것이다.

아마도 사용자 태그인 getShoppingList가 제품 목록을 다른 태그가 사용할 수 있도록 pageContext에 "반출"하는 것이 더 나은 방식일 것이다. 이 방식의 장점은 다른 태그가 제품 목록을 반환하는 메소드에 관해, 그리고 심지어는 목록을 처음 만든 객체에 대해서도 미리 알고 있지 않아도 된다는 것이다. pageContext에 저장된 데이터에 대한 라벨이 productData 속성에 의해 제공되면, 제품 태그의 태그 핸들러는 다음과 같은 모습일 것이다.:



List productList = (List)pageContext.getAttribute(getProductData());

이 기법은 쇼핑 목록 태그를 비롯한 다른 태그들에 대해 작동할 것인데, 세일 품목이나 주어진 카테고리 내의 제품을 호출하는 태그 같은 것을 예로 들 수 있다.

데이터가 세션에 저장되면 다음과 같은 모습일 것이다. :



HttpSession session = pageContext.getSession();
List productList = (List)session.getAttribute(getProductData());




위로


예제 커스텀 태그 살펴보기

아래의 참고 자료 섹션에서 여러분은 예제 커스텀 태그 세트에 대한 링크를 볼 수 있다. 코드 패키지를 다운로드하여 살펴보자. 이 글의 마지막 부분에서 나는 이 예제들을 이용하여 여러분에게 커스텀 태그를 사용해 얻을 수 있는 장점 몇 가지를 보여 줄 것이다.




위로


예제 태그 개요

샘플 태그 뒤의 아이디어는 매우 간단하고 직접적이다. 총 7개의 커스텀 태그와 하나의 JSP 파일 및 하나의 태그 라이브러리 디스크립터 (a.tld 파일)가 있다.

태그들은 함께 작동하여 환영 화면, 로그인 화면, 혹은 에러가 발생했을 경우 로그인 에러 메시지 중 하나를 표시한다. (실제 로그인 절차는 태그에 의해 처리되지 않는다. -- 간편함을 위해 우리는 servlet이나 JavaBean 컴포넌트가 설정해야 했을 세션과 요청 변수를 설정함으로써 프로세스를 모방해보자.)

Listing 7에 나와 있듯이, getUserDatanestedLogin이라는 두 개의 주 태그가 있다. 첫번째는 사용자 정보를 불러오고 두번째는 사용자인 John Q. Citizen이 로그인했는지, 아닌지에 따라 적절한 HTML을 표시한다.

이 두 태그는 nestedLogin이라는 태그가 pageContext에 저장되어 있는 getUserData 태그에 접근할 수 있는 방법을 표시한다.

nestedLogin 태그는 또한 한 태그 내에 다른 태그를 중첩시키는 프로세스를 보여주며, 다른 태그가 자신의 메소드에 접근하도록 해준다. isLoggedInHTML, notLoggedInHTML, 및 logInFailureHTML라는 세 개의 다른 태그에 의해 세 개의 화면이 표시될 수 있다. 이 세 태그는 nestedLogin 태그의 특성에 접근하도록 해준다.; nestedLogin에 의해 적절한 코드 블록이 결정되어 화면 출력될 것이다.

나머지 두 태그인 getUserNamegetLoginError는 중첩된 태그를 사용하는 두 가지 방식을 보여준다. : 간단한 body 컨텐츠로 사용하는 것과 조상 태그 내의 메소드에 접근하는 수단으로 사용하는 그것이다. 이 둘 모두 자신의 조상 태그를 오버라이드하지 않는다.; 조상 태그에서 간단히 데이터 (즉 사용자명과 로그인 에러, 둘 모두 설정되었을 경우)를 가져온다.



Listing 7. 예제 JSP 코드
<HTML>
<HEAD>
<TITLE>Custom Tag Communication</TITLE>
</HEAD>

<BODY bgcolor="#ffffff">

<!-- LOAD TAG LIBRARY -->
<%@ taglib uri="goforit.tld" prefix="goforit" %>

<!-- SET THE USER -->
<goforit:getUserData id="user"/>

<!-- SET THE LOGIN HTML BASED ON WHETHER OR NOT THE USER IS LOGGED IN -->
<!-- ONE OF THE NESTED NODES WILL BE DISPLAYED ACCORDINGLY -->
<goforit:nestedLogin userDataID="user">

    <goforit:isLoggedIn>
    <!-- THE HTML IN THIS NODE IS DISPLAYED IF THE USER IS LOGGED IN -->
    </goforit:isLoggedIn>

    <goforit:notLoggedIn>
    <!-- THE HTML IN THIS NODE IS DISPLAYED IF THE USER IS NOT LOGGED IN -->
    </goforit:notLoggedIn>

    <goforit:loginFailure>
    <!-- THE HTML IN THIS NODE IS DISPLAYED IF THERE WAS A LOGIN ERROR -->
    </goforit:loginFailure>

</goforit:nestedLogin>

</BODY>
</HTML>




위로


태그 라이브러리 디스크립터

Listing 8은 두개의 주 태그에 대한 디스크립터이다. getUserDataid 속성이, nestedLoginuserDataID가 필요함에 주의한다. 이들은 사용자 객체를 pageContext에 등록하고 이를 다른 클래스에서 검색하는데 사용된다.



Listing 8. Tag 디스크립터
  
  <tag>
    <name>getUserData</name>
    <tagclass>com.taglibrarycommunication.taglib.GetUserDataTag</tagclass>   
    <info></info>
    <bodycontent>JSP</bodycontent>    
    <attribute>
      <name>id</name>
      <required>true</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>     
  </tag>

  <tag>
    <name>nestedLogin</name>
    <tagclass>com.taglibrarycommunication.taglib.NestedLoginTag</tagclass>   
    <info></info>
    <bodycontent>JSP</bodycontent>    
    <attribute>
      <name>userDataID</name>
      <required>true</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>     
  </tag>




위로


태그 핸들러

다음 섹션에서는 클래스간 통신의 몇 가지 중요한 측면을 설명하겠다.

이 태그 핸들러 클래스는 다른 태그가 사용할 사용자 데이터를 검색한다.



Listing 9. 예제 시나리오를 제어하는 코드
// UNCOMMENT TO MIMIC A LOGGED IN USER STORED IN SESSION
//session.setAttribute("user","John Q. Citizen");

// UNCOMMENT TO MIMIC LOGIN ERROR STORED IN REQUEST
//pageContext.getRequest().setAttribute("loginError","Password incorrect");


사용자 데이터를 등록하기 위한 키는 doStartTag()메소드에 있다.:



Listing 10. doStartTag()
public int doStartTag() {

    session = pageContext.getSession();
    
    ... SET VARIOUS PROPERTIES BASED ON THE USER ...

    // THIS IS THE LINE THAT SAVES THIS CLASS TO pageContext
    pageContext.setAttribute(id,this);

    return SKIP_BODY;
}

Listing 11에 나와 있는 이 클래스는 사용자가 로그인했는지의 여부와 에러가 있는지의 여부에 따라 세 특성 중 어떤 것이 클라이언트에게 반환될지를 결정한다. 이 세 특성의 값은 JSP 페이지에서 이 클래스 내에 중첩된 다른 태그에 의해 결정된다. NestedLoginTag는 이전 태그에서 pageContext에 등록된 사용자를 추선 가져옴으로써 어떤 태그가 화면 출력될지를 결정한다. 그 후 사용자 명과 발생한 에러가 있는지 검색한다. 둘 다 비어 있으면 사용자가 로그인하지 않았다고 가정한다. 에러 메시지가 비어 있지 않으면 분명히 에러가 발생한 것이다. 사용자 이름이 설정되어 있으면, 사용자가 성공적으로 로그인한 것이다.



Listing 11. NestedLoginTag
// PULL THE userData OUT OF THE pageContext 
// WITH THE userDataID SUPPLIED THROUGH THE CUSTOM TAG
GetUserDataTag userData = 
     (GetUserDataTag) pageContext.getAttribute(getUserDataID());

// SET userName AND loginError FROM VALUES IN userData OBJECT
setUserName(userData.getUserName());
SetLoginError(userData.getLoginError());

...

if (getUserName()!="" &&
    getLoginError()==""){
    // IF userName IS SET PERSON IS LOGGED IN
    pageContext.getOut().print(getIsLoggedInHTML());
} else {
    if (getLoginError()=="")
        // IF NO userName SET BUT NO loginError SHOW LOGIN
        pageContext.getOut().print(getNotLoggedInHTML());
    else
        // IF loginError SHOW LOGIN AND ERROR
        pageContext.getOut().print(getLogInFailureHTML());
}

IsLoggedInTag, NotLoggedInTag과 LogInFailureTag
Listing 12의 세 태그는 nestedLogin 내에 중첩된 태그들이다. 이들은 모두 비슷한 기능을 수행하지만, nestedLogin의 다른 특성들을 설정한다. 이들의 body 내용은 JSP 개발자가 nestedLogin의 isLoggedInHTML, notLoggedInHTMLlogInFailureHTML 특성에 접근하고 설정하도록 해준다.



Listing 12. 부모 태그에 접근하기
    // THIS LINE ACCESSES THE PARENT CLASS NestedLoginTag
    NestedLoginTag parent = (NestedLoginTag) getParent();

    if (parent != null){
        BodyContent bc = getBodyContent();
        String body = bc.getString();

        // SET THE isLoggedInHTML PROPERTY OF THE PARENT CLASS
        // WITH THE BODY SUPPLIED THROUGH THE CUSTOM TAG
        parent.setIsLoggedInHTML(body);
    }

GetUserNameTag과 GetLoginErrorTag
Listing 13의 태그들은 간단히 pageContext에서 userData 객체를 검색하여 이 객체의 특성을 가져온다.



Listing 13. pageContext
    GetUserDataTag userData = 
        (GetUserDataTag) pageContext.getAttribute(getUserDataID());

    ...

    if (userData.getUserName() !=null){
        pageContext.getOut().print(userData.getUserName());
    }




위로


결론

JSP 페이지는 서버측 로직에서 클라이언트측 화면을 분리하고, 자바 프로그래머가 아닌 웹 개발자도 자바 기술의 힘을 자유롭게 이용할 수 있도록 해준다. 커스텀 태그를 사용하여 여러분은 웹 애플리케이션의 양 계층 모두에서 작업하는 개발자들에게 좀 더 많은 선택권을 줄 수 있고, 코드 모듈과 태그의 재사용을 장려할 웹 개발자에게 객체 지향적 접근 방식을 부과할 수 있다. 일단 여러분이 이 글에 포함된 샘플 태그 라이브러리를 검토했다면, 여러분의 애플리케이션에 커스텀 태그 사용을 시작할 준비가 된 것이다.




위로


참고자료

  • developerWorks worldwide 사이트에서 이 기사에 관한 영어원문.

  • Sun은 java.sun.com에 자체적인 tag libraries tutorial을 제공하고 있다.

  • JSPTags.com 은 Java Server Pages와 관련된 자료의 디렉토리를 제공하는데, JSP 태그 라이브러리에 중점을 두고 있다.

  • Apache는 JSP 커스텀 태그 라이브러리의 오픈 소스 리포지토리인 Jakarta Taglibs을 운영하고 있다.

  • developerWorks Java technology zone : 자바 관련 자료



위로


필자소개

Photo of Jeff Wilson

Jeff Wilson은 IBM의 개발자 관계 부문 내 컨설턴트들, 교육 담당자들 및 전도사들의 그룹인 DragonSlayers의 e-비즈니스 설계자이다.

:
Posted by 뽀기

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

developerWorks

 

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




난이도 : 고급

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

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

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

시리즈 소개


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

GWT는 코드 생성을 통해 이와 같은 기능을 이룩하고 GWT 컴파일러는 서버측 자바 코드에서 JavaScript를 생성한다. GWT는 GWT 자체에서 제공하는 API와 함께 java.langjava.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-containeroutput-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-Buttongwt-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에 있는 코드뿐만 아니라, txBoxucRadioWeather 클래스의 인스턴스 멤버가 되면서 확인 방법으로 처리된다.


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 타입이 통과되면서 오류 근원이 나타나게 된다.




위로


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

WeatherServiceWeatherServiceAsync의 비동기식 인터페이스가 적절한 상태에서 필자는 이제 Weather Reporter 클라이언트를 호출해 서비스를 호출하고 서비스에서 나온 응답을 다룬다. 이에 대한 첫 번째 단계는 단순히 반복 사용 설정 코드에 관한 것이다. 반복 사용 설정 코드는 GWT.create(WeatherService.class)를 불러내고 GWT.create(WeatherService.class)에서 반환하는 객체를 다운 캐스팅 해 weather 클라이언트가 사용하는 WeatherServiceAsync의 인스턴스를 생성한다. 그 다음 단계로, WeatherServiceAsyncServiceDefTarget에 캐스트 해 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의 블랙박스에서는 이런 일이 불가능하다. (라이센싱 사이드 바 참조)




위로


결론

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

기사의 원문보기





위로


다운로드 하십시오

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


참고자료

교육

제품 및 기술 얻기

토론


필자소개

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

:
Posted by 뽀기

데이터 직렬화

developerWorks

 

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



난이도 : 중급

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의 두 개의 자식 엘리먼트들은 createconvert이다.

Creat 엘리먼트

Create 엘리먼트는 DWR에게 서버 측 클래스가 Ajax 요청으로 노출되도록 명령하고, DWR이 그 클래스의 인스턴스를 얻는 방법을 정의한다. creator 애트리뷰트에는 new 값이 설정되고, 이는 DWR이 클래스의 디폴트 컨스트럭터를 호출하여 인스턴스를 얻어야 한다는 것을 의미한다. 다른 기능들은 Bean Scripting Framework (BSF)를 사용하여 스크립트 조각을 통해 인스턴스를 만들거나, IOC 컨테이너인 Spring과의 통합을 통해 인스턴스를 얻는 것이다. 기본적으로, DWR에 대한 Ajax 요청이 creator를 호출하면, 인스턴스화 된 객체가 페이지 범위에 놓이고, 요청이 완료된 후에는 더 이상 사용할 수 없다. 스테이트리스 CatalogDAO의 경우, 이것은 괜찮다.

Createjavascript 애트리뷰트는 JavaScript 코드에서 액세스 될 객체 이름을 지정한다. create 엘리먼트 내에 중첩된 param 엘리먼트는 creator가 만들 자바 클래스를 지정한다. 마지막으로, include 엘리먼트는 노출되어야 하는 메소드의 이름을 지정한다. 노출될 메소드를 명확히 드러내는 것은 잠재적으로 발생할 수 있는 위험들을 피하기에 좋다. 엘리먼트가 생략된다면 모든 클래스의 메소드들은 원격 호출에 노출될 것이다. exclude 엘리먼트를 사용하여 접근을 방지하고 싶은 메소드만 지정할 수 있다.

Convert 엘리먼트

creator가 클래스를 노출하는 것과, 웹 리모팅 방식과 연관이 있는 반면, convertor는 그러한 메소드의 매개변수와 리턴 유형과 연관이 있다. convert 엘리먼트의 역할은 서버 측 자바 객체 구현과 직렬화된 JavaScript 구현 사이에서 데이터 유형을 변환하는 방법을 DWR에게 명령하는 것이다.

DWR은 자바와 JavaScript 구현들 간 데이터 유형들을 자동으로 중재한다. 이 유형에는 자바 프리머티브와 각각의 클래스 구현, String과 Date, 어레이, 컬렉션 유형들이 포함된다. DWR은 또한 JavaBeans를 JavaScript 구현으로 변환하고, 보안 때문에, 명확한 설정이 필요하다.

Listing 2convert 엘리먼트는 DWR에게 리플렉션 기반 빈 convertor를 CatalogDAO의 노출된 메소드에 의해 리턴된 Item에 사용하고 직렬화에 포함될 Item의 멤버를 지정한다. 멤버들은 JavaBean 네이밍 규약을 사용하여 지정되어, DWR은 상응하는 get 메소드를 호출할 것이다. 이 경우, 숫자 price 필드를 생략하고, 대신 통화로 포맷된 formattedPrice 필드를 포함시켰다.

이 시점에서, dwr.xml을 나의 웹 애플리케이션의 WEB-INF 디렉토리에 전개할 준비가 되고, 이 곳에서 DWR 서블릿이 집어낸다. 진행하기 전에, 모든 것이 생각한 대로 잘 작동하는지를 확인해보는 것이 좋다.




위로


전개 테스팅

DWRServletweb.xml 정의에서 init-param debugtrue로 설정했다면 DWR의 가장 유용한 테스트 모드가 실행된 것이다. /{your-web-app}/dwr/을 검색하면 DWR이 설정했던 클래스 리스트가 나타날 것이다. 해당 클래스에 대해 상태 스크린을 클릭한다. CatalogDAO용 DWR 테스트 페이지는 그림 2에 나타나 있다. script 태그를 웹 페이지에 붙일 뿐만 아니라, 클래스용으로 생성된 DWR의 JavaScript를 가리키면서, 이 스크린 역시 클래스의 메소드 리스트를 제공한다. 이 리스트에는 클래스의 상위 유형에서 상속된 메소드가 포함되어 있고, dwr.xml에서 원격용으로 명확하게 지정한 메소드도 액세스 가능한 것으로 표시된다.


그림 2. CatalogDAO용 DWR 테스트 페이지
The diagnostic and test page generated by DWR for CatalogDAO

매개변수 값을 액세스 가능한 메소드 옆 텍스트 박스에 입력하고 Execute 버튼을 눌러 이들을 호출할 수 있다. 단순한 값이 아니라면, 서버의 응답은 경고 박스에 JSON 공지를 사용하여 디스플레이 될 것이다. 이 경우 메소드를 따라서 한 줄로 디스플레이 된다. 이러한 테스트 페이지들은 매우 유용하다. 어떤 클래스와 메소드가 원격으로 노출되어있는지 쉽게 검사할 수 있고 각 메소드가 기대한 대로 작동하는지를 테스트 할 수 있다.

일단 원격 메소드가 올바르게 실행되었다면 DWR의 JavaScript 스텁을 사용하여 클라이언트 측 코드에서 서버 측 객체들을 호출할 수 있다.




위로


원격 객체 호출하기

원격 자바 객체 메소드와 이에 상응하는 JavaScript 스텁 함수들간 매핑은 간단하다. 일반적인 폼은 JavaScriptName.methodName(methodParams ..., callBack)인데, 여기에서 JavaScriptNamecreatorjavascript 애트리뷰트로 지정된 이름이고, 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의 Itemconvertor에 포함된 필드의 콘텐트를 리턴한다. 마지막 함수는 버튼을 만들고, 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.javaMap<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에서, Cartcreatorconvertor를 추가했다. create 엘리먼트는 addItemToCart()getCart() 메소드가 원격화 되고, 생성된 Cart 인스턴트가 사용자 세션에 놓이도록 지정한다. 결과적으로, 카트의 콘텐트는 사용자 요청 사이에 되풀이 된다.

Cartconvert 엘리먼트는 원격 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;

}


simpleContentsString을 숫자로 매핑하는 JavaScript 어레이라는 것을 기억하라. 각 스트링은 아이템의 이름이고, 연관 어레이의 상응하는 숫자는 카트에 있는 아이템의 수량이다. 따라서 cart.simpleContents[item] + " x " + item 식은 "2 x Oolong 128MB CF Card"로 계산된다.

DWR Store 애플리케이션

그림 3은 DWR 기반의 Ajax 애플리케이션 실행 모습이다. 오른쪽에 사용자 쇼핑 카트와 검색된 아이템들을 디스플레이 하고 있다:


그림 3. DWR 기반의 Ajax 스토어 애플리케이션 실행 모습
Screenshot of example scenario, with search results and shopping cart



위로


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로 변환될 수 있는 것은 아니기 때문에 자바 객체에 추가 메소드를 추가하여 보다 쉽게 원격화 될 수 있도록 한다. 예제 시나리오에서, CartgetSimpleContents() 메소드를 추가하여 이 문제를 해결했다. 또한 getCart() 메소드를 추가했는데, 이것은 DWR 시나리오에서는 유용하지만 완전한 과잉이다. 원격 객체에 대한 대단위 API가 필요하고, 특정 자바 유형을 JavaScript로 변환하는 문제 때문에, 원격화된 JavaBean이 Ajax 클라이언트에만 유용한 메소드로 인해 어떻게 오염되는지를 볼 수 있다.

이를 해결하기 위해, 래퍼 클래스를 사용하여 여분의 DWR 스팩의 메소드를 JavaBean에 추가한다. JavaBean 클래스의 자바 클라이언트는 원격화와 관련된 과잉이 더 이상 없고, 원격화된 메소드에 보다 친숙한 이름을 줄 수 있다. 예를 들어, getFormattedPrice() 대신 getPrice()로 한다. 그림 4는 Cart를 래핑하여 여분의 DWR 기능을 추가하는 RemoteCart 클래스 모습이다:


그림 4. 원격 기능을 위해 Cart를 래핑하는 RemoteCart
Class diagram of RemoteCart wrapper class

마지막으로, 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
다운로드 방식에 대한 정보 Get Adobe® Reader®


참고자료

교육

제품 및 기술 얻기

토론


필자소개

Philip McCarthy는 자바와 웹 기술 전문 소프트웨어 개발 컨설턴트이다. 현재는 Hewlett Packard 연구소와 Orange에서 디지털 미디어 및 텔레콤 프로젝트에 참여하고 있으며, City of London에서 금융 소프트웨어 관련 작업을 하고 있다. philmccarthy@gmail.com

:
Posted by 뽀기

Ajax 애플리케이션에서 데이터를 직렬화 할 수 있는 다섯 가지 방법

developerWorks

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

 


난이도 : 중급

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

2005 년 10 월 04 일

Asynchronous JavaScript and XML(Ajax)를 사용하여 자바 웹을 개발하고 있다면 서버에서 클라이언트로 데이터를 전달하는 일은 아마도 최고의 관심사일 것이다. Philip McCarthy는 다섯 가지 자바 객체 직렬화 방법을 설명하고 애플리케이션에 가장 잘 맞는 데이터 포맷과 기술을 선택할 때 필요한 것이 무엇인지를 설명한다.

이 시리즈의 첫 번째 글에서는 Ajax의 구현 블록을 소개했다.

  • JavaScript의 XMLHttpRequest 객체를 사용하여 웹 페이지에서 비동기식 요청을 서버로 보내는 방법
  • 자바 서블릿으로 그 요청을 핸들 하여 응답하여 XML 문서를 클라이언트에 리턴 하는 방법
  • 클라이언트에서 응답 문서를 사용하여 페이지 뷰를 업데이트 하는 방법

이번에는 Ajax 개발의 기초를 계속해서 설명하고 많은 자바 웹 개발자들이 관심을 갖고 있는 부분에 초점을 맞추겠다. 즉 클라이언트용 데이터를 만들어내는 문제를 집중 설명하겠다.

대부분의 자바 개발자들은 모델-뷰-컨트롤러(MVC) 패턴을 웹 애플리케이션에 적용했다. 전통적인 웹 애플리케이션에서 뷰 컴포넌트는 JSP로 구성되거나 Velocity 같은 또 다른 프리젠테이션 기술들로 구성되었다. 이러한 프리젠테이션 컴포넌트들은 완전히 새로운 HTML 페이지를 만들어서 사용자 인터페이스를 업데이트 하여 사용자기 이전에 봐왔던 것을 대체해버린다. 하지만 Ajax UI를 가진 자바 웹 애플리케이션의 경우, JavaScript 클라이언트 코드는 XMLHttpRequest에 대한 응답으로 받은 데이터에 기반하여 사용자가 보고 있는 것을 업데이트 하는 궁극적인 책임이 있다. 서버의 관점에서 보면 뷰는 클라이언트 요청에 응답할 때 보내는 데이터 표현이 된다.

이 글은 자바 객체의 데이터 중심 뷰를 만드는 기술에 초점을 맞춘다. JavaBeans를 XML 문서로 변환할 때 사용할 수 있는 다양한 방법들을 설명하고 각각의 장단점도 설명한다. XML이 언제나 해결책이 되는 것은 아니다. 평이한 텍스트를 이동하는 것으로도 간단한 Ajax 요청을 처리할 수 있다. 마지막으로 JavaScript Object Notation(JSON)을 소개한다. JSON은 데이터가 직렬화 된 JavaScript 객체 그래프의 형태로 전송하여 클라이언트 측 코드와 잘 작동되도록 한다.

예제에 대하여

예제 애플리케이션과 여러 사용 케이스들을 통해 이 글에서 논의되는 기술의 특징과 기술들을 설명할 것이다. 그림 1은 이 예제 사용 케이스를 나타내는 간단한 데이터 모델이다. 모델은 온라인 스토어의 사용자 계정을 나타내고 있다. 사용자는 이전 주문의 컬렉션을 갖고 있고 각 주문은 여러 Item들로 구성된다.


그림 1. 객체 모델
Object model representing customer's account

XMLHttpRequest가 요청 데이터를 보내는데 사용되는 포맷에는 제한이 없지만 대부분의 경우 전통적인 형식 매개변수들을 보내는 것이 알맞다. 따라서 나의 논의도 서버의 응답에 초점을 맞춘다. 응답은 텍스트 기반 포맷을 갖고 있지만 이름에서 시사하듯, XMLHttpRequest는 XML 응답 데이터를 처리하는 빌트인 기능을 갖고 있다. 이것 때문에 XML이 Ajax 응답의 기본 수단이 된다.




위로


자바 클래스에서 XML 만들기

Ajax 응답을 XML로 전달하는 많은 이유가 있다. 모든 Ajax를 실행할 수 있는 브라우저에는 XML 문서들을 검색할 수 있는 메소드를 갖고 있고 XML 데이터로 잘 작동하는 서버 측 기술이 있다. Ajax 클라이언트와 서버 간 콘트랙트를 정의하는 것은 쉽다. 스키마를 만들어서 교환될 문서의 유형을 기술하고 서비스 지향 방식을 취한다면 XML을 사용하는 것으로도 비 Ajax 클라이언트는 데이터 피드를 소비할 수 있다.

자바 객체에서 XML 데이터를 만들 수 있는 세 가지 방법들과 각각의 장단점도 분석해 보자.




위로


'Roll-your-own' 직렬화

우선, 자신의 객체 그래프에서 XML을 프로그램 방식으로 생성할 수 있다. 이 방식은 JavaBean 클래스에 toXml()을 구현하는 것만큼 간단하다. 그런 다음 알맞은 XML API를 선택하여 각 빈이 엘리먼트를 제거하여 상태를 표현하고 객체 그래프를 반복적으로 호출하도록 한다. 분명한 것은 이 방식은 많은 클래스 까지는 적용될 수 없다는 점이다. 각자 고유의 XML 생성 코드가 작성되어야 하기 때문이다. 구현이 쉽고, 추가 설정 또는 보다 복잡한 구현 프로세스의 관점에서 볼 때 오버헤드가 없으며 두 개의 호출로 JavaBeans로 구성된 어떤 그래프도 XML 문서로 변환될 수 있다는 점은 장점이다.

이전 글에 쓰였던 예제 코드에서 toXml() 메소드를 구현했다. XML 마크업의 스트링을 함께 붙였다. 그 때 언급했듯이 이것은 toXml() 메소드의 코드에 대한 태그 밸런스를 확인하고 엔터티가 인코딩 되는지를 확인해야 하는 까다로운 방식이다. 자바 플랫폼에서 사용할 수 있는 여러 XML API들은 이 작업을 수행한다. 따라서 XML의 내용에만 집중할 수 있다. Listing 1은 JDOM API를 사용하여 온라인 스토어 예제에서 주문을 나타내는 클래스에 toXml()을 구현하는 모습이다. (그림 1)


Listing 1. Order 클래스를 위한 toXml()의 JDOM 구현


public Element toXml() {

  Element elOrder = new Element("order");
  elOrder.setAttribute("id",id);

  elOrder.setAttribute("cost",getFormattedCost());

  Element elDate = new Element("date").addContent(date);
  elOrder.addContent(elDate);

  Element elItems = new Element("items");
  for (Iterator<Item> iter = 
   items.iterator() ; iter.hasNext() ; ) {
    elItems.addContent(iter.next().toXml());
  }
  elOrder.addContent(elItems);

  return elOrder;
}

JDOM을 사용하여 엘리먼트를 생성하고, 애트리뷰트를 설정하며, 엘리먼트 콘텐트를 추가하는 것이 얼마나 쉬운지를 알 수 있다. 합성 JavaBeans의 toXml() 메소드에 대한 반복 호출로 하위그래프의 Element 구현을 얻을 수 있다. 예를 들어, items 엘리먼트의 콘텐트는 Order에 모아진 각 Item 객체에 대해 toXml()을 호출하면 생성된다.

일단 JavaBeans 모두 toXml() 메소드를 구현하면 어떤 임의의 객체 그래프도 XML 문서로 직렬화 하고 이를 Ajax 클라이언트로 리턴 하기는 간단하다. (Listing 2)


Listing 2. JDOM 엘리먼트에서 XML 응답 만들기


public void doGet(HttpServletRequest req, HttpServletResponse res)
  throws java.io.IOException, ServletException {

    String custId = req.getParameter("username");
    Customer customer = getCustomer(custId);

    Element responseElem = customer.toXml();
    Document responseDoc = new Document(responseElem);

    res.setContentType("application/xml");
    new XMLOutputter().output(responseDoc,res.getWriter());
}

JDOM이 여기에서도 능력을 발휘한다. 객체 그래프의 루트로서 리턴된 XML 엘리먼트 주위에 Document를 래핑하고 XMLOutputter를 사용하여 서블릿 응답에 이 문서를 작성하면 된다. Listing 3은 이 방식으로 만들어지고, XMLOutputter를 JDOM의 Format.getPrettyFormat()으로 초기화 하여 포맷된 XML 샘플이다. 이 예제에서 사용자는 두 개의 Item으로 구성된 한 개의 주문만 했다.


Listing 3. 사용자를 나타내는 XML 문서 샘플


<?xml version="1.0" encoding="UTF-8"?>
<customer username="jimmy66">
  <realname>James Hyrax</realname>
  <orders>
    <order id="o-11123" cost="$349.98">
      <date>08-26-2005</date>
      <items>
        <item id="i-55768">
          <name>Oolong 512MB CF Card</name>
          <description>512 Megabyte Type 1 CompactFlash card. 
          Manufactured by Oolong Industries</description>
          <price>$49.99</price>
        </item>
        <item id="i-74491">
          <name>Fujak Superpix72 Camera</name>
          <description>7.2 Megapixel digital camera featuring six 
          shooting modes and 3x optical zoom. Silver.</description>
          <price>$299.99</price>
        </item>
      </items>
    </order>
  </orders>
</customer>


'Roll-your-own' 직렬화의 단점

재미있게도, Listing 3은 JavaBeans가 스스로를 XML로 직렬화 할 때의 단점을 드러내고 있다. 이 문서가 사용자에게 Order History 뷰를 제공하는데 사용되었다고 가정해 보자. 이 경우 지난 주문 시 모든 Item의 전체 디스크립션 까지는 디스플레이 하지 않거나 사용자에게 이름을 묻고 싶지는 않을 것이다. 하지만 이 애플리케이션이 검색 결과를 Item 빈의 리스트로 리턴하는 ProductSearch 클래스를 갖고 있을 경우 Item의 XML 표현에 이 디스크립션이 포함되도록 하는 것이 유용하다. 더욱이 현재 재고 수준을 나타내는 Item 클래스에 대한 추가 필드는 Product Search 뷰에 디스플레이 하기에 유용한 정보이다. 하지만 이 필드는 Item을 포함하고 있는 객체 그래프에서 직렬화 된다. 현재 재고 레벨이 Customer의 Order History와 관계가 없을지라도 말이다.

디자인 관점에서 보면 이것은 뷰 생성과 연결되는 데이터 모델의 전통적인 문제이다. 각 빈은 일방으로 스스로를 직렬화하고 one-size-fits-all 접근 방식으로 쓸모 없는 데이터를 교환하고 클라이언트 코드가 문서에서 필요한 정보를 배치하는 것이 어려워지고 클라이언트 측에서 대역폭 소비와 XML 파싱 시간이 늘어난다는 것을 의미한다. 이러한 커플링의 다른 결과는 XML 문법이 자바 클래스와 독립적으로 돌아갈 수 없다는 것이다. 예를 들어 사용자 문서의 스키마 변경이 여러 자바 클래스에 영향을 미쳐서 이것도 변경 및 재 컴파일 되도록 한다.

나중에 더 자세히 설명하도록 하고, 우선은 ‘Roll-your-own’ 직렬화 방식의 확장성 문제에 대한 솔루션부터 살펴보기로 하자.




위로


XML 바인딩 프레임웍

최근 몇 년 동안, XML 문서를 자바 객체 그래프로의 바인딩 과정을 간소화 할 여러 자바 API들이 개발되었다. 대부분이 XML 마샬링과 언마샬링을 제공한다. 다시 말해서 자바 객체 그래프와 XML간 투웨이(two-way) 변환을 수행한다는 의미이다. 이러한 프레임웍들은 XML을 핸들링 하는 모든 작업들을 캡슐화 한다. 애플리케이션 코드는 오직 순수 자바 객체만 처리한다. 또한 밸리데이션 같은 유용한 기능들도 제공한다. 일반적으로 이러한 프레임웍은 두 개의 다른 접근 방식을 취한다. 코드 생성과 객체-XML 매핑이 그것이다.

코드 생성 방식

코드 생성 방식을 적용하는 바인딩 프레임웍에는 XMLBeans, JAXB, Zeus, JBind가 포함된다. Castor 역시 이 기술을 사용한다. 이 프레임웍의 시작점은 문서의 데이터 유형을 기술하는 XML 스키마이다. 이 프레임웍에서 제공하는 툴을 사용하여 스키마 정의된 유형들을 나타내는 자바 클래스를 생성한다. 마지막으로 이렇게 생성된 클래스를 사용하여 모델 데이터를 나타내고 이들을 직렬화 하여 XML로 직렬화 한다.

코드 생성 방식은 애플리케이션이 큰 XML 문법을 사용할 경우에 유용하다. 수십 개의 클래스들에 걸쳐 XML 직렬화 메소드를 작성하는 문제가 사라진다. 한편 자신의 JavaBeans를 더 이상 정의하지 않아도 된다. 프레임웍에서 생성된 자바 클래스는 일반적으로 XML의 구조를 따른다. 생성된 클래스들은 "죽은" 데이터 컨테이너가 된다. 일반적으로 작동을 추가할 수 없기 때문이다. 애플리케이션 코드가 스키마에서 생성된 유형과 잘 작동하도록 해야 한다. 또 다른 단점은 스키마에서 변경사항이 생기면 생성된 클래스도 변경된다. 더 나아가 관련 코드에도 영향을 미친다.

이러한 유형의 XML 바인딩 프레임웍은 데이터 언마샬링(XML 문서를 소비하고 이들을 자바 객체로 변환하기)에 가장 유용하다. 거대한 데이터 모델이 아닌 이상, 그리고 클래스를 생성하여 혜택을 보는 경우가 아니라면 코드 생성 기반 프레임웍은 Ajax 애플리케이션에는 과분하다.

매핑 방식

매핑을 사용하는 프레임웍에는 Castor와 Apache Commons Betwixt가 있다. 매핑은 코드 생성 보다 유연하고 경량의 솔루션이다. 우선 일상적인 방식으로 JavaBeans를 코딩한다. 그런 다음, 런타임 시 프레임웍 내부의 마샬러(marshaler)에 호출하여 객체 멤버의 이름, 유형, 값을 기반으로 XML 문서를 만들어 낸다. 클래스에 대해 매핑 파일을 정의하면 디폴트 바인딩 전략을 무시하고 마샬러에게 원하는 클래스 표현 방식을 권할 수 있다

이 방식은 확장성과 유연성을 서로 타협한 방식이다. 원하는 방식으로 자바 클래스를 작성하고 마샬러가 XML을 처리한다. 하지만 매핑 정의 파일을 쉽게 작성할 수 있고 확장성도 좋지만 매핑 규칙들은 표준 바인딩 작동을 너무 많이 변경할 수 있고 객체의 구조와 XML 간 커플링이 어느 정도 남아있게 된다. 결과적으로 자바 표현 아니면 XML 표현 중 하나를 타협해야 한다.

데이터 바인딩 요약

Dennis Sosnoski는 코드 생성과 코드 매핑에 대한 XML 데이터 바인딩 API에 대해 자세히 연구했다. 그의 글을 읽어보기 바란다.(참고자료)

대체로, 코드 생성 방식은 Ajax 애플리케이션에 유연성과 편의를 제공한다. 한편 매핑 기반 프레임웍은 필요한 XML을 객체에서 만들어낼 정도로 충분한 매핑 전략을 만드는 한 괜찮은 방식이다.

XML 바인딩 API 모두 수동 직렬화 기술에 단점이 있다. 바로 모델과 뷰의 커플링이다. 각 객체 유형에 대한 한 개의 XML 구현으로 제한한다는 것은 네트워크를 통해 이동하는 과잉의 데이터가 생긴다는 것을 의미한다. 더 심각한 문제는 어떤 경우에는 직렬화 된 뷰를 획득할 수 없다는 것이다.

전통적인 웹 애플리케이션 개발에서, 페이지 템플릿 시스템은 뷰 생성을 컨트롤러 로직과 모델 데이터에서 깨끗하게 분리하는데 사용된다. 이 방식 역시 Ajax 시나리오에 도움이 된다.




위로


페이지 템플릿 시스템

범용 페이지 템플릿 기술은 XML을 생성하는데 사용될 수 있는 기술이다. Ajax 애플리케이션이 데이터 모델에서 임의의 XML 응답 문서를 만든다. 더욱이 이 템플릿들은 단순하고 표현적인 마크업 언어를 사용하여 작성될 수 있다. Listing 4는 Customer 빈을 사용하여, 클라이언트 코드가 Order History 컴포넌트를 만들기에 적합하도록 커스텀 XML 뷰를 렌더링 한다.


Listing 4. 주문 히스토리 문서를 만드는 JSP 페이지


<?xml version="1.0"?>
<%@ page contentType="application/xml" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:set var="cust" value="${requestScope.customer}"/>

<orderhistory username="${cust.username}">
<c:forEach var="order" items="${cust.orders}">
  <order id="${order.id}" cost="${order.formattedCost}">
    <date>${order.date}</date>
    <items>
    <c:forEach var="item" items="${order.items}">
      <item id="${item.id}">
        <name><c:out value="${item.name}" escapeXml="true"/></name>
        <price>${item.formattedPrice}</price>
      </item>
    </c:forEach>
    </items>
  </order>
</c:forEach>
</orderhistory>


이 템플릿은 Order History 뷰에 필요한 데이터만 만든다. 아이템의 디스크립션 같은 쓸모 없는 것은 만들지 않는다. 각 아이템에 대한 전체 디스크립션과 재고 레벨이 포함된 Product Search 뷰에 대한 커스텀 XML을 만들기도 쉽다.

이 템플릿의 문제점

이 템플릿의 문제는 필요한 모든 다른 뷰에 대해 새로운 JSP를 만들어야 한다는 점이다. 필요한 객체 그래프를 어셈블링하고 이를 직렬화 하는 것과는 대조된다. 디자인 관점에서 볼 때 서버가 만들어낼 문서 유형들을 생각하기 때문에 좋은 것이라고 할 수 있다. 또한 XML 스팩의 API가 아닌 일반 템플릿 환경에서 작업하기 때문에 태그 밸런싱이 되는지, 엘리먼트와 애트리뷰트 순서가 정확한지, XML 엔터티(< 또는 &)가 없어졌는지를 확인하는 것은 나의 몫이다. JSP의 out 태그로 후자의 태스크를 수월하게 할 수 있지만, 모든 템플릿 기술이 그 방식을 제공하는 것은 아니다. 마지막으로 서버 측에서, 스키마에 대해 생성된 XML 문서의 유효성 검사를 할 수 있는 쉬운 방법이란 없다. 하지만 어쨌든 제품 환경에서는 이를 수행해야 하고 개발하는 동안 쉽게 해결할 수 있는 문제이다.




위로


XML 없는 응답 데이터

지금까지, 여기에서 다룬 모든 기술들은 XML 문서 형태로 서버 응답을 만들어 낸다. 하지만 XML에도 몇 가지 문제가 있다. 그 중 하나는 레이턴시(Latency)와 관련이 있다. 브라우저는 XML 문서를 파싱하지 않고 DOM 모델들을 즉시 만들지 않기 때문에 Ajax 컴포넌트에 필요한 신속함이 줄어든다. 특히 느린 머신에서 큰 문서를 파싱할 때 그렇다. 한 예로, 검색 결과가 서버에서 보내지고 사용자에게 디스플레이 되는 곳의 "라이브 검색"을 들 수 있다. 라이브 검색 컴포넌트는 빠르게 응답하는 것이 중요한데, 이와 동시에 서버 응답을 빠르고 지속적으로 파싱해야 한다.

레이턴시는 중요한 고려 사항이다. 하지만 XML을 피하는 가장 큰 이유는 어색한 클라이언트 측 DOM API이다. 브라우저 호환 방식으로 DOM을 통해 값을 얻기 위해 어떤 명령도 따라야 한다. (Listing 5)


Listing 5. JavaScript에서 XML 응답 문서 검색하기


// Find name of first item in customer's last order
var orderHistoryDoc = req.responseXML;

var orders = orderHistoryDoc.getElementsByTagName("order");
var lastOrder = orders[orders.length - 1];

var firstItem = lastOrder.getElementsByTagName("item")[0];
var itemNameElement = firstItem.firstChild;

var itemNameText = itemNameElement.firstChild.data;

엘리먼트들 간 공백이 있다면 상황은 더 복잡해 진다. 모든 엘리먼트의 firstChild는 언제나 공백 텍스트 노드가 되기 때문이다. JavaScript 라이브러리는 XML 문서를 보다 손쉽게 처리하는데 도움이 된다. Sarissa(참고자료)와 Google-ajaXSLT가 있는데 이 두 가지 모두 XPath 기능을 대부분의 브라우저에 추가한다.

대안도 생각해 볼 수 있다. responseXML 외에도 XMLHttpRequest 객체는 responseText 속성을 제공한다. 이것은 서버 응답 바디(server's response body)를 스트링으로 제공한다.

responseText 속성

responseText는 서버가 매우 간단한 값을 클라이언트로 보내야 할 때 매우 편리하다. 대역폭을 피하고 XML의 오버헤드를 처리한다. 간단한 true/false 응답은 평이한 텍스트로 서버에 의해 리턴될 수 있다. 간단한 콤마 분리형 리스트로 된 이름 또는 번호 역시 가능하다. 일반적으로 XML 응답과 평이한 텍스트 응답을 같은 애플리케이션에 섞지 않는 것이 가장 좋다. 한 개의 데이터 포맷을 고수하면 코드 추상화와 재사용이 간단해 진다.

responseText는 XML 응답 데이터의 결합에도 유용하다. 응답 문서에서 하나의 값만 추출해야 할 때 "치팅(cheat)" 하는 것이 더 편리할 수 있다. XML을 구조화된 문서 보다는 텍스트의 스트링으로 간주한다. Listing 6은 사용자의 Order History에서 첫 번째 주문 날짜를 추출할 때 정규식을 사용하는 방법을 보여준다. 이것은 핵(hack)일 뿐이므로 XML 문서의 어휘 표현에 의존해서는 안된다.


Listing 6. XMLHttpRequest의 responseText 객체로 정규식 사용하기


var orderHistoryText = req.responseText;
var matches = orderHistoryText.match(/<date>(.*?)<\/date>/);

var date = matches[1];

이러한 방식으로 responseText를 사용하면 어떤 상황에서는 편리할 수 있다. 이상적으로는 JavaScript에서 쉽게 검색될 수 있는 포맷에 복잡하고 구조화된 데이터를 표현하는 방식이다. XML의 프로세싱 오버헤드가 없다.




위로


JavaScript Object Notation

JavaScript 객체들은 대부분 공동 어레이, 숫자 인덱싱 어레이, 스트링, 숫자, 이러한 유형들의 중첩 결합으로 구성된다. 모든 유형들이 JavaScript에서 선언될 수 있기 때문에 객체 그래프를 한 문장에 정적으로 정의할 수 있다. Listing 7은 JSON 신택스를 사용하는 객체를 선언하고 이것이 액세스 되는 방법을 보여준다. 중괄호는 공동 어레이(객체)를 의미한다. 키 값 상은 콤마로 분리된다. 대괄호는 숫자 인덱싱 어레이를 나타낸다.


Listing 7. JSON을 사용하여 JavaScript에 객체 선언하기


var band = {
  name: "The Beatles",
  members: [
    {
      name: "John",
      instruments: ["Vocals","Guitar","Piano"]
    },
    {
      name: "Paul",
      instruments: ["Vocals","Bass","Piano","Guitar"]
    },
    {
      name: "George",
      instruments: ["Guitar","Vocals"]
    },
    {
      name: "Ringo",
      instruments: ["Drums","Vocals"]
    }
  ]
};

// Interrogate the band object
var musician = band.members[3];
alert( musician.name
        + " played " + musician.instruments[0] 
        + " with " + band.name );

JSON은 재미있는 언어 기능이기는 한데 이것이 Ajax와 어떤 관계가 있는가? JSON을 사용하여 JavaScript 객체 그래프를 네트워크를 통해 Ajax 서버의 응답으로 보낼 수 있다. DOM API를 통해 클라이언트 상에서 XML을 검색하는 것에서 탈피하여 JSON 응답을 계산하고 JavaScript 객체 그래프에 지속적으로 액세스 할 수 있다는 의미이다. 우선 JavaBeans를 JSON으로 변경해야 한다.

자바 클래스에서 JSON 만들기

다른 XML 생성 기술들에 적용되는 것과 같은 장단점이 JSON을 만드는데도 적용된다. 프리젠테이션 템플릿 기술을 다시 사용해야 하는 케이스가 있다. 하지만 JSON을 사용하면 개념상으로는 애플리케이션 상태 뷰를 만드는 것 보다 애플리케이션 티어 사이에 직렬화 된 객체를 전달하는 것에 더 가깝다. org.json 자바 API를 사용하여 toJSONObject() 메소드를 구현하는 방법을 설명하겠다. JSONObjects는 JSON으로 직렬화 될 수 있다. Listing 8은 Listing 1을 되풀이하여 Order클래스용 toJSONObject() 구현을 보여준다.


Listing 8. Order 클래스용 toJSONObject() 메소드 구현


public JSONObject toJSONObject() {

  JSONObject json = new JSONObject();
  json.put("id",id);
  json.put("cost",getFormattedCost());
  json.put("date",date);

  JSONArray jsonItems = new JSONArray();
  for (Iterator<Item> iter = 
   items.iterator() ; iter.hasNext() ; ) {
    jsonItems.put(iter.next().toJSONObject());
  }
  json.put("items",jsonItems);

  return json;
}

org.json API는 매우 간단하다. JSONObject는 JavaScript 객체(공동 어레이)를 나타내고 String 키와 값(프리머티브 String 유형 또는 또 다른 JSON 유형)을 갖고 있는 다양한 put() 메소드를 취한다. JSONArray는 인덱싱 어레이를 나타내기 때문에 put() 메소드는 하나의 값만 취한다. Listing 8을 보면 jsonItems 어레이를 만들고 이것을 put()과 함께 json 객체에 추가하는 대안 방식은 각 아이템에 대하여 json.accumulate("items",iter.next().toJSONObject());를 호출한다. accumulate() 메소드는 put()과 비슷하다. 다만 그 값을 키로 구분된 인덱싱 어레이에 붙인다는 것만 다르다.

Listing 9는 JSONObject를 직렬화하고 이것을 서블릿의 응답에 작성하는 방법을 보여준다.


Listing 9. JSONObject에서 직렬화 된 JSON 응답 만들기


public void doGet(HttpServletRequest req, HttpServletResponse res) 
  throws java.io.IOException, ServletException {

	String custId = req.getParameter("username");
	Customer customer = getCustomer(custId);

	res.setContentType("application/x-json");
	res.getWriter().print(customer.toJSONObject());
}

사실 아무것도 없다. 암시적으로 호출된 JSONObject에 대한 toString() 메소드가 모든 일을 한다. application/x-json 콘텐트 유형은 약간 모호하다. 이것을 작성할 당시 JSON의 MIME 유형에 대한 동의가 없었다. 하지만 application/x-json은 지금으로서는 최선의 선택이다. Listing 10은 서블릿 코드에 대한 응답 예제이다.


Listing 10. Customer 빈의 JSON 구현


{
  "orders": [
    {
      "items": [
        {
          "price": "$49.99",
          "description": "512 Megabyte Type 1 CompactFlash card. 
                              Manufactured by Oolong Industries",
          "name": "Oolong 512MB CF Card",
          "id": "i-55768"
        },
        {
          "price": "$299.99",
          "description": "7.2 Megapixel digital camera featuring six 
            shooting modes and 3x optical zoom. Silver.",
          "name": "Fujak Superpix72 Camera",
          "id": "i-74491"
        }
      ],
      "date": "08-26-2005",
      "cost": "$349.98",
      "id": "o-11123"
    }
  ],
  "realname": "James Hyrax",
  "username": "jimmy66"
}

클라이언트에서 JSON 소비하기

이 프로세스의 마지막 단계는 JSON 데이터를 클라이언트 상의 JavaScript 객체로 변환하는 것이다. JavaScript 식을 포함하고 있는 스트링을 인터프리팅하는 함수인 eval()을 호출하면 된다. Listing 11은 JSON 응답을 JavaScript 객체 그래프로 변환하고 사용자의 마지막 주문에서 첫 번째 아이템의 이름을 획득하는 Listing 5의 태스크를 수행한다


Listing 11. JSON 응답 계산하기


var jsonExpression = "(" + req.responseText + ")";
var customer = eval(jsonExpression);

// Find name of first item in customer's last order
var lastOrder = customer.orders[customer.orders.length-1];
var name = lastOrder.items[0].name;

Listing 11을 Listing 5와 비교해 보면 JSON을 사용하는 클라이언트에 어떤 이점이 있다는 것을 알 수 있다. Ajax 프로젝트가 클라이언트 상에서 수 많은 복잡한 서버 응답을 검색해야 한다면 JSON이 제격이다. JSON과 XMLHttpRequest를 함께 두면 Ajax 인터랙션이 시작하여 SOA 요청 보다는 RPC 호출 처럼 보인다. 다음 글에서 자세히 설명하겠다.

JSON의 단점

JSON도 단점이 있다. 여기에서 설명한 JSON 방식을 사용하면 요청 기반으로 객체의 직렬화를 맞출 방법이 없기 때문에 불필요한 필드가 네트워크를 통해 보내질 수 있다. 게다가 toJSONObject() 메소드를 각 JavaBean에 추가하면 스케일링이 잘 되지 않는다. 내부검사와 JavaBean을 JSON 시리얼라이저에 작성하는 것은 간단하더라도 말이다. 마지막으로 서버 측 코드가 서비스 지향이고 Ajax 클라이언트에서 요청을 핸들링 하도록 지정되지 않았다면 유비쿼터스 지원이 되는 XML이 더 낫다.




위로


직렬화 기술 비교

서버 측 자바 상태를 Ajax 클라이언트로 전송할 때의 다섯 가지 다른 기술들을 보았다. 직접적인 XML 직렬화, 코드 생성을 통한 XML 바인딩, 매핑 메커니즘을 통한 XML 바인딩, 템플릿 기반 XML 생성, JSON으로의 직렬화를 설명했다. 이들 모두 저마다의 장단점을 갖고 있고, 가장 잘 맞는 애플리케이션 아키텍쳐도 다르다.

각 방식의 장단점을 다음 여섯 가지 범주로 분류하여 표 1에 요약했다.

확장성(Scalability)
이 방식이 대규모의 데이터 유형들과 얼마나 잘 맞는가? 코딩과 설정 워크로드가 그 추가 유형으로 인해 증가하는가?
통합 용이성(Ease of integration)
해당 기술을 프로젝트와 수월하게 통합되는가? 좀더 복잡한 구현 프로세스가 필요한가? 개발 복잡성이 늘어나는가?
Java class API
이 방식에 적용된 서버 측 자바 객체와 쉽게 작동하는가? 표준 빈을 작성해야 하거나 어색한 문서로 작업해야 하는가?
아웃풋 제어(Control over output)
직렬화 된 클래스 구현을 얼마나 정밀하게 제어할 수 있는가?
뷰 유연성(View flexibility)
다양하게 개인화 된 데이터 직렬화가 같은 객체 세트에서 구현될 수 있는가?
클라이언트 데이터 액세스(Client data access)
JavaScript 코드가 서버의 응답 데이터와 쉽게 작동하는가?

표 1. 데이터 생성 기술 비교
Roll-your-own XML 코드 생성을 통한 XML 바인딩 매핑을 통한 XML 바인딩 페이지 템플리팅 XML 핸드코딩 JSON 직렬화
확장성 나쁨 좋음 보통 보통 나쁨
통합 용이성 좋음 나쁨 보통 보통 좋음
Java class API 좋음 나쁨 좋음 좋음 좋음
아웃풋 제어 좋음 좋음 보통 좋음 좋음
뷰 유연성 나쁨 나쁨 나쁨 좋음 나쁨
클라이언트 데이터 액세스 나쁨 나쁨 나쁨 보통 좋음




위로


맺음말

표 1의 내용은 어떤 직렬화 기술이 더 좋은지를 가려내려는 것이 아니다. 여섯 가지 범주의 중요도는 프로젝트의 특성에 따라 달라진다. 수 백 가지의 데이터 유형들을 다루어야 할 경우 확장성이 필요하다. 따라서 코드 생성 방식을 사용하는 것이 좋다. 여러 가지 다른 뷰를 가진 데이터 모델을 생성하려면 페이지 템플리팅이 최선의 방법이다. 규모가 작은 프로젝트라서 직접 작성해야 하는 JavaScript의 양을 줄이려면 JSON이 제격이다.

직렬화 기술을 선택할 때 도움이 되기 바란다. 참고자료 섹션에 이 글에 설명한 기술들을 보다 자세히 공부할 수 있도록 링크를 수록하였다. 다음 시리즈도 기대해 주기 바란다. Direct Web Remoting(DWR)을 사용하여 자바 Ajax 애플리케이션을 작성하는 방법을 설명할 것이다. DWR 프레임웍에서는 JavaScript 코드에서 직접 자바 클래스에 대해 메소드를 호출할 수 있다. 데이터 직렬화를 관리하여 보다 고급 추상화 레벨에서 Ajax를 사용할 수 있다.




위로


참고자료

교육

제품 및 기술 얻기

토론



위로


필자소개

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

:
Posted by 뽀기

Ajax를 이용한 웹 애플리케이션 구현

developerWorks

 

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



난이도 : 초급

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

2005 년 9 월 20 일
2006 년 10 월 24 일 수정

페이지 리로드 사이클은 웹 애플리케이션 개발에 있어서 가장 큰 사용 장애이자 자바 개발자들에게는 심각한 도전 과제이다. Philip McCarthy가 혁신적인 동적 웹 애플리케이션 구현 방법을 소개한다. Ajax (Asynchronous JavaScript and XML)는 자바, XML, JavaScript가 혼합된 프로그래밍 기술이다. 자바 기반 웹 애플리케이션의 페이지 리로드 패러다임을 과감히 바꾼다.

시리즈 소개


Ajax (Asynchronous JavaScript and XML)는 클라이언트 측 스크립팅을 사용하는 웹 애플리케이션 개발 방식으로서 데이터를 웹 서버와 교환한다. 따라서 웹 페이지는 동적으로 업데이트 될 수 있다. 전체 페이지를 리프레시 하여 인터랙션 플로우에 영향을 끼치지도 않는다. Ajax를 사용하여 보다 풍부하고 동적인 웹 애플리케이션 사용자 인터페이스를 만들 수 있다. 원시 데스크탑 애플리케이션의 가용성에 접근한 풍부하고 동적인 웹 애플리케이션 사용자 인터페이스이다.

Ajax는 기술이 아니다. 오히려 패턴에 더 가깝다. Ajax는 많은 개발자들에게는 생소할지 모르지만, Ajax 애플리케이션을 구현한 모든 컴포넌트들은 여러 해 동안 존재했다. 2004년과 2005년에 Ajax 기술에 기반한 동적 웹 UI가 생겨나면서 주목을 받게 되었다. 대표적인 예로 Google의 Gmail과 Maps 애플리케이션 그리고 사진 공유 사이트인 Flickr을 들 수 있다. 이러한 UI들은 몇몇 웹 개발자들에 의해 "Web 2.0"으로 불릴 만큼 혁신적이었다. 이에 따라 Ajax 애플리케이션에 대한 인기도 하늘로 치솟았다.

이 시리즈를 통해 Ajax를 사용하여 애플리케이션을 개발할 때 필요한 모든 툴들을 소개하겠다. 우선 이 첫 번째 글에서는 Ajax의 개념을 설명하고, 자바 기반 웹 애플리케이션에 Ajax 인터페이스를 구현하는 기본적인 단계들을 설명하겠다. 코드 예제를 사용하여 서버측 자바 코드와 클라이언트측 JavaScript를 설명하겠다. 이것이 바로 동적인 Ajax 애플리케이션의 핵심축을 구성한다. 마지막으로 Ajax 방식의 단점과 Ajax 애플리케이션을 구현할 때, 반드시 고려해야 할 가용성 및 접근성 문제들도 설명하도록 하겠다.

더 나은 쇼핑 카트

Ajax를 사용하여 전통적인 웹 애플리케이션을 만들 수 있다. 페이지 부하를 줄여서 인터랙션을 간소하게 할 수 있다. 아이템들이 추가될 때 마다 동적으로 업데이트 되는 쇼핑 카트 예제를 설명하겠다. 온라인 스토어와 결합된 이 방식을 사용하면, 사용자들은 전체 페이지가 업데이트 될 때까지 기다리지 않고도 카트에 아이템들을 검색 및 추가할 수 있다. 이 글에 소개한 몇몇 코드는 이 쇼핑 카트 예제에만 국한된 것이지만, 여기에 사용된 기술들은 Ajax의 어떤 애플리케이션에나 적용될 수 있다. Listing 1은 쇼핑 카트 예제에서 사용되는 HTML 코드이다.


Listing 1. 쇼핑 카트 예제
				
<!-- Table of products from store's catalog, one row per item -->
<th>Name</th> <th>Description</th> <th>Price</th> <th></th>
...
<tr>
  <!-- Item details -->
  <td>Hat</td> <td>Stylish bowler hat</td> <td>$19.99</td>
  <td>
    <!-- Click button to add item to cart via Ajax request -->
    <button onclick="addToCart('hat001')">Add to Cart</button>
  </td>
</tr>
...

<!-- Representation of shopping cart, updated asynchronously -->
<ul id="cart-contents">

  <!-- List-items will be added here for each item in the cart -->
  
</ul>

<!-- Total cost of items in cart displayed inside span element -->
Total cost: <span id="total">$0.00</span>

			




위로


Ajax 작동

Ajax 인터랙션은 XMLHttpRequest라고 하는 JavaScript 객체로 시작한다. 이름만 보아도 알겠지만 이것은 클라이언트측 스크립트가 HTTP 요청을 수행하도록 하고 XML 서버 응답을 파싱한다. Ajax 연구의 첫 번째 단계는 XMLHttpRequest 인스턴스를 만드는 것으로 시작한다. 이 요청(GET 또는 POST)에 사용되는 HTTP 메소드와 도착지 URL은 XMLHttpRequest 객체에서 설정된다.

이제 Ajax의 첫 번째 단어 a가 비동기식(asynchronous)을 의미한다는 것을 기억하라. HTTP 요청을 보내면 서버가 응답할 때 까지 기다릴 것을 브라우저에 요청하지 않아도 된다. 대신 사용자와 페이지 간 인터랙션에 지속적으로 반응하고, 서버의 응답이 도착하면 이를 다루도록 요청한다. 이를 위해서 XMLHttpRequest로 콜백 함수를 등록하고, XMLHttpRequest를 비동기식으로 실행한다. 컨트롤이 브라우저에 리턴되지만, 콜백 함수는 서버 응답이 도착하면 호출될 것이다.

자바 웹 서버에 요청은 다른 HttpServletRequest와 마찬가지로 도착한다. 요청 매개변수를 파싱한 후에 서블릿은 필요한 애플리케이션 로직을 호출하고, 응답을 XML로 직렬화 하고, 이를 HttpServletResponse에 작성한다.

다시 클라이언트로 돌아가서, XMLHttpRequest에 등록된 콜백 함수가 서버에서 리턴된 XML 문서를 처리하도록 호출된다. 마지막으로, 사용자 인터페이스는 서버로부터 온 데이터에 대한 응답으로 업데이트된다. 이때 JavaScript를 사용하여 페이지의 HTML DOM을 조작한다. 그림 1은 Ajax의 시퀀스 다이어그램이다.


그림 1. Ajax 시퀀스 다이어그램
Sequence diagram of the Ajax roundtrip

위 그림은 고급 시퀀스 다이어그램이다. 각 단계를 자세히 설명하도록 하겠다. 그림 1에서 보면 Ajax의 비동기식 방식 때문에 시퀀스가 단순하지 않다.




위로


XMLHttpRequest 실행

Ajax 시퀀스의 시작 부분부터 설명하겠다. 브라우저에서 XMLHttpRequest를 생성 및 실행하는 Ajax 시퀀스의 시작 부분부터 설명하겠다. 불행히도 XMLHttpRequest를 만드는 방식은 브라우저마다 다르다. Listing 2의 JavaScript 함수는 부드럽게 진행되면서 현재 브라우저에 맞는 정확한 방식을 찾고, 사용 준비가 된 XMLHttpRequest를 리턴한다. 이것을 JavaScript 라이브러리에 복사하여 XMLHttpRequest가 필요할 때 사용하도록 한다.


Listing 2. 크로스 브라우저 XMLHttpRequest 구현하기
				
/*
 * Returns a new XMLHttpRequest object, or false if this browser
 * doesn't support it
 */
function newXMLHttpRequest() {

  var xmlreq = false;

  if (window.XMLHttpRequest) {

    // Create XMLHttpRequest object in non-Microsoft browsers
    xmlreq = new XMLHttpRequest();

  } else if (window.ActiveXObject) {

    // Create XMLHttpRequest via MS ActiveX
    try {
      // Try to create XMLHttpRequest in later versions
      // of Internet Explorer

      xmlreq = new ActiveXObject("Msxml2.XMLHTTP");

    } catch (e1) {

      // Failed to create required ActiveXObject

      try {
        // Try version supported by older versions
        // of Internet Explorer

        xmlreq = new ActiveXObject("Microsoft.XMLHTTP");

      } catch (e2) {

        // Unable to create an XMLHttpRequest with ActiveX
      }
    }
  }

  return xmlreq;
}
  
			

나중에 XMLHttpRequest를 지원하지 않는 브라우저를 다루는 방법을 설명하겠다. 지금은 Listing 2의 새로운 newXMLHttpRequest가 언제나 XMLHttpRequest를 리턴하는 것으로 간주한다.

쇼핑 카트 시나리오 예제로 돌아가서, 사용자가 해당 카탈로그 아이템에서 addToCart() 버튼을 누를 때 마다 Ajax 인터랙션을 호출하도록 할 것이다. addToCart()라는 onclick 핸들러 함수는 Ajax 호출 시 카트의 상태를 업데이트 한다.(Listing 1). Listing 3에서 보듯 addToCart()가 해야 하는 첫 번째 일은 Listing 2에서 newXMLHttpRequest()함수를 호출하여 XMLHttpRequest의 인스턴스를 획득하는 것이다. 다음에는 콜백 함수를 등록하여 서버의 응답을 받는 것이다. (Listing 6).

요청으로 인해 서버상의 상태가 변경되기 때문에 HTTP POST가 이 일을 수행하도록 할 것이다. POST 를 통해 데이터를 보내려면 세 단계를 거쳐야 한다. 우선, 통신하고 있는 서버 리소스에 대한 POST에 연결한다. 이 경우 URL cart.do에 매핑된 서블릿이다. 그런 다음 XMLHttpRequest에 헤더를 설정한다. 이때 요청 내용은 폼 인코딩(form-encoded) 데이터라는 것을 언급한다. 마지막으로 폼 인코딩 데이터를 가진 요청을 바디(body)로서 보낸다.

Listing 3은 그 세 단계를 나타낸다.


Listing 3. Add to Cart XMLHttpRequest 실행
				
/*
 * Adds an item, identified by its product code, 
 * to the shopping cart
 * itemCode - product code of the item to add.
 */
function addToCart(itemCode) {

  // Obtain an XMLHttpRequest instance
  var req = newXMLHttpRequest();

  // Set the handler function to receive callback notifications
  // from the request object
  var handlerFunction = getReadyStateHandler(req, updateCart);
  req.onreadystatechange = handlerFunction;
  
  // Open an HTTP POST connection to the shopping cart servlet.
  // Third parameter specifies request is asynchronous.
  req.open("POST", "cart.do", true);

  // Specify that the body of the request contains form data
  req.setRequestHeader("Content-Type", 
                       "application/x-www-form-urlencoded");

  // Send form encoded data stating that I want to add the 
  // specified item to the cart.
  req.send("action=add&item="+itemCode);
}

			

지금까지 Ajax 설정의 첫 번째 부분을 보았다. 주로 HTTP 요청을 클라이언트에서 생성하여 실행하는 것이다. 다음에는 이 요청을 핸들할 때 사용하는 자바 서블릿 코드이다.




위로


서블릿 요청 핸들링

서블릿으로 XMLHttpRequest를 핸들하는 것은 브라우저에서 일반적인 HTTP 요청을 핸들하는 것과 비슷하다. 포스트 바디(body)에 보내진 폼 인코딩 데이터는 HttpServletRequest.getParameter() 호출로 얻을 수 있다. Ajax 요청은 애플리케이션의 일반 웹 요청과 같은 HttpSession에서 발생한다. 이것은 쇼핑 카트 시나리오 예제에 유용하다. 왜냐하면 사용자의 쇼핑 카트 상태를 JavaBean에 캡슐화 하고, 그 상태를 요청들 간 세션에 지속시키기 때문이다.

Listing 4는 Ajax 요청을 핸들하여 쇼핑 카트를 업데이트 하는 간단한 서블릿의 일부이다. Cart빈은 사용자 세션에서 검색되고, 상태는 요청 매개변수에 따라 업데이트 된다. Cart는 XML로 직렬화 되고, 그 XML은 ServletResponse에 작성된다. 응답 내용 유형을 application/xml로 설정하는 것이 중요하다. 그렇지 않으면 XMLHttpRequest는 응답 내용을 XML DOM으로 파싱할 것이다.


Listing 4. Ajax 요청을 핸들하는 서블릿 코드
				
public void doPost(HttpServletRequest req, 
  HttpServletResponse res)
      throws java.io.IOException {

  Cart cart = getCartFromSession(req);

  String action = req.getParameter("action");
  String item = req.getParameter("item");
  
  if ((action != null)&&(item != null)) {

    // Add or remove items from the Cart
    if ("add".equals(action)) {
      cart.addItem(item);

    } else if ("remove".equals(action)) {
      cart.removeItems(item);

    }
  }

  // Serialize the Cart's state to XML
  String cartXml = cart.toXml();

  // Write XML to response.
  res.setContentType("application/xml");
  res.getWriter().write(cartXml);
}

			

Listing 5는 Cart.toXml() 메소드에서 만들어진 XML 예제이다. 매우 단순하다. cart 엘리먼트의 generated 애트리뷰트에 주목하라. System.currentTimeMillis()에서 만들어진 타임스템프이다.


Listing 5. Cart 객체의 XML 직렬화 예제
				
<?xml version="1.0"?>
<cart generated="1123969988414" total="$171.95">
  <item code="hat001">
    <name>Hat</name>
    <quantity>2</quantity>
  </item>
  <item code="cha001">
    <name>Chair</name>
    <quantity>1</quantity>
  </item>
  <item code="dog001">
    <name>Dog</name>
    <quantity>1</quantity>
  </item>
</cart>

			

다운로드 섹션의, 애플리케이션 소스 코드에서 Cart.java를 보았다면, 스트링(strings)들을 함께 붙여서 XML이 만들어 졌다는 것을 알 수 있을 것이다. 이 예제에서는 가능하지만, 자바 코드에서 XML을 만드는 방법 중 최악의 방법이다. 다음 글에서는 더 나은 방법을 소개하겠다.

이제 CartServletXMLHttpRequest에 어떻게 응답하는지 알았다. 이제 클라이언트측으로 리턴할 차례 이다. 여기에서 XML 응답이 페이지 상태의 업데이트에 어떻게 사용되는지를 보게 된다.




위로


JavaScript를 이용한 응답 핸들링

XMLHttpRequestreadyState 속성은 요청의 주기 상태를 부여하는 숫자 값이다. "초기화되지 않은 것"에는 0 부터 "완료된 것"에는 4 까지 붙을 수 있다. readyState가 변경될 때 마다 readystatechange 이벤트가 실행되고, onreadystatechange 속성을 통해 붙은 핸들러 함수가 호출된다.

Listing 3에서, getReadyStateHandler()함수가 호출되어 핸들러 함수를 만드는 방법을 보았다. 이 핸들러 함수는 onreadystatechange속성으로 할당된다. getReadyStateHandler()는 함수들이 JavaScript의 첫 번째 객체라는 것을 활용한다. 다시 말해서 함수들은 다른 함수들에 대한 매개변수가 될 수 있고, 다른 함수들을 생성 및 리턴할 수 있다. XMLHttpRequest가 완료되었는지 여부를 검사하여 그 핸들러 함수에 대한 XML 응답으로 전달하는 함수를 리턴하는 것은 getReadyStateHandler()의 역할이다. Listing 6은 getReadyStateHandler()의 코드이다.


Listing 6. getReadyStateHandler() 함수
/*
 * Returns a function that waits for the specified XMLHttpRequest
 * to complete, then passes its XML response
 * to the given handler function.
 * req - The XMLHttpRequest whose state is changing
 * responseXmlHandler - Function to pass the XML response to
 */
function getReadyStateHandler(req, responseXmlHandler) {

  // Return an anonymous function that listens to the 
  // XMLHttpRequest instance
  return function () {

    // If the request's status is "complete"
    if (req.readyState == 4) {
      
      // Check that a successful server response was received
      if (req.status == 200) {

        // Pass the XML payload of the response to the 
        // handler function
        responseXmlHandler(req.responseXML);

      } else {

        // An HTTP problem has occurred
        alert("HTTP error: "+req.status);
      }
    }
  }
}

HTTP 상태 코드

Listing 6에서, XMLHttpRequest의 상태 속성(status property)은 요청이 성공적으로 완료되었는지를 보기 위해 테스트된다. status에는 서버 응답에 대한 HTTP 상태 코드가 포함되어 있다. GETPOST 요청을 수행할 때 200(OK)이 아닌 어떤 코드도 에러이다. 서버가 리다이렉트 응답(예를 들어, 301 또는 302)을 보내면 브라우저는 리다이렉트를 따라 새로운 위치에서 리소스를 가져온다. XMLHttpRequest는 리다이렉트 상태 코드를 볼 수 없다. 또한 브라우저(Cache-Control: no-cache)는 헤더를 모든 XMLHttpRequest에 추가하여 클라이언트 코드가 (변경되지 않은) 304 서버 응답을 다루지 않도록 한다.

getReadyStateHandler()에 대하여

특히 JavaScript 읽기에 익숙하지 않다면 getReadyStateHandler()는 비교적 복잡한 코드이다. 대신 여러분의 JavaScript 라이브러리에 이 하수를 추가하여 XMLHttpRequest의 내부를 다루지 않고도 Ajax 서버 응답들을 핸들할 수 있다. 중요한 것은 자신의 코드에서 getReadyStateHandler()를 사용하는 방법을 이해하는 것이다.

Listing 3에서 getReadyStateHandler() 는 다음과 같이 호출된다. handlerFunction = getReadyStateHandler(req,updateCart). 이 경우 getReadyStateHandler()에 의해 리턴된 함수는 변수 req에 있는 XMLHttpRequest가 완료되었는지를 확인한 다음 응답 XML이 있는 updateCart 함수를 호출한다.

카트 데이터 추출하기

Listing 7은 updateCart()의 코드이다. 이 함수는 DOM 호출들을 사용하여 쇼핑 카트 XML 문서를 조사하여 새로운 카트 내용을 반영하여 웹 페이지를 업데이트 한다. (Listing 1) XML DOM에서 데이터를 추출하는데 사용된 호출에 집중해보자. cart 엘리먼트에 대한, generated 애트리뷰트는 Cart가 XML로 직렬화 되었을 때 만들어진 타임스템프이다. 이것은 새로운 카트 데이터가 옛 카트 데이터에 반영되지 않았는지를 확인한다. Ajax 요청은 근본적으로 비동기식이라서 시퀀스에서 도착하는 서버 응답에 대한 세이프가드를 체크한다.


Listing 7. 카트 XML 문서를 반영하여 페이지 업데이트하기
				
function updateCart(cartXML) {

 // Get the root "cart" element from the document
 var cart = cartXML.getElementsByTagName("cart")[0];

 // Check that a more recent cart document hasn't been processed
 // already
 var generated = cart.getAttribute("generated");
 if (generated > lastCartUpdate) {
   lastCartUpdate = generated;

   // Clear the HTML list used to display the cart contents
   var contents = document.getElementById("cart-contents");
   contents.innerHTML = "";

   // Loop over the items in the cart
   var items = cart.getElementsByTagName("item");
   for (var I = 0 ; I < items.length ; I++) {

     var item = items[I];

     // Extract the text nodes from the name and quantity elements
     var name = item.getElementsByTagName("name")[0]
                                               .firstChild.nodeValue;
                                               
     var quantity = item.getElementsByTagName("quantity")[0]
                                               .firstChild.nodeValue;

     // Create and add a list item HTML element for this cart item
     var li = document.createElement("li");
     li.appendChild(document.createTextNode(name+" x "+quantity));
     contents.appendChild(li);
   }
 }

 // Update the cart's total using the value from the cart document
 document.getElementById("total").innerHTML = 
                                          cart.getAttribute("total");
}

			

이것으로 Ajax 실행이 완료되었다. 물론 여러분은 웹 애플리케이션이 실행되는 것을 보고 싶을 것이다.(다운로드 참조). 이 예제는 매우 간단하고 환경 범위도 다양하다. 예를 들어 서버측 코드를 추가하여 카트에서 아이템을 제거했지만, UI에서 접근하는 방법은 없다. 애플리케이션의 기존 JavaScript 코드에 이러한 기능을 구현하기 바란다.




위로


Ajax를 사용할 때의 문제점

다른 기술과 마찬가지로 Ajax도 허점이 많이 있다 . 최근에는 쉬운 솔루션이 없다는 것이 문제되고 있지만 Ajax가 성숙해 가면서 개선될 전망이다. 개발자 커뮤니티가 Ajax 애플리케이션 개발에 경험을 많이 쌓고 최선의 방법과 가이드라인이 문서화 될 것이다.

XMLHttpRequest의 기능

Ajax 개발자들이 직면한 가장 큰 문제들 중 하나는 XMLHttpRequest를 사용할 수 없을 경우이다. 대다수의 브라우저들은 XMLHttpRequest를 지원하지만, 어떤 사람들은 그러한 브라우저를 사용하지 않거나 브라우저 보안 설정 때문에 XMLHttpRequest를 사용할 수 없는 경우도 있다. 기업 인트라넷에 전개할 웹 애플리케이션을 개발한다면 어떤 브라우저가 지원되는지를 지정하고, XMLHttpRequest를 언제나 사용할 수 있도록 해야 한다. 하지만 공용 웹상에 전개한다면 XMLHttpRequest를 사용할 수 있겠지만 오래된 브라우저, 장애인용 브라우저, 핸드헬드용 경량 브라우저에는 사용할 수 없다.

따라서 애플리케이션을 "부드럽게 강등시켜서" XMLHttpRequest지원이 안되는 브라우저에서 작동하도록 해야 한다. 이 쇼핑 카트 예제에서 애플리케이션을 강등시키는 최선의 방법은 Add to Cart 버튼이 폼 제출을 수행하도록 하는 것이다. 페이지를 리프레시하여 카트의 업데이트를 반영한다. 페이지가 로딩되면 Ajax 작동이 JavaScript를 통해 페이지에 추가될 수 있다. JavaScript 핸들러 함수를 각 Add to Cart 버튼에 붙인다. 이는 어디까지나 XMLHttpRequest가 가능할 때이다. 또 다른 방법은 사용자가 로그인할 때 XMLHttpRequest를 탐지하는 것이다. 그리고 나서 Ajax 버전의 애플리케이션을 실행하든지 아니면 폼 기반 버전을 실행한다.

가용성 문제

Ajax 애플리케이션에 관련한 몇 가지 가용성 문제들이 있다. 예를 들어, 인풋이 등록되었다는 것을 사용자에게 알리는 것이 중요할 수 있다. 왜냐하면 보통의 피드백 방식인 모래시계 커서와 돌아가는 브라우저 "쓰로버(throbber)"가 XMLHttpRequest에 적용되지 않기 때문이다. 한 가지 방법은 Submit 버튼을 "Now updating..."메시지로 대체시키는 것이다. 이렇게 해서 사용자들이 응답을 기다리는 동안 버튼을 반복적으로 클릭하지 않도록 한다.

또 다른 문제는 사용자가 그들이 보고 있는 페이지의 일부가 업데이트 되었다는 것을 인식하지 못하는 것이다. 다양한 시각 기술을 사용하여 업데이트된 페이지 부분을 그려서 이 문제를 해결할 수 있다. 페이지 업데이트와 관련한 또 다른 문제로는 브라우저의 뒤로가기(back) 버튼이 "비활성화(breaking)"되는 것, 주소 바의 URL이 페이지의 전체 상태를 반영하지 않는 문제, 북마킹이 안되는 경우 등이 있다. (참고자료)

서버 부하

폼 기반의 Ajax UI를 구현하면 서버에서 요청 수가 상당히 많이 늘어난다. 예를 들어, 사용자가 검색 폼을 제출할 때 Google 웹 검색이 서버에 한번의 히트를 일으킨다고 해보자. 하지만 사용자의 검색어를 자동 완성하는 Google Suggest는 서버에 여러 요청들을 보낸다. Ajax 애플리케이션을 개발할 때 서버에 얼마나 많은 요청들을 보낼 것인지, 그리고 서버 부하가 어떻게 될 것인지를 알아야 한다. 또한 클라이언트에 요청을 버퍼링하고 클라이언트에 서버 응답을 캐싱하여 서버 부하를 완화시킬 수 있다. 또한 Ajax 웹 애플리케이션을 설계하여 가능한 많은 로직들이 굳이 서버에 연결되지 않고도 클라이언트에서 수행될 수 있도록 한다.

비동기식 다루기

XMLHttpRequest가 발송되는 순서대로 완료될 것이라는 보장이 없다. 실제로 그것까지 염두해 가며 애플리케이션을 설계하지 않는다. 쇼핑 카트 예제에서 마지막으로 업데이트 된 타임스탬프는 새로운 카트 데이터가 오래된 데이터에 적용되었는지를 확인할 떄 사용되었다. (Listing 7). 이것은 쇼핑 카트 시나리오에서 매우 기본적인 방식이지만 다른 경우에는 그렇지 않다. 설계할 때 비동기식 서버 응답을 어떻게 처리할 것인지를 생각하라.




위로


결론

Ajax의 기본 원리와 Ajax 인터랙션에 참여하는 클라이언트와 서버측 컴포넌트에 대해서 설명했다. 이들 모두 자바 기반 Ajax 웹 애플리케이션을 만드는 요소들이다. 또한 Ajax 접근방식에 따르는 고급 디자인 문제들도 이해해야 한다. 성공적인 Ajax 애플리케이션을 만들기 위해서는 UI 디자인부터 자바 스크립트 디자인, 그리고 서버측 아키텍쳐에 이르기까지 유기적인 접근 방식이 필요하다. 하지만 이제 여러분도 Ajax에 대한 기본적인 지식이 있으니 문제없다.

이 글에서 설명한 기술들을 사용하여 대형 Ajax 애플리케이션을 작성할 때의 복잡함에 압도될지 모르겠다. 하지만 좋은 소식이 있다. Struts, Spring, Hibernate 같은 프레임웍이 저급의 Servlet API와 JDBC에서 탈피하여 추상 웹 애플리케이션으로 진화했기 때문에 툴킷들을 사용하면 Ajax 개발이 쉬워진다. 이들 중 몇몇은 클라이언트 측에만 해당된다. 페이지에 시각 효과를 쉽게 추가할 수 있고, XMLHttpRequest의 사용법도 간단해진다. 서버측 코드에서 Ajax 인터페이스를 자동으로 생성하는 방법도 제공한다. Ajax 개발 방식이 고급화 되었다. .

Ajax 커뮤니티는 빠르게 변화하고 있고 정보도 풍부하다. 이 글을 다 읽었다면 참고자료 섹션을 참조하기 바란다. Ajax 또는 클라이언트측 개발이 처음인 사람들에게 권한다. 또한 시간을 내어 샘플 코드도 공부하기 바란다.

다음에는 XMLHttpRequest API를 보다 자세히 설명하고 JavaBean에서 XML을 쉽게 생성하는 방법도 설명하겠다. JSON (JavaScript Object Notation)도 소개할 테니 기대하라.

기사의 원문보기





위로


다운로드 하십시오

설명 이름 크기 다운로드 방식
Sample code j-ajax1.zip 8 KB  FTP
다운로드 방식에 대한 정보 Get Adobe® Reader®


참고자료

교육

제품 및 기술 얻기

토론


필자소개

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

:
Posted by 뽀기

Ajax 클라이언트/서버 통신이 복잡한 문제가 될 수 있다.

developerWorks

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



난이도 : 중급

Brett McLaughlin, Author and Editor, O'Reilly Media Inc.

2007 년 1 월 02 일

지난 시리즈에서는, Ajax 애플리케이션인 서버로 가는 요청을 XML로 포맷팅 하는 방법을 설명했습니다. 그리고 대부분의 경우, 이것이 좋은 방법이 아닌지를 설명했습니다. 이번에는, 좋은 방법을 소개합니다. XML 응답을 클라이언트로 리턴하는 방법을 설명합니다.

시리즈 소개


여러분이 해서는 안될 일에 대한 글을 쓰는 것을 별로 좋아하지 않는다. 그것은 매우 바보 같은 일이다. 지금까지 어떤 것을 설명하는데 할애한 시간만큼, 나머지는 그 동안 배웠던 그 기술을 사용하는 것이 왜 좋은 생각이 아닌지를 설명하는데도 시간을 할애하곤 한다. 바로 지난달 지난 달 기술자료(참고자료)가 그 경우이다. Ajax 애플리케이션 요청에 데이터 포맷으로서 XML을 사용하는 방법을 설명했다.

모쪼록 이 글이, XML 요청에 대해 배우느라 여러분이 보냈던 시간을 보상해 주기를 바란다. Ajax 애플리케이션에서, 송신 데이터 포맷으로서 XML을 사용하는 경우는 드물지만, 서버에서 XML을 클라이언트보내야 하는 이유는 충분하다. 따라서, 지난 글에서 여러분이 XML에 대해 배웠던 모든 것이 이번 글에서 비로서 가치를 지니기 시작했다.

(가끔씩) 서버는 많은 말을 하지 못한다.

서버에서 XML 응답을 받는 것에 대한 기술적인 상세를 보기 전에, 서버가 요청에 대한 응답으로 XML을 보내는 것이 좋은 것인지를 이해해야 한다. (그리고 클라이언트가 요청을 XML로 보내는 것과는 어떻게 다른지도 이해해야 한다.)

클라이언트는 이름/값 쌍으로 말한다.

클라이언트는 대부분의 경우에 XML을 사용할 필요가 없다. 이름값 쌍을 사용하여 요청을 보낼 수 있기 때문이다. 따라서, 다음과 같이 이름을 보내게 된다: name=jennifer. 또한 이어지는 이름값 쌍들은 앰퍼샌드(&)를 사용하여 추가할 수 있다: name=jennifer&job=president. 간단한 텍스트와 이러한 이름값 쌍을 사용하면, 클라이언트는 여러 값을 가진 요청을 서버로 쉽게 보낼 수 있다. XML이 제공하는 추가 구조(오버헤드)가 필요 없다.

사실, XML을 서버로 보내야 하는 대부분의 이유들이 다음 두 개의 범주로 나뉠 수 있다.

  • 서버는 XML 요청을 수락하기만 한다. 이 경우, 선택권이 없다. 지난 달 기술자료에서, 이러한 유형의 요청들을 보낼 때 필요한 모든 툴을 제공했다.
  • XML 요청이나 SOAP 요청만 수락하는 원격 API를 호출할 경우. 이것은 매우 특별한 케이스이지만, 짚고 넘어가야 한다. 비동기식 요청에서 Google이나 Amazon에서 API를 사용할 경우, 특별히 고려해야 할 것이 있다. 다음 달 기술자료에서 API로 요청하는 문제를 예제를 통해 설명하겠다.

서버는 (표준 방식으로) 이름값 쌍을 보낼 수 없다.

이름값 쌍을 보낼 때, 요청을 보내는 웹 브라우저와 요청에 응답하고 서버 프로그램을 호스팅 하는 플랫폼은 이런 이름값 쌍들을 서버 프로그램이 쉽게 작업할 수 있는 데이터로 변환한다. 실제로, 모든 서버 측 기술 -- Java™ servlets, PHP, Perl, Ruby on Rails- 덕택에 다양한 메소드를 호출하여 이름에 기반한 값들을 얻을 수 있다. 따라서 name 애트리뷰트를 얻는 것은 간단하다.

서버가 name=jennifer&job=president 스트링으로 애플리케이션에 응답한다면, 클라이언트는 두 개의 이름값 쌍을 나누고, 각 쌍을 이름과 값으로 나눌 표준화 된 쉬운 방법이 없다. 리턴된 데이터를 직접 파싱해야 할 것이다. 서버가 이름값 쌍으로 구성된 응답을 리턴하면, 응답은 세미콜론, 파이프 심볼, 기타 비표준 포맷팅 문자로 구분된 엘리먼트를 가진 응답만큼이나 해석하기 어렵다.

싱글 스페이스(single space)!

대부분의 HTTP 요청에서, %20은 싱글 스페이스를 나타내는데 사용된다. 따라서 "Live Together, Die Alone"이라는 텍스트는 Live%20Together,%20Die%20Alone 형태로 HTTP로 보내진다.

응답에 플레인 텍스트를 사용하고, 적어도 응답에 여러 값이 포함되어 있을 때, 클라이언트가 그 응답을 받아서 표준 방식으로 인터프리팅 해야 하는 쉽지 않은 방식이다. 서버가 넘버 42를 보냈다면, 플레인 텍스트로도 충분하다. 하지만, TV 쇼 Lost, Alias,, Six Degrees,의 최근 시청률을 보냈다면? 여러분은 플레인 텍스트를 사용하여 이 응답을 보낼 많은 방법들을 선택할 수 있지만(Listing 1), 클라이언트에 의한 작업 없이는 쉬운 인터프리팅 방법이 없고, 표준화된 방법도 없다.



Listing 1. TV 시청률에 대한 서버 응답 (다양한 버전)
				
show=Alias&ratings=6.5|show=Lost&ratings=14.2|show=Six%20Degrees&ratings=9.1

Alias=6.5&Lost=14.2&Six%20Degrees=9.1

Alias|6.5|Lost|14.2|Six%20Degrees|9.1

이러한 응답 스트링들을 나누는 방법을 알아내는 것이 어려운 일은 아니지만, 클라이언트는 세미콜론, 등가 표시, 파이프, 앰퍼샌드에 따라 스트링을 파싱하고 나누어야 한다. 이는 다른 개발자들이 쉽게 이해하고 관리할 수 있는 강력한 코딩 방법이 아니다.

XML

이름값 쌍으로 서버가 클라이언트에 응답하는 표준 방법이 없다는 것을 깨달았다면, XML을 사용해야 하는 이유가 분명해진다. 데이터를 서버로 보낼 때, 이름값 쌍은 최상의 선택이다. 서버와 서버 측 언어는 그 쌍들을 쉽게 인터프리팅 할 수 있기 때문에다. 마찬가지로, 데이터를 클라이언트로 리턴할 때 XML을 사용하면 일이 쉬워진다. 이전 기술자료들에서 XML을 파싱할 때 DOM을 사용하는 것에 대해 다루었고, 앞으로의 기술자료에서는 JSON이 XML을 파싱하는 옵션을 어떻게 제공하는지를 설명할 것이다. 무엇보다도, 여러분은 XML을 플레인 텍스트로서 취급할 수 있고, 이러한 방식으로 값을 얻어낼 수 있다. 따라서, 서버에서 XML 응답을 얻을 수 있는 여러 가지 방식이 있고, 매우 표준적인 방식으로 클라이언트에 있는 데이터를 가져다가 사용할 수 있다.

게다가 XML은 이해하기도 쉽다. 프로그래밍을 하는 사람이라면 Listing 2의 데이터를 이해할 수 있을 것이다.



Listing 2. TV 시청률에 대한 서버 응답 (XML)
				

<ratings>
 <show>
  <title>Alias</title>
  <rating>6.5</rating>
 </show>
 <show>
  <title>Lost</title>
  <rating>14.2</rating>
 </show>
 <show>
  <title>Six Degrees</title>
  <rating>9.1</rating>
 </show>
</ratings>

Listing 2의 코드는 특정 세미콜론이나 어포스트로피 때문에 생기는 혼란이 없다.




위로


서버에서 XML 받기

이 시리즈의 초점은 Ajax의 클라이언트 측에 맞춰져 있기 때문에, 서버 측 프로그램이 XML로 응답을 생성하는 방법에 대해서는 자세히 들어가지 않겠다. 하지만, 클라이언트가 XML을 받을 때 몇 가지 특별한 고려 사항이 있다.

서버에서 오는 XML 응답을 두 가지 기본 방식으로 취급할 수 있다.

  • XML로 포맷팅 되는 플레인 텍스트
  • DOM Document 객체로 나타나는, XML 문서

서버에서 오는 간단한 응답 XML을 보자. Listing 3은 위와 똑 같은 TV 리스팅을 보여주고 있다. (Listing 2와 같은 XML이지만, 여러분의 편의를 돕고자 다시 기재한다.)



Listing 3. XML-포맷 TV 시청률 예제
				

<ratings>
 <show>
  <title>Alias</title>
  <rating>6.5</rating>
 </show>
 <show>
  <title>Lost</title>
  <rating>14.2</rating>
 </show>
 <show>
  <title>Six Degrees</title>
  <rating>9.1</rating>
 </show>
</ratings>

XML을 플레인 텍스트로서 처리하기

새로운 프로그래밍 기술을 배우는 관점에서 볼 때, XML을 다루는 가장 쉬운 옵션은 서버로부터 리턴된 텍스트 조각처럼 다루는 것이다. 다시 말해서, 데이터 포맷은 기본적으로 무시하고, 서버에서 응답만 취하면 된다.

이러한 상황에서, 마치, 서버가 비 XML 응답을 보낼 때처럼, 요청 객체의 responseText 속성을 사용한다. (Listing 4).



Listing 4. XML을 정상적인 서버 응답으로서 처리하기
				
function updatePage() {
  if (request.readyState == 4) {
    if (request.status == 200) {
      var response = request.responseText;

      // response has the XML response from the server
      alert(response);
    }
  }
}

이전 기술자료 리뷰

코드 중복을 피하기 위해, 후반 기술자료에서는 논의되는 주제와 관련된 코드 부분만 제시하겠다. 따라서, Listing 4에서는 Ajax 클라이언트 코드의 콜백 메소드만 보인다. 비동기식 애플리케이션에 이 부분이 어떻게 적용되는지 잘 모르겠다면, 본 시리즈의 기술자료들을 복습하기 바란다. 참고자료 섹션에 이전 기술자료 링크가 제공된다.

이 코드에서, updatePage()는 콜백이고, requestXMLHttpRequest 객체이다. 모든 것이 response 변수 안에 이어진 XML 응답이 될 것이다. 이 변수를 프린트하면, Listing 5와 같이 된다. (Listing 5의 코드는 하나의 연속적인 라인이다. 여기에서는 디스플레이를 위해서 여러 줄로 표현했다.)



Listing 5. 응답 변수의 값
				
<ratings><show><title>Alias</title><rating>6.5</rating>
</show><show><title>Lost</title><rating>14.2</rating></show><show>
<title>Six Degrees</title><rating>9.1</rating></show></ratings>

가장 중요한 것은 XML이 모두 함께 실행된다는 것이다. 대부분의 경우, 서버는 스페이스와 캐리지 리턴으로 XML을 포맷팅 하지 않는다. 단지 하나로 연결할 뿐이다. (Listing 5) 물론, 애플리케이션은 스페이싱(spacing)에 대해서 신경 쓰지 않는다. 다만 읽기가 힘들어질 뿐이다.

이때, JavaScript split 함수를 사용하여 데이터를 나누고, 기본 스트링 조작을 통해서 엘리먼트 이름과 값을 얻을 수 있다. 물론, 이것은 엄청난 작업이고, 이전 시리즈에서 다루었던 DOM(Document Object Model)을 보는데 많은 시간을 보내야 한다. 따라서, 나는 여러분에게 responseText를 사용하여 서버의 XML 응답을 사용 및 출력할 것을 권하고 싶지만, 그 코드 모두를 보여줄 수는 없다. DOM을 사용할 수 있다면, 이러한 방식을 사용하여 XML 데이터를 얻어서는 안된다.

XML을 XML로 취급하기

서버의 XML 포맷 응답을 다른 텍스트 응답처럼 취급할 수 있지만, 이것은 좋은 방법이 아니다. 우선, 이 시리즈를 잘 읽어보았다면, XML을 조작할 수 있는, JavaScript 친화적인 API인 DOM을 사용하는 방법을 익혔을 것이다. JavaScript와 XMLHttpRequest 객체는 서버의 XML 응답을 완벽하게 얻고 이것을 DOM Document 객체 폼으로 얻을 수 있는 속성을 제공한다.

Listing 6을 보자. 이 코드는 Listing 4와 비슷하지만, responseText 속성을 사용하기 보다는, 콜백이 responseXML 속성을 사용한다. XMLHttpRequest에 사용할 수 있는 이 속성은 DOM 문서의 형태로 서버 응답을 리턴한다.



Listing 6. XML을 XML로 취급하기
				
function updatePage() {
  if (request.readyState == 4) {
    if (request.status == 200) {
      var xmlDoc = request.responseXML;

      // work with xmlDoc using the DOM
    }
  }
}

DOM Document가 생겼으니, 다른 XML처럼 작업할 수 있다. 예를 들어, Listing 7에서처럼, 모든 show 엘리먼트를 얻을 수 있다.



Listing 7. 모든 show 엘리먼트 얻기
				
function updatePage() {
  if (request.readyState == 4) {
    if (request.status == 200) {
      var xmlDoc = request.responseXML;

      var showElements = xmlDoc.getElementsByTagName("show");
    }
  }
}

여러분이 DOM에 익숙하다면, 이것도 익숙해야 한다. 이미 배웠던 모든 DOM 메소드를 사용할 수 있고, 서버에서 받은 XML을 쉽게 조작할 수 있다.

물론, 정상적인 JavaScript 코드로 혼합할 수도 있다. 예를 들어, 모든 show 엘리먼트들을 통해 반복할 수 있다. (Listing 8)



Listing 8. 모든 show 엘리먼트들을 통해 반복하기
				
function updatePage() {
  if (request.readyState == 4) {
    if (request.status == 200) {
      var xmlDoc = request.responseXML;

      var showElements = xmlDoc.getElementsByTagName("show");
      for (var x=0; x<showElements.length; x++) {
        // We know that the first child of show is title, and the second is rating
        var title = showElements[x].childNodes[0].value;
        var rating = showElements[x].childNodes[1].value;

        // Now do whatever you want with the show title and ratings
      }
    }
  }
}

비교적 간단한 코드를 사용하여, XML 응답을 XML로 취급할 수 있고, 약간의 DOM과 단순한 JavaScript를 사용하여 서버 응답을 다룰 수 있다. 더 중요한 것은, 콤마로 분리된 값이나 파이프로 구분된 이름값 쌍 대신, 표준화된 포맷인 XML로 작업했다는 점이다. 다시 말해서, 서버로 요청을 보내는 것 같은, 합법적인 곳에서 XML을 사용할 수 있었다는 점이다.

서버의 XML: 간단한 예제

서버에서 XML을 생성하는 방법에 대해 많이 설명하지는 않았지만, 많은 설명 필요 없이, 간단한 예제를 보면서 이러한 상황을 다루는 방법에 대해 알아보자. Listing 9는 요청에 대한 응답으로 XML을 출력하는 PHP 스크립트이다. 비동기식 클라이언트에서 온 것이다.

이것은 과격한 접근 방식이다. PHP 스크립트는 실제로 XML 아웃풋을 직접 작성하고 있다. PHP와 XML 응답을 만들 수 있는 서버 측 언어를 위한 다양한 툴킷과 API들이 있다. XML로 응답을 생성 및 보내는 서버 측 스크립트가 어떤 모습인지를 파악할 수 있다.



Listing 8. XML을 리턴하는 PHP 스크립트
				
<?php

// Connect to a MySQL database
$conn = @mysql_connect("mysql.myhost.com", "username", "secret-password");
if (!conn)
  die("Error connecting to database: " . mysql_error());

if (!mysql_select_db("television", $conn))
  die("Error selecting TV database: " . mysql_error());

// Get ratings for all TV shows in database
$select = 'SELECT title, rating';
$from   = '  FROM ratings';
$queryResult = @mysql_query($select . $from);
if (!$queryResult)
  die("Error retrieving ratings for TV shows.');

// Let the client know we're sending back XML
header("Content-Type: text/xml");
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
echo "<ratings>";

while ($row = mysql_fetch_array($queryResult)) {
  $title = $row['title'];
  $rating = $row['rating'];

  echo "<show>
  echo "<title>" . $title . "</title>";
  echo "<rating>" . $rating . "</rating>";
  echo "</show>";
}

echo "</ratings>";

mysql_close($conn);

?>

각자가 선호하는 서버 측 언어를 사용하여, XML을 만들 수 있다. IBM developerWorks의 많은 기술자료들에서 서버 측 언어를 사용하여 XML 문서를 만드는 방법을 설명하고 있다. (참고자료)




위로


XML을 인터프리팅 하는 기타 옵션

XML을 다루는 한 가지 매우 일반적인 옵션들은, 포맷팅 되지 않은 텍스트로서 취급하는 것 또는 DOM을 사용하는 것 이상으로 중요하다. 이것이 바로 JSON, (JavaScript Object Notation)이고, JavaScript에 포함된 무료 텍스트 포맷이다. 향후 시리즈에서는 JSON을 다루도록 하겠다.

일반적으로, JSON을 사용하여 할 수 있는 모든 것은, DOM을 사용해서도 할 수 있다. 그 반대의 경우도 마찬가지다. 이것은 선호도의 문제이고, 특정 애플리케이션에 알맞은 방식을 선택하는 문제이다. 지금까지는 DOM을 설명했고, 서버의 응답을 받는 상황에서 이를 사용하는 방법을 익혔다. 앞으로 남은 두 건의 기술자료에서는 JSON에 대해 설명할 것이다. 두 가지 중에서 한 가지를 선택할 준비를 해야 할 것이다. 다음 기술자료를 기다려주기 바란다.




위로


맺음말

지난 번 기술자료부터, 지금까지 거의 논스톱으로 XML에 대해 설명했다. 하지만 Ajax에 기여하는 XML의 빙산의 일각을 다루었을 뿐이다. 다음 기술자료에서는, XML을 보내는 특수한 상황에 대해 자세히 설명하겠다. (XML을 받는 상황에 대해서도 설명하겠다.) 특히, 사용 웹 서비스와 Google 같은 API 등 Ajax 인터랙션 관점으로, 웹 서비스에 대해서도 설명하겠다.

가장 중요한 것은 XML이 자신의 애플리케이션에 잘 맞는지를 생각하는 것이다. 많은 경우, 애플리케이션이 잘 작동한다면, XML은 그냥 유행어에 지나지 않고, XML을 사용해야 할 것 같은 유혹과 싸워야 한다.

서버가 보내는 데이터가 제한되어 있거나, 이상한 콤마 또는 파이프로 구분된 포맷으로 되어있다면, XML이 확실히 도움이 될 것이다. 서버 측 컴포넌트로 작업하거나 이를 변경하는 것을 고려하여, XML을 사용하여, XML만큼 강력하지 않은 상용 포맷을 사용하는 것 보다는, 표준 방식으로 응답을 리턴할 수 있다.

Ajax와 관련한 기술을 배우면 배울수록, 여러분의 결정에 신중을 더욱 기해야 한다. Web 2.0 애플리케이션을 작성하는 것은 재미있는 일이지만, 친구의 관심을 끌 요량으로 웹 페이지를 실행할 때 기술을 포기하지 않도록 조심해야 한다. 여러분이 좋은 애플리케이션을 작성할 것임을 믿고 있다. 다 만들고 나서 다음 글을 기대해주기 바란다.

기사의 원문보기



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Photo of Brett McLaughlin

Nextel Communications에서는 복잡한 엔터프라이즈 시스템을 구현했고, Lutris Technologies에서는 애플리케이션 서버를 개발했다. O'Reilly Media에서는 기고 및 편집 활동을 하고 있다. 베스트 셀러 작가인 Eric과 Beth Freeman과 공동 집필한 Head Rush Ajax는 Ajax에 혁신적인 Head First 방식을 취하고 있다. 그의 신간 Java 1.5 Tiger: A Developer's Notebook은 최신 자바 버전을 다룬 첫 번째 책이며, 그의 고전 Java and XML은 자바 언어에서 XML을 사용하는 방법에 대한 바이블로 통하고 있다.

:
Posted by 뽀기

사용할 때와 사용하지 않아야 할 때를 분간하기

developerWorks

 

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



난이도 : 중급

Brett McLaughlin, Author and Editor, O'Reilly Media Inc.

2006 년 12 월 19 일

평범한 Ajax 개발자들도 Ajax 단어에 있는 xXML.을 의미한다는 것 정도는 알고 있습니다. XML은 가장 인기 있는 데이터 포맷들 중 하나이고, 실제로, 비동기식 애플리케이션에서 서버 응답에 효력을 발휘합니다. 이 글에서, 서버가 XML로 된 응답을 보내는 방법을 설명합니다.

시리즈 소개


XML을 통하지 않고서는 중요한 프로그래밍을 수행할 수 없다. XHTML로 옮기려고 하는 웹 페이지 디자이너, JavaScript로 작업하는 웹 프로그래머, 전개 디스크립터와 데이터 바인딩을 사용하는 서버 측 프로그래머, XML 기반 데이터베이스를 분석하는 백엔드 개발자..등 이 모든 사람들은 이 확장성 있는 마크업 언어를 사용한다. 이쯤 되면, XML이 Ajax를 지탱하고 있는 핵심 기술로 간주되는 것쯤은 놀랄 일도 아니다.

하지만, Ajax 애플리케이션에서 사용되는 핵심 객체(XMLHttpRequest)의 의미를 생각해 봐야 한다. 대부분의 사람들은, XMLHttpRequest 객체가 실제로 언제나 XML을 사용한다고 생각하기 때문에, XML을 Ajax의 핵심 부분으로 생각한다. 이는 사실이 아니며, 그 이유를 밝히는 것이 이 글의 목적이다. 사실, 대부분의 Ajax 애플리케이션에서, XML이 거의 나타나지 않는다.

XML은 Ajax에서 사용되고, XMLHttpRequest는 이를 허용한다. XML은 서버로 보내질 수도 있다. 이 시리즈의 이전 기술자료들에서, 플레인 텍스트와 이름/값 매개변수들을 사용하여 데이터를 보냈지만, XML 역시도 사용 가능한 포맷이다. 이 글에서는, 그 사용 방법을 설명할 것이다. 요청 포맷으로 XML을 사용하는 이유를 설명하고, 대부분의 경우 XML을 사용하지 말아야 하는 이유를 설명하겠다.

XML: 너무나 작은 의미?

Ajax 애플리케이션과 XML의 사용에 대해 추측하기란 쉽다. 기술의 이름(Ajax)과 이것이 사용하는 핵심 객체(XMLHttpRequest)를 보면 XML이 사용되고 있음을 짐작할 수 있고, Ajax 애플리케이션과 XML을 연결 지어 말하는 이야기도 듣게 된다. 그러나, 이러한 인식은 잘못된 것이고, 특히, 비동기식 애플리케이션을 작성할 경우라면, 이러한 인식이 그릇된 것인지를 알아야 한다.

XMLHttpRequest: 의미와 HTTP

기술적인 문제에 발생할 수 있는 가장 나쁜 일은 이것인 너무 굳어져서 기본적인 부분을 변경하는 것이 불가능할 경우가 있다. Ajax 애플리케이션에서 사용되는 기본 객체인 XMLHttpRequest에서도 이 같은 일이 발생한다. 이것은 HTTP 요청을 통해서 XML을 보내거나, 일종의 XML 포맷으로 HTTP 요청을 만드는 것처럼 여겨진다. 객체 이름이 어떤 뉘앙스를 풍기든 상관없이, 이것은 실제로 클라이언트 코드(대게 웹 페이지에 있는 JavaScript)가 HTTP 요청을 보낼 수 있는 방식을 제공한다. 이것이 전부이다. 더 이상 어떤 것도 없다.

따라서, XMLHttpRequest의 이름을 HttpRequest나 간단히 Request 같이, 보다 정확한 단어로 바꾸는 것도 좋을듯하다. 하지만, 수 백만 명의 개발자들이 Ajax에 뛰어들었고, 대다수의 사용자들이 Internet Explorer 7.0이나 Firefox 1.5같은 새로운 브라우저 버전으로 이동하는데 수년이 걸린다는 알고 있기에, 이름을 변경하는 것은 사실상 불가능하다.

XMLHttpRequest를 지원하지 않는 브라우저(특히 Windows)를 다룰 때, 대체 시스템으로는 Microsoft IFRAME 객체를 사용한다. XML, HTTP, 심지어 request 라는 뉘앙스는 어디에서도 풍기지 않는다. 확실히, XMLHttpRequest 객체는 XML이나 HTTP 그 자체 보다는, 페이지 리로드 없이 요청을 할 수 있는 것 이상의 의미를 풍긴다.

요청은 XML이 아니라 HTTP이다.

자주 실수하는 부분 중에, XML이 막후에서 사용된다고 생각하는 것이다. 사실, 나도 그랬다. 하지만, 이 같은 견해는 이 기술을 잘 이해하지 못함을 증명하는 것이다. 사용자가 브라우저를 열고, 서버에서 웹 페이지를 요청할 때, http://www.google.com 또는 http://www.headfirstlabs.com를 타이핑한다. http://를 타이핑 하지 않아도 브라우저는 브라우저 주소창에 그 부분을 채울 것이다. http://는 통신이 어떻게 이루어지는지를 알려주는 단서이다. 바로 HTTP, Hypertext Transfer Protocol을 통한 통신이다. 여러분이 웹 페이지에 코드를 작성하여 서버와 통신할 때, Ajax든, 평범한 형식의 POST이든, 하이퍼링크이든 간에, 무조건 HTTP이다.

HTTPS도 HTTP이다.

웹에 생소한 사람이라면 https://intranet.nextel.com 같은 URL이 낯설 것이다. https는 보안 HTTP이고, 일반 웹 요청에 사용되는 HTTP 프로토콜에서 보다 안전한 형식을 사용하는 것뿐이다. 따라서, HTTPS에 추가 보안 레이어가 추가되었더라도 HTTP와 통신할 수 있다.

브라우저와 서버간 모든 웹 통신은 HTTP를 통해 이루어진다고 할 때, XML은 XMLHttpRequest에 의해 사용되는 전송 또는 기술이라는 개념은 이치에 맞지 않는다. XML이 HTTP 요청으로 보내질 수 있지만, HTTP는 매우 정교하게 정의된 표준이다. 요청에 XML을 명확하게 사용하거나, 서버가 XML로 된 응답을 보내지 않는 한, XMLHttpRequest 객체에서 사용되는 것은 평범하고 오래된 HTTP 뿐이다. 따라서, "막후에서 XML을 사용하기 때문에 이것이 XMLHttpRequest로 호출되었다” 라고 말하는 사람이 있다면, HTTP가 무엇인지, XML이 HTTP를 통해 보내질 수 있고, XML은 전송 프로토콜이 아닌 데이터 포맷이라는 점을 친절하게 알려줘야 한다.




위로


XML 사용하기

지금까지는, XML이 Ajax에 사용되지 않는다는 것을 증명했다. 하지만 Ajax의 xXMLHttpRequestXML은 무엇을 말하는가? 웹 애플리케이션에서 XML을 사용할 수 있는 여러 옵션들이 있다. 이 섹션에서는 기본 옵션들을 자세히 검토해 보겠다.

XML용 옵션

비동기식 애플리케이션에서, 두 개의 기본적인 XML 애플리케이션이 있다:

  • 웹 페이지에서 서버로 웹 페이지 포맷으로 된 요청 보내기
  • 서버에서 온 요청을 XML 포맷으로 웹 페이지에서 받기

첫 번째 항목인, XML로 된 요청을 보낼 때는 요청을 XML로 포맷팅 해야 한다. API를 사용하거나 텍스트를 연결하여 그 결과를 서버로 보내는 것이다. 이 옵션에서, 기본적인 작업은 XML 규칙에 순응하는 방식으로, 그리고 서버가 이해할 수 있도록 요청을 만드는 것이다. 이 부분에서의 초점은 XML 포맷이다. 보낼 데이터가 있다면, 이를 XML 문법으로 래핑해야 한다. 이 글 나머지 부분에서는 Ajax 애플리케이션에서의 XML 사용에 대해 설명하겠다.

두 번째 옵션인, XML로 된 요청을 받을 때에는, 서버에서 응답을 가져다가, XML에서 데이터를 추출해야 한다. (API나 다른 방식을 사용한다.) 이 경우, 서버에서 온 데이터에 집중하여, XML에서 데이터를 가져와서 이를 사용한다. 이 부분은 다음 기술자료의 주제이다.

경고

XML 사용법을 자세히 설명하기 전에, 한가지 주의를 주고 싶다. XML은 작고, 빠르며, 공간 절약형 포맷이 아니다. 다음 여러 섹션들과 다음 기술자료에서 보겠지만, 어떤 정황에서 XML을 사용해야 하는 몇 가지 이유가 있고, XML이 플레인 텍스트 요청과 응답보다 더 나은 점도 갖고 있다. 하지만, XML은 플레인 텍스트 보다 더 많은 공간을 차지하고, 더 느리다. XML에 필요한 포든 태그와 문법을 메시지에 추가해야 하기 때문이다.

매우 빠른 애플리케이션을 작성하고 싶다면, XML은 올바른 선택이 아니다. 플레인 텍스트로 시작하고, 특정 부분에서 XML이 필요할 경우, 그때 XML을 선택하는 것이 좋다. 하지만, 처음부터 XML을 사용한다면, 애플리케이션의 반응이 늦어진다. 대부분의 경우, 다음과 같이 텍스트를 XML로 전환하는 것 보다는 name=jennifer 같은 이름/값 쌍을 사용하여 플레인 텍스트를 보내는 것이 더 빠르다:

<name>jennifer</name>

XML을 사용하면 시간이 오래 걸리는 모든 상황을 생각해 보자. 텍스트를 XML로 래핑(wrap)할 때, 추가 정보(실제 요청의 일부인 주변 엘리먼트, XML 헤더 등은 포함하지 않았다.)를 통해 보낼 때, 서버에서 XML을 파싱하고, 응답을 만들고, XML로 응답을 래핑하고, 이것을 다시 웹 페이지로 보낼 때 페이지에서 응답을 파싱하여 이를 사용할 때 등이 있다. 따라서, XML을 사용해야 할 때를 배우고, 많은 상황에서 애플리케이션을 빠르게 실행하려는 생각으로 시작해서는 안된다.




위로


XML을 클라이언트에서 서버로

포맷으로서 XML을 사용하여 클라이언트에서 서버로 데이터를 보내는 방법을 보자. 우선, 기술적인 관점에서 그 방법을 규명하고, 어떤 것이 좋은 생각인지를 검토해보자.

이름/값 쌍 보내기

여러분이 작성하는 웹 애플리케이션의 90퍼센트 정도가 이름/값 쌍을 서버로 보낸다. 예를 들어, 사용자가 이름과 주소를 웹 페이지의 폼에 입력하면, 폼에서 다음과 같은 데이터를 얻는다:

firstName=Larry
lastName=Gullahorn
street=9018 Heatherhorn Drive
city=Rowlett
state=Texas
zipCode=75080

서버로 보낼 데이터에 플레인 텍스트를 사용하면, Listing 1과 같은 코드를 사용한다. (본 시리즈의 첫 번째 글에 사용했던 예제와 비슷하다. (참고자료)


Listing 1. 플레인 텍스트로 된 이름/값 쌍 보내기
				
function callServer() {
  // Get the city and state from the Web form
  var firstName = document.getElementById("firstName").value;
  var lastName = document.getElementById("lastName").value;
  var street = document.getElementById("street").value;
  var city = document.getElementById("city").value;
  var state = document.getElementById("state").value;
  var zipCode = document.getElementById("zipCode").value;

  // Build the URL to connect to
  var url = "/scripts/saveAddress.php?firstName=" + escape(firstName) +
    "&lastName=" + escape(lastName) + "&street=" + escape(street) +
    "&city=" + escape(city) + "&state=" + escape(state) +
    "&zipCode=" + escape(zipCode);

  // Open a connection to the server
  xmlHttp.open("GET", url, true);

  // Set up a function for the server to run when it's done
  xmlHttp.onreadystatechange = confirmUpdate;

  // Send the request
  xmlHttp.send(null);
}

이름/값 쌍을 XML로 변환하기

다음과 같이, 데이터용 포맷으로서 XML을 사용하고 싶다면, 데이터를 저장한 기본적인 XML 포맷을 따라야 한다. 이름/값 쌍은 모두 XML 엘리먼트로 바뀔 수 있고, 여기에서 엘리먼트 이름은 그 쌍에서 이름이 되고, 엘리먼트의 콘텐트는 값이 된다:

<firstName>Larry</firstName>
<lastName>Gullahorn</lastName>
<street>9018 Heatherhorn Drive</street>
<city>Rowlett</city>
<state>Texas</state>
<zipCode>75080</zipCode>

물론, 여러분은 루트 엘리먼트를 갖거나, 단순히 문서 조각 (XML 문서의 일부)으로 작업하는 것이라면, 인클로징(enclosing) 엘리먼트가 필요하다. 위 XML을 다음과 같이 변환한다:

<address>
  <firstName>Larry</firstName>
  <lastName>Gullahorn</lastName>
  <street>9018 Heatherhorn Drive</street>
  <city>Rowlett</city>
  <state>Texas</state>
  <zipCode>75080</zipCode>
</address>

이제 웹 클라이언트에 구조를 만들고, 이것을 서버로 보낼 준비가 얼추 되었다.

구두상의 통신

네트워크를 통해서 XML을 전달하기 전에, 데이터를 보낼 서버, 그리고 스크립트가 XML을 실제로 받아들이는지를 확인해야 한다. 물론 여러분에게는 이렇게 말하는 것 자체가 미안하지만, 신참 프로그래머들은 네트워크를 통해서 XML을 보내면 올바르고 받아서 인터프리팅 되는 것으로 착각하기도 한다.

여러분이 보내는 XML로 된 데이터가 올바르게 수신되도록 하기 위해서는 두 단계를 거쳐야 한다:

  1. 전송할 스크립트가 데이터 포맷으로서 XML을 수락하는지를 확인한다.
  2. 스크립트가 여러분이 보내는 데이터의 특정 XML 포맷과 구조를 수락하는지를 확인한다.

이 두 단계 모두 사람들과 실제로 이야기 해봐야 한다. XML로 데이터를 보내는 것이 중요한 문제라면, 스크립트 라이터는 여러분에게 그렇게 할 것을 명령할 것이다. XML을 받아들일 스크립트를 찾는 것은 어려운 일이 아니다. 하지만, 여러분이 가진 포맷이 스크립트가 기대하고 있는 것과 맞는지를 확인해야 한다. 예를 들어, 서버가 다음과 같은 데이터를 받아들인다고 하자:

<profile>
  <firstName>Larry</firstName>
  <lastName>Gullahorn</lastName>
  <street>9018 Heatherhorn Drive</street>
  <city>Rowlett</city>
  <state>Texas</state>
  <zip-code>75080</zip-code>
</profile>

두 가지를 제외하고는 위 XML과 비슷해 보인다:

  1. 클라이언트에서 온 XML은 address 엘리먼트 내에서 래핑되지만, 서버는 데이터가 profile 엘리먼트 내에서 래핑될 것을 기대하고 있다.
  2. 클라이언트에서 온 XML은 zipCode 엘리먼트를 사용하지만, 서버는 집 코드가 zip-code 엘리먼트에 있을 것을 기대하고 있다.

데이터를 수락하고 처리하는 서버와 페이지를 엉망으로 만들 서버는 이 작은 차이로 만들어진다. 따라서, 여러분은 서버가 기대하고 잇는 것이 무엇인지를 파악하고, 데이터와 포맷을 혼합해야 한다. 그리고 나서야, 클라이언트에서 서버로 XML을 보낼 수 있는 기술을 다룰 준비가 되는 것이다.

서버로 XML 보내기

서버로 XML을 보낼 때, 데이터를 실제로 전송하는 것 보다, 데이터를 취해서 이를 XML로 래핑하는데 코드 작업을 더 많이 해야 한다. 사실, 서버로 보낼 준비가 된 XML 스트링이 있다면, 다른 플레인 텍스트를 보내는 것과 똑같이 보낸다. Listing 2에서, 이것이 어떻게 작동하는지를 보자.


Listing 2. XML로 된 이름/값 쌍 보내기
				

function callServer() {
  // Get the city and state from the Web form
  var firstName = document.getElementById("firstName").value;
  var lastName = document.getElementById("lastName").value;
  var street = document.getElementById("street").value;
  var city = document.getElementById("city").value;
  var state = document.getElementById("state").value;
  var zipCode = document.getElementById("zipCode").value;

  var xmlString = "<profile>" +
    "  <firstName>" + escape(firstName) + "</firstName>" +
    "  <lastName>" + escape(lastName) + "</lastName>" +
    "  <street>" + escape(street) + "</street>" +
    "  <city>" + escape(city) + "</city>" +
    "  <state>" + escape(state) + "</state>" +
    "  <zip-code>" + escape(zipCode) + "</zip-code>" +
    "</profile>";

  // Build the URL to connect to
  var url = "/scripts/saveAddress.php";

  // Open a connection to the server
  xmlHttp.open("POST", url, true);

  // Tell the server you're sending it XML
  xmlHttp.setRequestHeader("Content-Type", "text/xml");

  // Set up a function for the server to run when it's done
  xmlHttp.onreadystatechange = confirmUpdate;

  // Send the request
  xmlHttp.send(xmlString);
}

코드만 봐도 충분히 설명되지만, 몇 가지 중요한 포인트가 있다. 우선, 요청 데이터는 XML로 직접 포맷팅 되어야 한다. Document Object Model을 사용하는 방법을 설명한 세 개의 기술자료를 읽어서 그런지 약간 실망한 것 같다. JavaScript를 사용하는 XML 문서를 만들기 위해 DOM을 사용하지 말란 법은 없지만, GET 또는 POST 요청으로 네트워크를 통해서 이것을 보내기 전에 DOM 객체를 텍스트로 변환해야 한다. 따라서, 일반적인 스트링 조작을 통해 데이터를 포맷팅 하는 것이 훨씬 더 쉽다. 물론, 에러와 타이핑 실수가 있을 수 있으므로, XML을 다루는 코드를 작성할 때 특별히 주의를 기울여야 한다.

XML을 구현하면, 텍스트를 보낼 때와 거의 같은 방법으로 연결을 구축한다. 나는 XML에 POST 요청을 사용하는 것을 더 좋아한다. 어떤 브라우저는 GET 쿼리 스트링에 길이 제한을 두고, 게다가 XML은 길이가 매우 길어질 수 있기 때문이다. 따라서 Listing 2는 GET에서 POST로 변환되었다. 게다가, 여러분이 요청한 URL의 끝에 고정된 매개변수로서가 아닌, send() 메소드를 통해 XML이 보내진다. 이 모두가 매우 사소한 차이이지만 조절하기 쉽다.

하지만, 완전히 새로운 코드를 작성해야 한다:

xmlHttp.setRequestHeader("Content-Type", "text/xml");

이해하기 어렵지 않다: 평이한 이름/값 쌍이 아닌 XML로 보낼 것을 서버에 명령하고 있다. 두 경우 모두, 데이터를 텍스트로 보내지만, 여기에서는 text/xml을 사용하거나 플레인 텍스트로 보내진 XML을 보낸다. 이름/값 쌍을 사용한다면, 이 라인은 다음과 같이 읽힌다:

xmlHttp.setRequestHeader("Content-Type", "text/plain");

XML로 보낼 것을 서버에 명령하지 않으면 문제가 생길 수 있다. 그러므로 주의하기 바란다.

여기까지 다 했다면, 이제는 send()를 호출하고 XML 스트링으로 패스해야 한다. 서버는 XML 요청을 받게 되고, (여러분이 사전 작업을 수행한 상태라면) XML을 수락하여, 이를 파싱하고, 응답을 보낸다. 몇 가지 코드가 변경된 XML 요청이 전부이다.




위로


XML 보내기: 좋은 것인가, 나쁜 것인가?

XML 요청 전에, 요청에 XML을 사용하는 문제에 대해 생각해 보자. 앞서 언급했듯이, XML은 전송의 관점에서 볼 때 가장 빠른 데이터 포맷일 뿐, 그 이상도 이하도 아니다.

XML은 구현이 간단하지 않다.

요청에 사용할 XML은 구성하기가 쉽지 않다. Listing 2에서 보듯, 데이터는 XML 문법으로 인해 빠르게 복잡해진다:

var xmlString = "<profile>" +
  "  <firstName>" + escape(firstName) + "</firstName>" +
  "  <lastName>" + escape(lastName) + "</lastName>" +
  "  <street>" + escape(street) + "</street>" +
  "  <city>" + escape(city) + "</city>" +
  "  <state>" + escape(state) + "</state>" +
  "  <zip-code>" + escape(zipCode) + "</zip-code>" +
  "</profile>";

그렇게 심각한 문제는 아니지만, XML은 단 여섯 개의 필드를 갖고 있다. 여러분이 개발 할 대부분의 웹 폼들은 10개에서 15개의 필드를 갖고 있다. 모든 요청에 Ajax를 사용하지 않더라도, 중요한 문제이다. 실제 데이터만큼 많은 대괄호와 태그 이름들을 처리하는데 많은 시간을 허비해야 하고, 오타의 가능성도 크다.

또 다른 문제는 XML을 직접 구현해야 한다는 점이다. DOM 객체를 요청으로 보낼 수 있는 스트링으로 전환하는 것은 간단한 방법이 아니므로, DOM을 사용하는 것은 좋지 않다. 따라서, 이와 같이 스트링으로 작업하는 것이 최고의 방법이다. 하지만, 관리하기 어렵고, 새로운 개발자들이 이해하기 힘든 방식이기도 하다. 이 경우, 한 줄로 모든 XML을 만들었다. 여러 단계에 걸쳐 이를 수행하면 상황은 더 복잡해진다.

XML은 요청에 어떤 것도 추가하지 않는다.

복잡성 문제 외에도, 요청에 XML을 사용하면 플레인 텍스트와 이름/값 쌍 보다 더 나은 점도 없다. 이 글의 초점은, 이름/값 쌍을 사용하여(Listing 1) 보냈던 데이터를, XML을 사용하여 보내는 것에 맞춰져 있다. 플레인 텍스트를 사용하여 보낼 수 없는 것을 XML을 사용하여 보낼 수 있다라고 말하는 것이 아니다. 플레인 텍스트를 사용하여 보낼 수 없는 것을 XML을 사용한다고 해서 가능해지는 것은 결코 아니기 때문이다.

이것이 XML과 요청의 본질이다. 여기에 중요한 이유는 없다. 다음 글에서는, 플레인 텍스트를 사용할 때 다루기 어려웠던 몇 가지 사안들을 XML로 해결하는 방법에 대해 설명하겠다. 오직 XML만 받아들일 것을 스크립트에 명령하지 않을 것이라면, 거의 모든 요청 상황에서 플레인 텍스트를 사용하는 것이 더 낫다.




위로


맺음말

Ajax에서 XML의 의미에 대해 감이 잡혔으리라 생각한다. Ajax 애플리케이션은 XML을 사용할 필요가 없고, XML은 데이터 전송에 있어서 만병 통치약도 아니라는 것도 이제 알게 되었다. 웹 페이지에서 서버로 XML을 보내는 것에도 익숙해졌을 것이다. 서버가 요청을 처리하고 응답을 보낼 때 어떤 것들이 개입되어 있는지도 알게 되었다. 서버 스크립트가 XML을 허용하도록 하고, 데이터를 보내려면 여러분이 사용하는 포맷으로 이를 수락해야 한다.

요청용 데이터 포맷에 XML이 좋은 선택이 아니라는 이유도 알았다. 다음 글에서는, XML이 효과적인 경우와, 대부분의 요청에서는 모든 것을 느려지게 만들고, 복잡해진다는 사실을 알게 될 것이다. 이 글에서 배운 것을 직접 실행에 옮겨 보고, 매우 신중히 수행할 것을 말해주고 싶다. XML 요청은 Ajax 애플리케이션에서 역할이 있지만, 그 역할은 여러분이 생각하는 것만큼 크지 않다.

다음 글에서는, XML을 사용하여 서버가 응답하는 방법, 웹 애플리케이션이 응답을 처리하는 방법을 설명하겠다. 서버가 XML을 웹 애플리케이션으로 돌려보내는 경우가 많기 때문에 자세한 기술적인 부분들까지 배우게 될 것이다. 지금까지는, 요청을 보낼 때 XML이 좋은 선택이 아닌지를 이해하는 것으로 충분하다. 요청용 데이터 포맷으로서 XML을 사용하여 웹 애플리케이션을 구현하고, 이를 다시 플레인 텍스트로 변환하여, 어떤 것이 더 빠르고 쉬운지를 파악할 수 있다. 다음 글을 기대해주기 바란다.

기사의 원문보기



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Photo of Brett McLaughlin

Nextel Communications에서는 복잡한 엔터프라이즈 시스템을 구현했고, Lutris Technologies에서는 애플리케이션 서버를 개발했다. O'Reilly Media에서는 기고 및 편집 활동을 하고 있다. 베스트 셀러 작가인 Eric과 Beth Freeman과 공동 집필한 Head Rush Ajax는 Ajax에 혁신적인 Head First 방식을 취하고 있다. 그의 신간 Java 1.5 Tiger: A Developer's Notebook은 최신 자바 버전을 다룬 첫 번째 책이며, 그의 고전 Java and XML은 자바 언어에서 XML을 사용하는 방법에 대한 바이블로 통하고 있다.

:
Posted by 뽀기

DOM과 JavaScript를 결합하여 페이지 리로드 없이 웹 페이지의 사용자 인터페이스 변경하기

developerWorks

 

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




난이도 : 중급

Brett McLaughlin, Author and Editor, O'Reilly Media Inc.

2006 년 12 월 12 일

Document Object Model (DOM)와 JavaScript 코드를 결합하여 인터랙티브 Ajax 애플리케이션을 구현해봅시다. 이전 글에서는, DOM 프로그래밍의 개념과 웹 브라우저가 어떻게 웹 페이지를 트리로서 보는지를 설명했습니다. 이제는 DOM에 사용되는 프로그래밍 구조를 이해할 차례입니다. 여러분이 배운 모든 것을 실제로 적용하여, 간단한 웹 페이지를 구현해 봅시다. 웹 페이지의 모든 효과들은 JavaScript를 사용하여 구현됩니다.

시리즈 소개

Document Object Model 또는 DOM을 소개한 두 개의 기술자료가 있다. 이제는 DOM이 어떻게 작동하는지 잘 알 수 있다. (참고자료) 이 글에서는 우리가 이해한 것을 실행에 옮길 차례이다. 사용자 액션에 기반하여 변하는 사용자 인터페이스를 가진 기본적인 웹 애플리케이션을 개발할 것이다. 물론, DOM을 사용하여 인터페이스도 변경할 것이다. 이 글을 다 마치면, DOM 기술과 개념을 실전에 적용할 정도의 실력을 갖추게 될 것이다.

지난 두 개의 기술자료를 다 읽었을 것이라고 간주하겠다. 그렇지 않다면, 잘 읽어보고 DOM과 웹 브라우저가 HTML과 CSS를 웹 페이지를 나타내는 하나의 트리 구조로 전환하는 방법을 이해하기 바란다. 지금까지, 내가 이야기했던 모든 DOM 원리들은 단순한 DOM 기반 동적 웹 페이지를 구현하는데 사용될 것이다. 이 글을 읽다가 조금이라도 이해가 되지 않는 부분이 있다면 이전 기술자료들을 다시 읽어보기 바란다.

샘플 애플리케이션으로 시작하기

코드에 대하여

DOM과 JavaScript 코드에 집중하기 위해, 인라인 스타일(예를 들어, h1p 엘리먼트에 대한 align 애트리뷰트 같은)로 HTML을 작성했다. 이렇게 하는 것도 좋지만, 시간을 들여서 여러분의 모든 스타일을 여러분이 개발하는 실행 애플리케이션용 외부 CSS 스타일시트에 두기를 권하고 싶다.

매우 기본적인 애플리케이션에 작은 DOM 매직을 추가해 보자. DOM이 폼을 제출하지 않고 웹 페이지 상에서 이동할 수 있다는 것을 유념하고(이것은 Ajax에 확실히 대조되는 부분이다.) 뒤집어진 모자와 Hocus Pocus!라는 레이블 버튼만 보여주는 페이지를 만들어 보자.

첫 HTML

Listing 1은 이 페이지에 대한 HTML이다. 헤딩과 폼을 가진 바디와 단순한 이미지와 누를 수 있는 버튼이 있을 뿐이다.


Listing 1. 샘플 애플리케이션용 HTML
				
<html>
 <head>
  <title>Magic Hat</title>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" />
   </p>
  </form>                                                                     
 </body>
</html>

아래 다운로드 섹션에서는, 이 글에 사용되는 모든 이미지와 HTML이 제공된다. 하지만, 이미지만 다운로드하고, 내가 이 글에서 구현한 애플리케이션 샘플을 보면서 여러분이 직접 HTML을 작성하길 바란다. 이렇게 하면 DOM 코드를 훨씬 더 잘 이해할 수 있을 것이다.

샘플 웹 페이지 보기

여기에는 트릭이 전혀 없다. 페이지를 열면 그림 1과 같은 모습이 보인다.


그림 1. 평범한 모자
A rather boring-looking top hat

HTML의 마지막 포인트

여러분이 기억해야 할 한 가지 중요한 포인트는 Listing 1그림 1의 폼에 있는 버튼이 제출 버튼이 아닌 button 유형이라는 것이다. 제출 버튼을 사용할 경우, 버튼을 누르면 브라우저가 폼을 제출하게 된다. 물론, 이 폼은 어떤 action 애트리뷰트(완전히 의도적인)가 없기 때문에, 이것은 어떤 액티비티도 없는 무한 루프만을 만들게 된다. (직접 실험해 봐도 좋다.) 정상적인 인풋 버튼을 사용하고, 제출 버튼을 피하면, JavaScript 함수를 버튼에 연결하고 폼을 제출하지 않고 브라우저와 인터랙팅 한다.




위로


샘플 애플리케이션에 더 많은 것 추가하기

이제 JavaScript, DOM 조작, 이미지 마법사를 가진 웹 페이지로 꾸며보자.

getElementById() 함수 사용하기

마법사 모자에는 토끼가 빠지면 안되는 법이다. 이 경우, 기존 페이지(그림 1에 있는 이미지를 토끼 이미지(그림 2)로 바꾼다.


그림 2. 똑 같은 모자에 토끼가 생겼다.
The same top hat, this time with a rabbit

DOM에 트릭을 추가하는 첫 번째 단계에는 웹 페이지에 img 엘리먼트를 나타내는 DOM 노드를 찾는 것도 포함된다. 일반적으로, 가장 쉬운 방법은 getElementById() 메소드를 사용하는 것이다. 이는 웹 페이지를 나타내는 document 객체에 대해 사용할 수 있다. 여러분은 전에 이 메소드를 보았다. 다음과 같이 작동한다.

var elementNode = document.getElementById("id-of-element");

HTML에 id 애트리뷰트 추가하기

이것은 매우 기본적인 JavaScript이지만, HTML에서는 몇 가지 작업이 필요하다. id 애트리뷰트를 액세스 하고자 하는 엘리먼트에 추가한다. 이것은 대체하고자 하는 img 엘리먼트이다. (토끼를 포함하고 있는 새로운 이미지이다.) 따라서 Listing 2처럼, HTML을 변경해야 한다.


Listing 2. id 애트리뷰트 추가하기
				

<html>
 <head>
  <title>Magic Hat</title>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" />
   </p>
  </form>                                                                     
 </body>
</html>

페이지를 리로드(reload)하면, 아무런 변화도 볼 수 없다. id 애트리뷰트를 추가한다고 해서 웹 페이지에 시각적인 효과가 생기지 않는다. 하지만, JavaScript와 DOM을 보다 쉽게 사용하여 엘리먼트로 작업할 수 있다.

img 엘리먼트

이제 getElementById()를 쉽게 사용할 수 있다. 원하는 엘리먼트의 아이디인 -- topHat-- 이 생겼고, 새로운 JavaScript 변수에 이것을 저장할 수 있다. Listing 3의 코드를 HTML 페이지에 추가한다.


Listing 3. img 엘리먼트에 액세스 하기
				

<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" />
   </p>
  </form>                                                                     
 </body>
</html>

이 지점에서 웹 페이지를 로딩 또는 재로딩 한다 해도 별다른 차이를 볼 수 없다. 이 이미지에 액세스 하더라도 어던 것도 할 수 없다.




위로


이미지 변경하기: 어려운 길

두 가지 방법으로 변경할 수 있다. 어려운 길과 쉬운 길이 있다. 모든 프로그래머가 그렇겠지만, 쉬운 방법을 선호한다. 하지만, 어려운 방법일 수록 DOM을 연습하는 최상의 방법이다. 우선 어려운 방법으로 이미지를 변경하는 방법부터 살펴보자. 나중에, 더 쉬운 방법으로 같은 작업을 하는 방법도 설명하겠다.

다음은 기존 이미지를 새로운, 토끼가 있는 이미지로 대체하기 위해서는;

  1. 새로운 img 엘리먼트를 만든다.
  2. 부모 엘리먼트에 액세스 한다. 즉, 현재 img 엘리먼트의 컨테이너에 액세스 한다.
  3. 기존 img 엘리먼트 바로 전에 컨테이너의 자식으로서 새로운 img 엘리먼트를 삽입한다.
  4. 이전 img 엘리먼트를 제거한다.
  5. 사용자가 Hocus Pocus! 버튼을 클릭하면 여러분이 만들었던 JavaScript 함수가 호출될 수 있도록 설정한다.

새로운 img 엘리먼트 만들기

여러분도 기억하겠지만, DOM의 핵심은 document 객체이다. 이것은 전체 웹 페이지를 나타내고, getElementById() 같은 강력한 메소드로 액세스 할 수 있도록 해주며, 새로운 노드를 만들 수 있도록 한다. 이것이 바로 지금 사용 할 마지막 속성이다.

새로운 img 엘리먼트를 만들어야 한다. 돔에서, 모든 것은 노드이지만, 노드들은 세 개의 기본적인 그룹으로 나뉜다:

  • 엘리먼트
  • 애트리뷰트
  • 텍스트 노드

다른 그룹들도 있지만 이 세 가지가 99퍼센트를 차지한다. 이 경우, img 유형의 새로운 엘리먼트가 필요하다. 따라서, JavaScript에 이 코드가 필요하다:

var newImage = document.createElement("img");

이것은 img라는 엘리먼트 이름을 가진 element 유형의 새로운 노드를 만든다. HTML에서는 다음과 같은 것이 된다:

<img />

DOM은 잘 구성된 HTML을 만들 것이고, 엘리먼트는 시작 태그와 엔딩 태그와 함께, 현재 비어있다. 이 엘리먼트에 콘텐트와 애트리뷰트를 추가하고, 이를 웹 페이지에 삽입한다.

콘텐트의 경우, img 엘리먼트는 빈 엘리먼트이다. 하지만 애트리뷰트를 추가해야 한다: src 애트리뷰트는 로딩 할 이미지를 지정한다. 여기에서 addAttribute() 같은 메소드를 사용해야 한다고 생각하겠지만, 틀렸다. DOM 스팩 작성자는, 프로그래머들은 지름길(shortcut)을 좋아한다고 생각했기 때문에, 새로운 애트리뷰트를 추가하고 기존 애트리뷰트의 값을 변경할 수 있는 하나의 메소드인 setAttribute()를 만들었다.

setAttribute()를 호출하고 기존 애트리뷰트를 제공한다면, 값은 여러분이 제공한 값으로 변한다. 하지만, setAttribute()를 호출하고 존재하지 않는 애트리뷰트를 제공하면, DOM은 여러분이 제공한 값을 사용하여 애트리뷰트를 추가한다. 한 가지 메소드로 두 가지 목적을 달성했다. 따라서, 다음을 JavaScript에 추가해야 한다.

var newImage = document.createElement("img");
newImage.setAttribute("src", "rabbit-hat.gif");

이것은 이미지를 만들고, 소스를 알맞게 설정한다. 이제 여러분의 HTML은 Listing 4와 같을 것이다.


Listing 4. DOM을 사용하여 새로운 이미지 만들기
				
<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
      var newImage = document.createElement("img");
      newImage.setAttribute("src", "rabbit-hat.gif");
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" />
   </p>
  </form>                                                                     
 </body>
</html>

이 페이지를 로딩할 수 있지만, 어떤 액션도 기대할 수는 없다. 실제 웹 페이지에 효과를 줄 어떤 것도 수행하지 않았다. step 5를 돌아보면, JavaScript 함수가 아직 호출되지도 않았다는 것을 알 수 있다.

원래 이미지의 부모에 액세스 하기

이미지를 삽입할 준비가 되었으니, 이를 어딘가에는 삽입해야 한다. 하지만, 기존 이미지에 삽입하지 않는다. 대신, 이것을 기존 이미지 앞에 두고, 기존 이미지를 제거한다. 이렇게 하려면, 기존 이미지의 부모가 필요하다. 이는 삽입과 제거의 핵심이다.

DOM은 웹 페이지를 노드의 계층인 트리로 간주한다. 모든 노드는 부모를 갖고 있고, 부모에는 자식 노드들이 있다. 이 이미지의 경우, 어떤 자식들도 없다. 이미지가 비어있는 엘리먼트라는 것을 기억하는가? 하지만 확실히 부모는 있다. 부모가 어떤 것인지에 대해서 신경 쓰지 않는다. 다만 여기에 액세스 할 뿐이다.

이렇게 하려면 모든 DOM 노드가 갖고 있는 parentNode속성을 사용한다:

var imgParent = hatImage.parentNode;

아주 간단하다. 부모는 자식들을 가질 수 있다는 것을 알고 있다. 이미 하나를 갖고 있다. 바로 기존 이미지이다. 부모가 div 인지 p 인지, 또는 페이지의 body 인지 알 필요가 없다; 전혀 문제가 되지 않는다!

새로운 이미지 삽입하기

오랜 이미지의 부모가 있으므로, 새로운 이미지를 삽입할 수 있다. 매우 쉽다. 자식을 추가 할 여러 메소드를 사용할 수 있기 때문이다:

  • insertBefore(newNode, oldNode)
  • appendChild(newNode)

오래된 이미지가 있는 곳에 새로운 이미지가 나타나도록 할 것이기 때문에 insertBefore()가 필요하다. (removeChild() 메소드도 사용해야 한다.) 다음은 기존 이미지 앞에 새로운 이미지 엘리먼트를 사용할 JavaScript 라인이다:

var imgParent = hatImage.parentNode;
imgParent.insertBefore(newImage, hatImage);

오래된 이미지의 부모는 두 개의 자식 이미지를 갖고 있다: 새로운 이미지 바로 다음에 오랜 이미지가 따라온다. 이러한 이미지들 주위에 있는 콘텐트는 변하지 않고, 콘텐트의 순서는 삽입되기 전과 똑같다. 이제 부모는 오랜 이미지 바로 앞에 새로운 이미지를 더 갖게 된다.

오랜 이미지 제거하기

이제 제거해야 하는 것은 오랜 이미지이다. 웹 페이지에 새로운 이미지만 필요하다. 이 샘플에서, 이미 오랜 이미지 엘리먼트의 부모가 있다. removeChild()를 호출하고 제거하기 원하는 노드로 전달한다:

var imgParent = hatImage.parentNode;
imgParent.insertBefore(newImage, hatImage);
imgParent.removeChild(hatImage);

여기에서, 오랜 이미지를 새로운 이미지로 대체해야 한다. HTML은 Listing 5처럼 된다.


Listing 5. 오랜 이미지를 새로운 이미지로 대체하기
				

<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
      var newImage = document.createElement("img");
      newImage.setAttribute("src", "rabbit-hat.gif");
      var imgParent = hatImage.parentNode;
      imgParent.insertBefore(newImage, hatImage);
      imgParent.removeChild(hatImage);
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" />
   </p>
  </form>                                                                     
 </body>
</html>

JavaScript 연결하기

마지막 단계이자, 가장 쉬운 단계는 HTML 폼을 여러분이 작성한 JavaScript 함수로 연결하는 것이다. 사용자가 Hocus Pocus! 버튼을 클릭할 때마다 실행 될 showRabbit() 함수가 필요하다. onClick 이벤트 핸들러를 HTML에 추가한다:

<input type="button" value="Hocus Pocus!" onClick="showRabbit();" />

JavaScript 프로그래밍에서, 이는 매우 일상적인 일이다. 이것을 HTML 페이지에 추가하고, 페이지를 저장하고, 페이지를 웹 브라우저로 로딩한다. 이 페이지는 처음에는 그림 1처럼 보인다; Hocus Pocus!를 클릭하면 그림 3과 같이 된다.


그림 3. 토끼가 등장하다!
The rabbit has come out to play



위로


이미지 변경하기: 보다 쉬운 방법

이미지를 변경할 때 여러 가지 방법을 사용할 수 있다고 배웠다. replaceNode() 메소드를 사용하여 하나의 노드를 다른 노드로 대체할 수 있다. 다음과 같은 순서로 실행한다:

  1. 새로운 img 엘리먼트를 만든다.
  2. 부모 엘리먼트에 액세스 한다. 현재 img 엘리먼트의 컨테이너에 액세스 한다.
  3. 기존 img 엘리먼트 바로 전에 컨테이너의 자식으로서 새로운 img 엘리먼트를 삽입한다.
  4. 이전 img 엘리먼트를 제거한다.
  5. 사용자가 Hocus Pocus! 버튼을 클릭하면 여러분이 만들었던 JavaScript 함수가 호출될 수 있도록 설정한다.

replaceNode()를 사용하면, 위 단계들을 줄일 수 있다. 3단계와 4 단계를 통합하여 다음과 같이 수행한다.

  1. 새로운 img 엘리먼트를 만든다.
  2. 부모 엘리먼트에 액세스 한다. 현재 img 엘리먼트의 컨테이너에 액세스 한다.
  3. 오랜 img 엘리먼트를 새로운 것으로 대체한다.
  4. 사용자가 Hocus Pocus! 버튼을 클릭하면 여러분이 만들었던 JavaScript 함수가 호출될 수 있도록 설정한다.

대단한 것 같지는 않지만, 코드가 확실히 간단해 졌다. Listing 6은 변경 방법이다: insertBefore()removeChild() 메소드 호출을 제거했다.


Listing 6. 오랜 이미지를 새로운 이미지로 대체하기
				

<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
      var newImage = document.createElement("img");
      newImage.setAttribute("src", "rabbit-hat.gif");
      var imgParent = hatImage.parentNode;
      imgParent.replaceChild(newImage, hatImage);
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" onClick="showRabbit();"  />
   </p>
  </form>                                                                     
 </body>
</html>

큰 변화가 없어 보이지만, DOM 코딩에서는 다소 중요한 포인트이다. 여러분은 주어진 태스크를 수행할 때 몇 가지 방법을 모색한다. DOM 메소드를 신중히 검토한다면, 네 개 또는 다섯 단계를 두 단계 또는 세 단계로 줄일 수 있다.




위로


이미지 변경하기: 가장 쉬운 방법

태스크를 수행하는데 더 쉬운 방법은 늘 있다고 말해왔기 때문에, 이제는 이미지를 변경하는 가장 쉬운 방법을 설명하겠다. 어떤 방법일지 알 수 있겠는가? 힌트는 애트리뷰트이다.

이미지 엘리먼트는 src 애트리뷰트로 제어된다. 이는 어딘가에 있는(로컬 URI 또는 외부 URL) 파일을 참조한다. 지금까지, 이미지 노드를 새로운 이미지로 대체했다. 하지만, 기존 이미지의 src 애트리뷰트를 변경하는 것이 훨씬 쉬운 방법이다! 새로운 노드를 만들고, 부모를 찾고, 오랜 노드를 대체하는 작업을 한 단계로 줄인다:

hatImage.setAttribute("src", "rabbit-hat.gif");

이것이 끝이다. Listing 7에서, 전체 웹 페이지의 정황에서 이 해법을 볼 수 있다.


Listing 7. src 애트리뷰트 변경하기
				

<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
      hatImage.setAttribute("src", "rabbit-hat.gif");
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" onClick="showRabbit();"  />
   </p>
  </form>                                                                     
 </body>
</html>

이것은 DOM의 가장 훌륭한 측면이다. 애트리뷰트를 업데이트 할 때, 웹 페이지도 즉시 변한다. 이미지가 새로운 파일을 가리키면, 브라우저는 그 파일을 로딩하고, 페이지가 업데이트 된다. 리로딩이 필요 없고, 새로운 이미지 엘리먼트를 만들 필요가 없다. 결과는 그림 3과 동일하다. -- 단지 코드만 단순해졌을 뿐이다.




위로


토끼 숨기기

현재, 웹 페이지는 너무 단순하고 엉성하다. 토끼가 모자에서 나왔지만, 스크린 밑에 있는 버튼은 여전히 Hocus Pocus!이다. 토끼가 나왔는데도 showRabbit()을 호출하는 형국이다. 토끼가 나온 후에도 버튼을 클릭하면, 프로세싱 시간만 낭비하는 것이다. 이것은 불필요한 것이다. DOM을 사용하면. 토끼가 안에 있거나 밖으로 나왔을 경우 유용한 버튼을 만들 수 있다.

버튼 레이블 변경하기

가장 쉬운 방법은 사용자가 클릭을 한 후에 버튼의 레이블을 변경하는 것이다. 이렇게 한다고 해서, 더 이상의 마법이 발생하지 않는 것은 아니다. 웹 페이지에서 발생할 수 있는 최악의 상황은 정확하지 않은 어떤 것을 삽입하는 것이다. 버튼 레이블을 변경하기 전에, 노드에 액세스 해야 한다. 그 이전에, 버튼을 참조할 ID가 필요하다. 그것이 바로 오랜 모자이다. Listing 8은 버튼에 id 애트리뷰트를 추가하는 모습이다.


Listing 8. id 애트리뷰트 추가하기
				

<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
      hatImage.setAttribute("src", "rabbit-hat.gif");
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" id="hocusPocus" 
           onClick="showRabbit();" />
   </p>
  </form>                                                                     
 </body>
</html>

JavaScript의 버튼에 액세스 하는 것은 쉽다:

function showRabbit() {
  var hatImage = document.getElementById("topHat");
  hatImage.setAttribute("src", "rabbit-hat.gif");
  var button = document.getElementById("hocusPocus");
}

물론, JavaScript의 다음 라인에 버튼 레이블의 값을 변경하기 위해 타이핑을 했을 것이다. setAttribute()가 작동한다:

function showRabbit() {
  var hatImage = document.getElementById("topHat");
  hatImage.setAttribute("src", "rabbit-hat.gif");
  var button = document.getElementById("hocusPocus");
  button.setAttribute("value", "Get back in that hat!");
}

이렇게 간단한 DOM 조작으로, 버튼의 레이블은 토끼가 나타나자마자 변한다. HTML과 완료된 showRabbit() 함수는 Listing 9처럼 보인다.


Listing 9. 완료된 웹 페이지
				

<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
      hatImage.setAttribute("src", "rabbit-hat.gif");
      button.setAttribute("value", "Get back in that hat!");
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" id="hocusPocus" 
           onClick="showRabbit();" />
   </p>
  </form>                                                                     
 </body>
</html>

토끼를 다시 집어넣기

새로운 버튼 레이블을 보면 알겠지만, 토끼를 다시 모자 속으로 집어넣어야 한다. 토끼를 나타내게 했던 프로세스를 거꾸로 실행하면 된다. 이미지의 src 애트리뷰트를 오랜 이미지로 바꾼다. 새로운 JavaScript 함수를 만들어야 한다:

function hideRabbit() {
  var hatImage = document.getElementById("topHat");
  hatImage.setAttribute("src", "topHat.gif");
  var button = document.getElementById("hocusPocus");
  button.setAttribute("value", "Hocus Pocus!");
}

showRabbit() 함수가 했던 모든 것을 역으로 하면 된다. 이미지를 토끼가 없었던 모자 이미지로 설정하고, 버튼을 그랩(grab)하고, 레이블을 다시 Hocus Pocus!로 바꾼다.




위로


이벤트 핸들러 다루기

샘플 애플리케이션에 한 가지 큰 문제가 생겼다. 버튼 레이블이 변하더라도, 버튼을 클릭할 때 발생하는 액션은 변하지 않았다. 다행히, DOM을 사용하면 사용자가 버튼을 클릭할 때, 이벤트(발생하는 액션)를 변경할 수 있다. 버튼이 Get back in that hat!,을 읽으면, 클릭될 때 hideRabbit()을 실행한다. 토끼가 숨겨지면, 버튼은 showRabbit()을 실행하는 것으로 리턴된다.

addEventHandler() 피하기

onclick 속성 이외에도, onClick이나 onBlur 같은 이벤트 핸들러를 추가하는데 사용되는 메소드가 있다; 바로 addEventHandler()이다. 하지만, Microsoft™ Internet Explorer™는 이 메소드를 지원하지 않으므로, JavaScript에서 이를 사용한다면, 수많은 Internet Explorer 사용자들은 에러 밖에 얻을 것이 없다. 이 메소드를 사용하지 않도록 한다. 이 글에서는 이 방식으로 같은 결과를 얻을 수 있지만, Internet Explorer에서는 그럴 수 없다.

HTML을 보면, 여기에서 여러분이 다루는 이벤트가 onClick이라는 것을 알 수 있다. JavaScript에서, 이 이벤트는 버튼의 onclick 속성을 사용하여 이벤트를 참조할 수 있다. (HTML에서, 이 속성은 onClick으로 참조된다. 대문자 C라는 것을 주목하라. JavaScript에서는 소문자 c를 사용한 onclick 이다.) 따라서, 버튼에 대해 실행되는 이벤트를 변경할 수 있다: 새로운 함수를 onclick 속성으로 할당하면 된다.

onclick 속성에는 함수의 스트링 이름이 아닌 함수 레퍼런스가 입력되어야 한다. 하지만 함수 자체에 대한 레퍼런스이다. JavaScript에서는 함수를 괄호가 없는 이름으로 참조할 수 있다. 따라서 버튼이 클릭될 때 실행되는 함수를 다음과 같이 변경할 수 있다:

button.onclick = myFunction;

HTML에서, 변경은 매우 단순하다. Listing 10에서, 버튼 실행 함수를 보자.


Listing 10. 버튼의 onClick 함수 변경하기
				

<html>
 <head>
  <title>Magic Hat</title>
  <script language="JavaScript">
    function showRabbit() {
      var hatImage = document.getElementById("topHat");
      hatImage.setAttribute("src", "rabbit-hat.gif");
      var button = document.getElementById("hocusPocus");
      button.setAttribute("value", "Get back in that hat!");
      button.onclick = hideRabbit;
    }

    function hideRabbit() {
      var hatImage = document.getElementById("topHat");
      hatImage.setAttribute("src", "topHat.gif");
      var button = document.getElementById("hocusPocus");
      button.setAttribute("value", "Hocus Pocus!");
      button.onclick = showRabbit;                                           
    }
  </script>
 </head>

 <body>
  <h1 align="center">Welcome to the DOM Magic Shop!</h1>
  <form name="magic-hat">
   <p align="center">
    <img src="topHat.gif" id="topHat" />
    <br /><br />
    <input type="button" value="Hocus Pocus!" id="hocusPocus" 
           onClick="showRabbit();" />
   </p>
  </form>                                                                     
 </body>
</html>

이제 DOM 애플리케이션이 완성되었다. 직접 시험해 보기 바란다!




위로


맺음말

이제는 DOM에 어느 정도 익숙해 졌으리라 믿는다. 이전 글에서는, DOM의 기본적인 개념과 API에 대해 설명했다. 오늘은 단순한 DOM 기반 애플리케이션을 설명했다. 이 글을 다시 복습하기 바란다.

이것으로 본 시리즈에서 Document Object Model을 다루는 일은 더 이상 없겠지만, 여러분의 임무는 끝나지 않았다. 사실, 여러분은 DOM을 사용하지 않고 Ajax와 JavaScript로 훨씬 더 많은 작업을 해야 할 상황에 처하게 될 것이다. 복잡한 하이라이팅과 움직임 효과를 만들든지, 텍스트 블록이나 이미지로 작업하든지 간에, DOM 방식을 사용하면 매우 쉽게 수행할 수 있다.

DOM을 사용하는 것이 아직 어색하다면, 세 개의 기술자료들을 복습하기 바란다. 나머지 기술자료에서는 DOM에 대해서는 자세히 설명하지 않을 것이다. DOM을 사용하고, DOM 기반 애플리케이션을 직접 작성해보면 여러분도 데이터 포맷에 좀더 익숙해 질 것이다.

기사의 원문보기





위로


다운로드 하십시오

설명 이름 크기 다운로드 방식
Example graphics only wa-ajaxintro6/ajax_6-images_download.zip 91 KB HTTP
Complete example, including HTML and graphics wa-ajaxintro6/ajax_6-complete_examples.zip 93 KB HTTP
다운로드 방식에 대한 정보 Get Adobe® Reader®


참고자료

교육

제품 및 기술 얻기

토론


필자소개

Photo of Brett McLaughlin

Nextel Communications에서는 복잡한 엔터프라이즈 시스템을 구현했고, Lutris Technologies에서는 애플리케이션 서버를 개발했다. O'Reilly Media에서는 기고 및 편집 활동을 하고 있다. 베스트 셀러 작가인 Eric과 Beth Freeman과 공동 집필한 Head Rush Ajax는 Ajax에 혁신적인 Head First 방식을 취하고 있다. 그의 신간 Java 1.5 Tiger: A Developer's Notebook은 최신 자바 버전을 다룬 첫 번째 책이며, 그의 고전 Java and XML은 자바 언어에서 XML을 사용하는 방법에 대한 바이블로 통하고 있다.

:
Posted by 뽀기