달력

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

태그 기능을 사용자 정의 애트리뷰트로 확장하기

developerWorks

 


난이도 : 초급

Brett McLaughlin, Author, O'Reilly and Associates

2003 년 8 월 05 일

커스텀 타임-스탬프를 확장하여 페이지 작성자가 자신의 타임-스탬프 포맷을 선택할 수 있도록 하는 방법을 설명한다.

지난 시간에는 JSP 페이지에서 커스텀 태그 라이브러리의 기본적인 사용 방법을 설명했다. 간단한 태그를 정의하고 태그 라이브러리 디스크립터(TLD)를 통해 다른 JSP 작성자까지 사용 할 수 있도록 만들었다. 이번 주에는 커스텀 태그에 대해 알고 있는 지식에 기반하여 구현을 할 것이다. 여기에서 사용할 예제 태그는 매우 간단하다. 따라서 커스텀 애트리뷰트를 결합하여 기능을 확장할 것이다.

예제에 대하여: 이 글에 사용된 모든 예제 코드는 지난 번 우리가 개발한 lastModified 태그에 기반하여 구현된다.

"Hello, world" 커스터마이징

JSP 태그의 가장 일반적인 요구사항은 페이지로부터 오는 데이터를 받아들이고 그 데이터에 응답할 수 있어야 한다는 것이다. 태그 애트리뷰트로 이 기능을 우리의 커스텀 태그에 결합할 수 있다.

간단한 예제로 "Hello, world" 애플리케이션을 들어보겠다. 이 스크립틀릿의 기능을 구현하는 커스텀 태그를 상상하는 것은 쉽지만 이를 어떻게 확장할 것인가?

Listing 1은 전형적인 "Hello, world!"를 결합한 JSP 페이지이다. name이라고 하는 애트리뷰트를 포함하고 있는 태그이다:


Listing 1. "Hello, world!" 태그

<p>
    <examples:hello name="Reader" />
</p>


name 애트리뷰트는 페이지 작성자가 hello 태그에 데이터를 공급할 수 있는 곳에 공간을 만든다. 이 경우 사람의 이름은 애플리케이션이 보내는 메시지를 받을 사람이다. Listing 2는 hello 태그를 구현하는 자바 코드이다:


Listing 2. hello 태그용 코드

package com.ibm.examples;

