본문 바로가기

Dev.../플밍 관련 자료

[펌] Custom Tag Library (1) - Tag Handler Class

Custom Tag Library (1) - Tag Handler Class
  
 

우리는 javabeans를 사용함으로써 어느 정도 복잡한 행동들을 jsp 페이지에서 분리시켜 독립적인 일을 하도록 만드는데 성공했다. 하지만, 아직 부족하다. Bean은 단순히 로직을 처리할 수는 있어도 결정적으로 jsp content를 조작할 수 가 없다. 이것은 부가적으로 다시 scripting 요소들로 하여금 jsp 페이지를 점령하도록 만든다.(없을때 보다는 많이 나아졌지만…) 커스텀 태그는 한번 더 여러분들을 스크립팅 요소들로부터 자유롭게 만들어 준다. 이것은 디자이너와 개발자간의 역할 분담을 점점 더 명확하게 만들어 주게 되는 것이다. 커스텀 태그는 여러분이 마음대로 태그를 정의해서 작동하게 해주는 태그 확장 메커니즘이다. 여러분이 만약 xxx라는 이름의 태그를 사용하고 싶고, 그것이 여러분이 원하는 의도대로 움직이길 바란다면 그렇게 할 수 있다는 말이다.

Custom tag(사용자 정의 태그라고 흔히 번역한다)를 만들고 사용하기 위해서는 세가지 사항이 필수적이다.

  1. Tag Handler Class (태그 핸들러 클래스)
  2. Tag Library Descriptor File (태그 라이브러리 설명자 파일)
  3. taglib 지시자 (JSP 페이지 내에서…)

그럼 이제 어떻게 이 세가지를 구성하는 가에 대해서 살펴보자.

Tag Handler Class (태그 핸들러 클래스)

태그 핸들러 클래스는 태그가 어떤 식으로 작동하라는 것을 정의하는 Class이다. 즉, 자바 클래스라는 것이다. JSP 페이지 내에서 커스텀 태그를 사용하게 되면 태그 라이브러리 설명자 파일에서 어떤 태그 핸들러 클래스가 이 태그를 작동하도록 정의된 것인지를 찾고, 이 핸들러 클래스를 찾아서 JSP 페이지에서 그에 상응하는 행동을 취하게 된다.

태그핸들러 클래스를 만드는 첫번째 규칙은 javax.servlet.jsp.tagext.Tag 인터페이스를 구현(implements)해야 한다는 것이다. 하지만, 실제로 이 인테페이스를 직접 구현하는 일은 없을 것이며 주로 javax.servlet.jsp.tagext.TagSupport 클래스와 javax.servlet.jsp.tagext.BodyTagSupport 클래스를 상속(extends)함으로써 이 인터페이스를 구현하게 된다. TagSupport, BodyTagSupport 클래스가 이미 Tag 인터페이스를 구현했으므로 이 두 클래스의 상속이 곧 Tag 인터페이스의 구현을 의미한다.

이미 우리는 Hello Jsp 예제에서 잠깐 태그 핸들러를 구현한 모습을 보았었다. 일단 무턱대고 어떻게 사용하는지 보도록 하자. 어떻게 이렇게 처리되는지는 천천히 살펴볼 것이니 너무 조급하게 생각하지 않기를 바란다. 아래 소스 5.1은 Hello jsp 예제와 거의 유사한 예이다.

소스 5.1 CustomTagEx.java

package com.boolpae.jsp;import javax.servlet.jsp.*;import javax.servlet.jsp.tagext.*;import java.io.*;

public class CustomTagEx extends TagSupport{ public int doStartTag(){ try{ JspWriter out = pageContext.getOut(); out.print("이 예제는 커스템태그 예입니다."); }catch(IOException e){ System.out.println("Error : "+e); } return SKIP_BODY; }}

소스 5.1은 TagSupport 클래스를 상속해서 만들었다. 태그의 몸체 처리가 필요하지 않은 경우에는 TagSupport 클래스를 상속하면 되고 몸체가 있다면 BodyTagSupport 클래스를 상속하면 된다. 이 두가지 클래스는 javax.servlet.jsp.tagext 패키지에 있는 클래스이다.

따라서 이 패키지를 import 시켰다. 이 패키지 내에는 그 외에도 여러가지 Tag 관련 클래스와 인터페이스들이 들어 있다. 또한 일반적으로 태그 핸들러 클래스를 만들면서 사용하는 일반적인 클래스들이 javax.servlet.jsp 패키지와 java.io 패키지에 들어 있다. 그래서 이 두가지 패키지도 import 했다.

