홈페이지 꼭대기로

JSP 2.0: 뭐가 바뀌었나? - 4부

원문 : http://www.onjava.com/pub/a/onjava/2004/05/12/jsp2part4.html

by Hans Bergsten, JavaServer Pages, 3판의 저자

번역 손권남(kwon37xi_aT_yahoo dOt co DoT kr)

2004/07/30

이번 "JSP 2.0: 뭐가 바뀌었나?" 시리즈의 마지막회에서는 커스텀 태그 라이브러리를 개발하기 쉽게 해주는 두가지 새로운 기법을 알아볼 것이다 : 태그 파일(tag files)과 새롭고 간결해진 태그 핸들러 JAVA API이다.

JSP 태그 파일로 커스텀 액션 개발하기

(주: "커스텀"은 "사용자 정의, 사용자가 따로 만든"이라는 정도의 의미이다. 이미 많은 책들이 커스텀 태그, 커스텀 액션이라는 말을 쓰고 있으므로 번역하지 않는다.)

JSP는 Java의 구루 경지에 오르지 않은 사람들이 동적인 컨텐트를 가진 웹 페이지를 만들 수 있도록 하려고 탄생한 것이다. 지금까지 동적이고 복잡한 컨텐트의 조각들을 여러 페이지간에 재사용하는 것이 약간 힘들었던게 사실이다. 만약 당신이 변수에 저장된 질문과 답변들을 가진 간단한 설문조사를 여러 페이지에 추가하고 싶다고 하자. JSP 2.0 이전에는 세가지 방식이 있었다. 설문조사가 필요한 모든 페이지에 설문조사 코드를 복사해서 붙여넣을 수 있다. 더 나은 방법으로 설문조사 폼을 생성하는 JSP를 따로 제작하고, 설문조사가 필요한 페이지에서 <jsp:include>나 <c:import> 액션으로 이 페이지를 불러들인다. 하지만 제약사항이 있는데, 예를들어 답변같은 것을 인자로 넘겨주기 위해 자바 빈이나 Map 등을 사용하지 못하고 오직 String만을 사용할 수 있다. 세번째 방식으로 Java 태그 핸들러 클래스로 커스텀 액션을 구현하는 것인데, 이것은 Java를 알아야만 한다.

JSP 2.0에는 네번째 방식이 있다: 다시 말해서 태그 파일로 커스텀 액션을 개발하는 것이다. 태그 파일은 정규 JSP페이지 처럼 동적인 부분에 JSP 요소들을 사용할 수 있는 텍스트 파일이다. 이것은 Java 태그 핸들러와 같은 목적으로 만들어진 것이다. 즉, 커스텀 액션을 위한 로직을 제공한다. 태그 파일과 JSP 페이지의 주된 차이점은 태그 파일은 파일 확장자가 .tag이고, 페이지 지시자 대신 태그 지시자를 사용하고 태그 파일에서만 유효한 새로운 몇몇 지시자를 이용해서 입력과 출력을 선언할 수 있다는 것이다.

자세히 한 번 살펴보자. 여기 간단한 설문조사 폼을 만드는 "poll.tag"라는 태그 파일이 있다.

<%@ tag body-content="empty" %>
<%@ attribute name="question" required="true" %>
<%@ attribute name="answers" required="true" 
   type="java.lang.Object" %>
<%@ attribute name="votesMapName" required="true" %>
<%@ attribute name="answersMapName" required="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

Question: ${question}<br>
<form action="result.jsp" target="result">
   <input type="hidden" name="question" value="${question}">
   <input type="hidden" name="votesMapName" value="${votesMapName}">
   <input type="hidden" name="answersMapName" value="${answersMapName}">
   <c:forEach items="${answers}" var="a">
      <input type="radio" name="vote" value="${a.key}">${a.value}<br>
   </c:forEach>
   <input type="submit" value="Vote">
</form>