import java.io.IOException;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class HelloTag extends TagSupport {

    // The "person" to say hello to
    private String name;

    // Accept the attribute data
    public void setName(String name) {
      this.name = name;
    }

    public int doEndTag() {
      try {
	  StringBuffer message = new StringBuffer("Hello, ");
	  message.append(name)
	         .append("!");
	  pageContext.getOut().println(message.toString());
	} catch (IOException ignored) { }
	return EVAL_PAGE;
    }


Listing 2의 코드는 매우 단순하다. setXXX() 메소드를 추가했다. XXX에는 애트리뷰트 이름이 들어간다. 보기에는 간단하지만 태그의 기능을 크게 확장한 것이다. 페이지 작성자는 특정 목적을 위해 커스텀 데이터를 설정할 수 있고 그 데이터는 저장, 조작, 활성화 될 수 있다. doEndTag() 메소드로는 필요한 어떤 방식으로든 태그 데이터를 사용할 수 있도록 한다.

lastModified 태그에 애트리뷰트를 추가할 때 어떻게 되는지 검토해보자.




위로


lastModified 확장하기

페이지 작성자에게 단지 하나의 디스플레이 옵션을 주는 대신 원하는 대로 아웃풋 포맷팅을 설정할 수 있도록 하고 싶다. lastModifiedTag 클래스가 아웃풋 포맷팅을 위해 java.text.SimpleDateFormat을 포함하도록 확장시켜서 백엔드에서 시작하겠다. (Listing 3):


Listing 3. LastModifiedTag 클래스에서 SimpleDateFormat 사용하기

package com.newInstance.site.tags;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.TagSupport;

public class LastModifiedTag extends TagSupport {

    private String format = "MMM d, yyyy";

    public int doEndTag() {
      try {
        HttpServletRequest request =
          (HttpServletRequest)pageContext.getRequest();
        String path = pageContext.getServletContext().getRealPath(
          request.getServletPath());
        File file = new File(path);

        DateFormat formatter = new SimpleDateFormat(format);

        pageContext.getOut().println(
          formatter.format(new Date(file.lastModified())));
      } catch (IOException ignored) { }
      return EVAL_PAGE;
    }
}


Listing 4에서는 새로운 포맷팅 기능을 태그로 가져왔다. format 애트리뷰트 값은 Listing 3에 적용된 새로운 format 메소드 변수에 어태치된다.


Listing 4. 새로운 애트리뷰트 핸들링하기

package com.newInstance.site.tags;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.TagSupport;

public class LastModifiedTag extends TagSupport {

    private String format = "MMM d, yyyy";

    public void setFormat(String format) {
      this.format = format;
    }

    public int doEndTag() {
      try {
        HttpServletRequest request = 
          (HttpServletRequest)pageContext.getRequest();
        String path = pageContext.getServletContext().getRealPath(
          request.getServletPath());
        File file = new File(path);

        DateFormat formatter = new SimpleDateFormat(format);

        pageContext.getOut().println(
          formatter.format(new Date(file.lastModified())));
      } catch (IOException ignored) { }
      return EVAL_PAGE;
    }
}





위로


구현 상세

format 애트리뷰트로 페이지 작성자는 날짜/시간 아웃풋 포맷을 원하는 대로 설정할 수 있다. 하지만 이 새로운 애트리뷰트를 사용하기 전에 TLD 파일을 약간 변경해야한다. (Listing 5):


Listing 5. TLD 수정하기

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE taglib
    PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2/EN"
           "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd" >

<taglib>
    <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>site-utils</short-name>
    <uri>http://www.newInstance.com/taglibs/site-utils</uri>

    <tag>
      <name>lastModified</name>
      <tag-class>com.newInstance.site.tags.LastModifiedTag</tag-class>
      <body-content>empty</body-content>
      <attribute>
        <name>format</name>
      </attribute>
    </tag>
</taglib>





위로


디폴트 값

TLD를 업데이트 할 때, 새로운 애트리뷰트를 사용해야만 한다. 테스트도 필수이다. 서블릿 컨테이너를 재시작하여 이것이 새로운 태그와 TLD 변화를 감지했는지를 확인하고 여기에 lastModified 태그로 페이지를 작동시킨다.

당연히 타임 스탬프가 나타난다. 만일 여러분이 내가 보고있는 것을 보고있다면 아웃풋의 포맷팅은 전과 같다. 문제는 어떤 새로운 format 값도 추가되지 않았다는 점이다. 따라서 예전과 같은 것을 보고있는 것이 된다. 이 작은 테스트 실행으로 format 애트리뷰트에 디폴트 값을 추가하는 것이 얼마나 중요한일인지 알게된다.

커스텀 애트리뷰트에 디폴트 값을 주는 것은 좋은 생각이다. 왜냐하면 페이지 작성자가 고유의 값을 제공하고 싶지 않을 때 곤경에 처하지 않기 때문이다. 때로는 새로운 애트리뷰트와 포맷을 배우는데 시간이 필요하고 그 사이 디폴트는 훌륭한 대리인이 되는 것이다.




위로


맺음말

Listing 6 은 타임 스탬프 예제("JSP best practices : 타임 스탬프의 힘" 참조)의 footer.jsp 이다. format 애트리뷰트에 값이 제공되었다:


Listing 6. 포맷 애트리뷰트 사용하기

<%@ taglib prefix="site-utils"
             uri="http://www.newInstance.com/taglibs/site-utils"%>

          </td>
          <td width="16" align="left" valign="top"> </td>
    </tr>
    <!-- End main content -->

<!-- Begin footer section -->
    <tr>
      <td width="91" align="left" valign="top" bgcolor="#330066"> </td>
      <td align="left" valign="top"> </td>
      <td class="footer" align="left" valign="top"><div align="center"><br>
          &copy; 2003 
          <a href="mailto:webmaster@newInstance.com">Brett McLaughlin</a><br>
          Last Updated: <site-utils:lastModified 
            format="HH:mm a zz :: MM/dd/yyyy"/>
        </div></td>
          <td align="left" valign="top"> </td>
      <td width="141" align="right" valign="top" bgcolor="#330066"> </td>
    </tr>
</table>
<!-- End footer section -->





위로


참고자료




위로


필자소개

Brett McLaughlin은 현재 자바 및 관련 기술을 이용하여 애플리케이션 기반구조 구현을 전문적으로 수행하고 있다. Brett은 Java Apache 프로젝트인 Turbine의 공동 창립자이다

:
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 뽀기