여기서 보면 JspWriter 클래스의 경우 javax.servlet.jsp 패키지 내에 들어 있다. 여러분이 정의할 태그가 몸체도 없고 속성도 없는 태그라면 doStartTag만 오버라이드하면 거의 모든 것이 해결된다. 몸체와 속성이 무엇이냐면
<tag attribute="value">body</tag>라는 태그에서 attribute가 속성, body가 몸체이다.

doStartTag 메쏘드는 태그가 시작되면 어떤 행동을 수행하게 되는 것이다. 그리고, 출력을 위해서라면 메쏘드 내부에 JspWriter 객체를 가지고 있어야 한다. 이 객체는 클라이언트측에 출력을 위한 것으로써 JSP 내장객체인 out의 모태가 되는 java.io.PrintWriter 객체의 특별(?)한 버전이라고 할 수 있다. 예전에 이야기 한 적이 있었는데, PrintWriter의 버퍼링 버전이다. 이 JspWriter 객체는 pageContext의 getOut() 메쏘드를 통해서 얻을 수 있다.
pageContext로부터는 이것 이외에도 request, response, session 등 jsp 내장객체들에 접근할 수가 있다. 내장객체 부분에서도 언급한 적이 있다. getRequest(), getResponse(), getSession() 등의 메쏘드를 사용하면 된다.

JspWriter의 print 메쏘드는 출력과 관련되어 있기 때문에 IOException 예외 처리를 해 주어야 한다. 그래서 위의 예제에서 try~catch 블록으로 싸서 처리해 주었다. 에러 타입을 클라이언트측에 보이고 싶다면 doStartTag 메쏘드에 JspException 예외 처리를 하고 에러가 일어나면 특정 예외를 던지면 된다. 무슨 말이냐면 위의 doStartTag 메쏘드를 아래와 같이 쓰면 된다는 것이다.

public int doStartTag() throws JspException{
try{
JspWriter out = pageContext.getOut();
out.print("이 예제는 커스템태그 예입니다.");
}catch(IOException e){
throw new JspTagException("Error : "+e.getMessage());
}
return SKIP_BODY;
}

SKIP_BODY는 생긴 것 자체가 상수처럼 생긴 것이… 태그 몸체가 없으면 SKIP_BODY를 반환하면 된다. 이렇게 하면 시작 태그와 끝 태그 사이에 모든 것을 무시하고 지나가라는 의미이다. 몸체를 가진 경우에도 SKIP_BODY를 사용하면 몸체를 무시하게 된다. 이에 대해서는 나중에 좀 더 살펴볼 것이다. 그냥 여러분은 몸체가 없다면 SKIP_BODY를 반환해 버리면 된다라고 생각하면 될 것이다.

말이 나왔으니 여기서 정리를 해보자. 무엇을? 이놈의 상수말이다. Tag 인터페이스는 4가지의 상수를 가지고 있다. 그 중 하나가 SKIP_BODY이고 나머지는 EVAL_BODY_INCLUDE, SKIP_PAGE, EVAL_PAGE 이다.

EVAL_BODY_INCLUDE 는 SKIP_BODY와 반대라고 생각하면 된다. 몸체 처리를 수행하라는 말이다. SKIP_PAGE와 EVAL_PAGE는 doEndTag의 리턴값이다. 위의 예에서는 doEndTag 메쏘드가 나오지 않았는데, tag의 끝 태그(닫는 태그)가 나오면 doEndTag 메쏘드가 수행되고 두가지 중 하나의 상수를 반환하게 된다.
SKIP_PAGE는 눈치채셨는지 모르겠지만, 끝 태그 이후의 페이지에 나오는 모든 것들을 무시하라는 것이며, EVAL_PAGE는 계속 처리를 수행하라는 의미이다.

눈에 보기 쉽게 표로 만들어 보았다.

상 수소 속의 미

SKIP_BODY
EVAL_BODY_INCLUDE

SKIP_PAGE
EVAL_PAGE

doStartTag
doStartTag

doEndTag
doEndTag

태그 몸체 처리를 하지말라
몸체를 처리하라

이후 페이지 처리를 중지하라
계속해서 페이지를 처리하라

한가지 더 덧붙이자면 BodyTagSupport 에는 EVAL_BODY_TAG라는 상수가 하나 더 정의되어 있는데 이 클래스에는 doAfterBody라는 메쏘드가 있다. 여기서 반환하는 상수인데, 이에 대해서는 나중에 몸체가 있는 태그를 설명할 때 다시 살펴볼 것이다.

이 태그 핸들러 클래스도 여러분의 jsp 컨테이너의 웹어플리케이션 루트 디렉토리 아래의 WEB-INF/classes 폴더 밑에 두면 된다. 자바빈 클래스를 두는 방법과 동일하다. 유일무이한 클래스를 만들기 위한 메커니즘인 패키지 생성을 이용해서 항상 패키지를 만들고 그 내부에 클래스를 두기 바란다.