파일 맨 위 태그 지시자가 있다. 태그 지시자는 JSP 페이지에서 사용하는 페이지 지시자와 비슷하게 일반적인 파일의 성격을 규정한다. 여기서 나는 body-content 속성을 사용해서 JSP 페이지에서 이 태그 파일을 나타내는 액션 요소의 값이 비어 있어야 함을 선언했다. 예를 들면, 이 액션 요소는 바디를 가져서는 안된다. 비어있는 값 대신 스크립트가 아닌 값(바디는 스크립팅 요소를 제외하고 어떤 값이든 포함할 수 있다), 혹은 태그 의존값(컨테이너는 바디의 내용을 어떠한 변환도 없이 그대로 태그 핸들러에 넘겨준다) 등을 사용할 수 있다. 커스텀 액션을 자바 클래스로 개발해봤다면, body-content와 유효한 값들이 태그 라이브러리 기술자(Tag Library Descriptor; TLD)에서 왔음을 알 수 있다. 태그 지시자와 다른 특별한 태그 파일 지시자들은 TLD가 JSP 컨테이너에 제공하는 것과 동일한 정보를 제공하기 위해 사용된다.

이 예제에서 태그 지시자 다음에 나오는 attribute 지시자는 동명의 TLD 요소가 하는 것과 같은 역할을 한다. 이것은 유효한 커스텀 액션 요소의 속성을 선언한다. poll 태그 파일은 네가지 속성을 가진다 :

액션 요소 속성 하나당 name 속성으로 선언된 이름을 가진 한개의 attribute 지시자가 있다. 이 예제의 모든 액션 요소 속성은 각 attribute 지시자의 required 속성에 선언된 대로 필수적으로 요구된다. type 속성에 의해 선언된 대로, answers 속성의 값은 Map 객체이어야만 한다. 그외 다른 모든 속성은 기본 형인 String 형이어야한다. 그래서 나는 그러한 속성에는 type 속성을 명시하지 않았다.

이 태그 파일의 나머지는 일반적인 기존 JSP 이다. taglib 지시자가 JSTL core 라이브러리가 이파일에서 사용된다고 선언하고, 파일의 바디에서는 EL 표현식안에서 속성 값들(태그 파일에 페이지 스코프 변수로 생성된)을 사용해서 질문과 각 답변마다 라디오 버튼이 달린 폼을 생성한다. 폼에서 answers와 votes의 Map 변수 이름과 질문은 hidden으로 인코딩 되어 다음과 같은 형태로 사용될 투표 처리하는 페이지로 보내지게 된다 :

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
   <head>
      <title>Poll Results</title>
   </head>
   <body bgcolor="white">
      <c:set target="${applicationScope[param.votesMapName]}" 
        property="${param.vote}" 
        value="${applicationScope[param.votesMapName][param.vote] + 1}" />
    
      <p>
        Question: ${param.question}<br>
        <c:forEach items="${applicationScope[param.answersMapName]}"
          var="a">
          ${a.key}) ${a.value}: 
          ${applicationScope[param.votesMapName][a.key]}<br>
        </c:forEach>
      </p>
   </body>
</html>

이것은 JSTL 액션을 사용한 평범한 JSP 페이지이다. voteMapName 파라미터에 의해 제공되는 이름으로 어플리케이션 스코프에 존재하는 Map 객체가 투표 결과를 가지고 있는데, 그 객체에 있는 설문조사답변과 키값이 일치하는 변수의 값을 증가시켜준다. 다음으로, 이것은 질문을 출력하고 "answerMapName" 파라미터에 의해 제공되는 어플리케이션 스코프의 설문조사 답변들의 맵 변수를 순회하여 각 답변을 현재 득표수와 함께 출력한다.

우리는 설문조사 투표 처리를 어떻게 하는가 보다는 설문조사 태그 파일을 통해 구현된 커스텀 액션을 어떻게 사용하는지에 더 관심이 있으므로, 여기 예제를 보여준다:

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %>

<html>
   <head>
      <title>My Page</title>
   </head>
   <body bgcolor="white">
      <jsp:useBean id="myAnswers" scope="application" 
        class="java.util.TreeMap">
        <c:set target="${myAnswers}" property="1" value="Yes" />
        <c:set target="${myAnswers}" property="2" value="No" />
        <c:set target="${myAnswers}" property="3" value="Maybe" />
      </jsp:useBean>
      <jsp:useBean id="myVotes" scope="application"
        class="java.util.HashMap" />    
      ...
      <p>
        <my:poll question="Will you start using tag files?" 
          answers="${myAnswers}"
          answersMapName="myAnswers" votesMapName="myVotes" />
      </p>
      ...
   </body>
</html>

주의해서 볼 첫번째 사항은 태그 파일을 가지고 있는 태그 라이브러리를 선언하는 taglib 지시자이다. TLD를 생성하지 않고 태그 파일을 사용하려면, WEB-INF/tags 디렉토리 아래의 어디엔가 태그 파일을 저장해야만 한다.

poll.tag 예제파일은 WEB-INF/tags/mytags 디렉토리에 저장되었고 이 디렉토리 이름을 taglib 지시자의 'tagdir ' 속성의 값으로 사용했다. 이렇게하여 컨테이너에게 이 디렉토리에 있는 모든 태그 파일이 한개의 동일한 태그 라이브러리에 속한다고 선언하고, 이 라이브러리의 액션을 위한 액션 요소들이 taglib 지시자의 prefix 속성에 지정된 값 - 이 예제에서는 "my" - 을 이용해 구분라고 알려준다. 다른 방법으로, 태그 파일을 TLD파일과 함께 .jar 로 묶을 수 있다. 그리고 태그 라이브러리를 일반적인 커스텀 태그 라이브러리와 마찬가지 방식의 taglib 지시자로 선언할 수 있다. 예를들어, tagdir 속성대신 uri 속성을 사용한다.

이 예제에서, 답변과 투표 결과를 위한 Map 객체는 <jsp:useBean> 액션에 의해 생성되고 JSTL의 <c:set> 액션에 의해 값이 증가된다. 하지만, 물론 다른 어떤 방식으로 객체를 생성해도 된다. (예: 아파치의 Struts 에서는 컨텍스트 리스너나 "plugin" 클래스에서). 객체들어 어떻게 생성되었든지 간에, 태그 파일은 일반적인 JSP 커스텀 액션 요소를 통해 수행된다. 나는 answers 속성의 값으로서 answers Map의 값을 계산하기 위해 EL 표현식을 사용했다. 태그 파일 속성은 EL 표현식을 기본적으로 받아들인다. 하지만 attribute 지시자의 rtexprvalue를 이용해 정적인 값만을 받아들이도록 선언할 수도 있다.

이 페이지를 요청하면, JSP 컨테이너는 다른 것들과 마찬가지로 그것을 처리한다. taglib 지시자와 요소의 선행자(prefix)의 도움을 받아 <my:poll> 커스텀 요소의 구현을 적재한다. 컨테이너는 태그 파일을 자기 나름의 방법으로 처리한다. Tomcat 5 컨테이너는 태그 파일을 Java 태그 핸들러 클래스로 바꾸고 컴파일한 뒤 그것을 실행한다.

커스텀 액션의 바디 처리하기

자바로 작성된 태그 핸들러 클래스와 마찬가지로, 태그 파일도 컨테이너에게 커스텀 액션 요소의 바디를 평가하고, 추가적으로 평가 결과를 더 처리하도록 할 수 있다.

액션 요소의 바디에 설문조사에 관한 짧은 설명을 추가할 수 있도록 만들어보자. 다음과 같다 :

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %>
...
<html>
   ...
   <body bgcolor="white">
      ...
      <p>
        <my:poll question="Will you start using tag files?" 
          answers="${myAnswers}"
          answersMapName="myAnswers" votesMapName="myVotes" >
          JSP 2.0 introduces a new way to develop custom action
          tag handlers, called <i>tag files</i>
        </my:poll>
      </p>
      ...
   </body>
</html>

이 예제에서의 바디는 오직 텍스트만을 가지고 있다. 하지만 액션 요소나 EL 표현식이 올 수도 있다. 바디를 평가하고 결과를 출력하기 위해, poll 태그 파일을 다음과 같이 수정할 필요가 있다:

<%@ tag body-content="scriptless" %>
<%@ attribute name="question" required="true" %>
<%@ attribute name="answers" required="true" 
   type="java.lang.Object" %>
<%@ attribute name="votesMapName" required="true" %>
<%@ attribute name="answersMapName" required="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<p>
   <jsp:doBody/>
</p>
Question: ${question}<br>
<form action="result.jsp" target="result">
   <input type="hidden" name="question" value="${question}">
   <input type="hidden" name="votesMapName" value="${votesMapName}">
   <input type="hidden" name="answersMapName" value="${answersMapName}">
   <c:forEach items="${answers}" var="a">
      <input type="radio" name="vote" value="${a.key}">${a.value}<br>
   </c:forEach>
   <input type="submit" value="Vote">
</form>

첫째, 태그 지시자의 body-content 속성의 값을 "scriptless"로 변경한다. 이미 말했다시피, 이렇게 하면 바디는 스크립팅 요소를 제외하고는 어떠한 내용이라도 포함할 수 있게된다. 다음으로, <jsp:doBody>액션을 추가한다. 이를통해 컨테이너가 바디를 평가하고 결과를 출력하게 한다. 다른 방법으로, var 속성을 사용해서 평가 결과를 가져와 다른 처리를 할 수도 있다.

여기서 설명한 기능외에 추가적으로, 태그 파일은 자신을 호출한 파일에 변수를 통해 정보를 돌려줄 수도 있고, 선언되지 않은 속성을 처리하고 단편적인 속성을 가질 수 있다(예: 태그 파일이 액션 요소의 바디를 평가하는 것과 비슷한 방법으로 태그 파일에의해 평가되는 액션 요소와 EL 표현식을 갖고 있는 속성). 이에 대한 모든 사항을 나의 JSP 책 11장에서 볼 수 있다. 온라인에서 샘플로 볼 수 있다.

더 간편해진 Java 태그 핸들러 API

커스텀 액션 태그 핸들러를 태그 파일로 작성할 수 있게 된 것은 특히 많은 양의 HTML을 생성하는 커스텀 액션에서 매우 훌륭한 기능이다. 그러나 몇몇 기능은 단지 JSP 액션과 EL 표현식만으로만 작성하기는 어렵다. 그래서 Java 태그 핸들러 API가 여전히 필요하다. JSP 2.0 이전에는 Java로 태그 핸들러를 작성하는 것이 꽤 까다로왔다. 액션 요소의 바디를 처리하기 위해 컨테이너와 태그 핸들어사이의 복잡한 상호작용이 필요했기 때문이다.

액션 요소 바디에서 Java 스크립팅 요소를 지원하기 위해서 이렇게 복잡하게 되었다. 그러나, 바디가 오직 템플릿 텍스트와 EL 표현식, 액션 요소만 포함하게 된다면, 더욱 단순한 API 디자인이 가능해 진다. 바로 그것이 JSP 2.0에서 이뤄진 것이고, 단순한 태그 핸들러 API(simple tag handler API)라고 불리운다.

좀전에 태그 파일로 구현했던 설문조사 커스텀액션의 Java 태그 핸들러 :

package com.mycompany.mylib;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class PollTag extends SimpleTagSupport {
   private String question;
   private Map answers;
   private String votesMapName;
   private String answersMapName;

   public void setQuestion(String question) {
      this.question = question;
   }

   public void setAnswers(Map answers) {
      this.answers = answers;
   }

   public void setVotesMapName(String votesMapName) {
      this.votesMapName = votesMapName;
   }

   public void setAnswersMapName(String answersMapName) {
      this.answersMapName = answersMapName;
   }

   public void doTag() throws JspException, IOException {
      JspWriter out = getJspContext().getOut();
      JspFragment body = getJspBody();
      if (body != null) {
         out.println("<p>");
         body.invoke(null);
         out.println("</p>");
      }
      out.print("Question:");
      out.print(question);
      out.println("<br>");
      out.println("<form action=\"result.jsp\" target=\"result\">");
      out.print("<input type=\"hidden\" name=\"question\" value=\"");
      out.print(question);
      out.println("\">");
      out.print("<input type=\"hidden\" name=\"votesMapName\" value=\"");
      out.print(votesMapName);
      out.println("\">");
      out.print("<input type=\"hidden\" name=\"answersMapName\" value=\"");
      out.print(answersMapName);
      out.println("\">");
      Iterator i = answers.keySet().iterator();
      while (i.hasNext()) {
         String key = (String) i.next();
         String value = (String) answers.get(key);
         out.print("<input type=\"radio\" name=\"vote\" value=\"");
         out.print(key);
         out.print("\">");
         out.print(value);
         out.println("<br>");
      }
      out.println("<input type=\"submit\" value=\"Vote\">");
      out.println("</form>");
   }
}

단순한 태그는 새로운 javax.servlet.jsp.tagext.SimpleTag 인터페이스를 구현해야 한다. 예제의 태그 핸들러는 모든 메소드의 기본적인 구현을 제공해 주는 javax.servlet.jsp.tagext.SimpleTagSupport 클래스를 상속해서 작성했다. 기존의 태그 핸들러 클래스와 마찬가지로, 각각의 커스텀 액션 속성을 위해 셋터 메쏘드(setter methods)가 필요하다. 하지만 처리 메소드는 오직 doTag() 하나만 구현하면 된다.

예제 태그 핸들러에서는, doTag() 메쏘드가 SimpleTagSupport 클래스에서 상속받은 getJspBody() 메소드를 실행함으로써 액션 요소 바디의 실행 대행자(JspFragment의 인스턴스)를 얻는다. 만약 바디가 존재한다면, 태그 핸들러는 수행 결과를 출력에 추가하라는 의미로 null 값을 인자로주어 invoke() 메쏘드를 실행한다. <jsp:doBody> 액션과 마찬가지로, Writer의 인스턴스를 invoke() 메쏘드의 인자로 넘김으로써 결과를 잡아낼 수 있다. 그러면 doTag() 메쏘드는 태그 파일 구현에서 했던 것 처럼 모든 답변을 위한 라디오 단추를 가진 폼 HTML을 출력한다.

기존의 태그 핸들러가 바디를 처리하기위해 세 개의 메쏘드(doStartTag(), doAfterBody() 그리고 doEndTag(). 각 메소드는 컨테이너가 다음에 뭘 할지를 지정하는 값을 리턴한다)를 호출하는데 반해, 지금은 태그 핸들러를 작동시키기 위해 컨테이너가 호출하는 메소드가 오직 한 개이기 때문에, SimpleTag로 태그 핸들러를 구현하는것이 기존 태그 핸들러 API를 이용하는 것보다 훨씬 쉽다. 게다가 단순한 태그 핸들러의 인스턴스는 결코 재사용되지 않기 때문에, 상태를 재설정하는 것에 대해 걱정 꺼도 된다. 상태 재설정하기는 나의 이전 글, "JSP 1.2: Great News for the JSP Community, Part 2" 에서 설명한 대로 상당히 많은 문제를 야기시키는 것으로 알려졌다.

단순한 태그 핸들러의 TLD 선언은 기존 태그 핸들러와 완전히 동일한 방식으로 한다. 하지만 <body-content> TLD 요소는 JSP가 아닌 값을 가져야만 한다. 스크립팅 요소가 단순한 태그 핸들러로 구현된 커스텀 액션 요소의 바디에서 허락되지 않기 때문이다.

이 사항 이외에는 단순한 태그 핸들러로 구현한 커스텀 액션을 사용한는 것은 기존의 태그 핸들러를 사용하는 것과 전혀 다를 바 없다.

단순한 태그 핸들러와 기존 태그 핸들러 모두 javax.faces.jsp.tagext.DynamicAttributes라 고 불리는 새로운 인터페이스를 구현함으로써 선언되지 않은 속성을 지원할 수 있다. 두 형태 모두를 위한 속성은 다른 액션이나 EL 표현식을 포함하는 속성을 위한 JspFragment 형이 될 수 있고 태그 핸들러에 의해 몇번이라도 평가 될 수 있다. 이러한 기능에 대해 나의 책 JavaServer Pages 제3판에서 읽어 볼 수 있다.

마무리

이번 시리즈에서 나는 JSP 2.0에 추가된 모든 새로운 기능을 보여주었다 : Expression Language, 더 나은 오류 처리, 새로운 설정 옵션, 더 나아진 XML 페이지 형식 그리고 태그 핸들러를 개발하기위한 두가지 새로운 방법. 전부 해서, 이러한 개선 사항들은 JSP 페이지를 더욱 쉽게 이해하고 유지보수 할 수 있도록 해준다.

JSP 2.0의 새로운 기능들을 접해보고 싶다면, Apache Tomcat 5를 사용해보라고 권하고 싶다. 톰캣은 새로운 JSP 스펙을 최초로 구현한 JSP 컨테이너이다. Jakarta Project 사이트에서 구할 수 있다.