2013년 9월 10일 화요일

[Head First]Servlets & JSP 내용정리-10장

Chapter.10 사용자 정의 태그 개발(JSTL만으로 만족스럽지 않을 때)

태그 파일 : include 처럼 행동하지만, 좀 더 개선된 방식
  • 태크 파일을 그냥 가벼운 태그 핸들러 “태크 핸들러 라이트”라고 부른다.
  • 복잡한 자바 태그 핸들러 클래스를 작성할 필요없음.
  • 초 간단 태그 파일 사용 방법
    • 포함할 파일(Header.jsp)을 복사해서 확장자를 .tag로 바꿉니다.
    • “WEB-INF” 밑에 “tags” 디렉토리를 새로 만들고 여기에 태크 파일(“Header.tag”)을 옮깁니다.
    • 태그 파일을 호출할 JSP를 만들고 taglib 지시자(tagdir 속성 포함)를 아래와 같이 작성함.
      <%@ taglib prefix=”myTags” tagdir=”/WEB-INF/tags” %>
    • 사용예) <jsp:include page=”Header.jsp”/> ==> <myTags:Header/>
  • 파라미터를 보내는 방법 : 태그 속성으로 정보를 넘깁니다.

이전 방식
Header.jsp : ${param.subTitle}
호출하는 페이지
<jsp:include page=”Header.jsp”>
 <jsp:param name=”subTitle” value=”we message param”/>
</jsp:include>
태그 파일
Header.tag : ${subTitle}
호출하는 페이지
<myTags:Header subTitle=”we message param”/>

  • 태그 파일에서는 attribute 지시자를 사용하여 속성을 정의 한다. 즉 TLD를 사용하지 않는다.

Header.tag
<%@ attribute name=”subTtitle” required=”true” rtexprvalue=”true” %>
…${subTtitle}...

  • body로 정보를 넘겨주는 방법 및 정의

Header.tag
<%@ tag body-content=”tagdependent” %>
       ---------------------------
태그 몸체에 스크립팅을 사용할 수 없음. 기존값은 scriptless 이고
empty 나 tagdependent가 올 수 있다.
…<jsp:doBody />...
---------------
호출 태그의 몸체 내용을 표시하라는 뜻

호출하는 페이지
<myTags:Header>
이것은 연습니다....
이것도 연습니다....
</myTags:Header>

  • 컨테이너가 태그 파일 찾는 곳
    • WEB-INF/tags 바로 밑
    • WEB-INF/tags 아래 하위 디렉토리 안
    • WEB-INF/lib 밑 JAR 파일로 배포 했다면 JAR 안 META-INF/tags 디렉토리
    • WEB-INF/lib 밑 JAR 파일로 배포 했다면 JAR 안 META-INF/tags 아래 하위 디렉토리
    • 태그 파일이 JAR로 배포 되었다면 반드시 TLD 파일이 있어야 한다.
  • 태그 파일에 추가적인 내용
    • response , request 및 내장 객체 사용가능 그리고 JspContext(ServletContext 대신)를 사용가능
    • 태그 파일용 TLD

<taglib …>
 <tlib-version>1.0</tlib-version>
 <uri>myTagLibrary</uri>
 <tag-file>
   <name>Header</name>
   <path>/META-INF/tags/Header.tag</path>
 </tag-file>
</taglib>

커스텀 태그 핸들러를 사용를 사용하는 경우 :: 아주 잘 만들어진 자바 코드를 스크립틀릿으로 이를 호출하지 않고 사용하고 싶을 때..
컴스텀 태그의 두 가지 형식
  • Classic (클래식) : JSP 2.0 이전 형식 , 이전에 개발 것은 유지 보수를 위해서
  • Simple (심플) : JSP 2.0 이후

심플태그


초간단 심플 태그 핸들러 구현중..
  1. SimpleTagSupport를 상속받아 클래스를 작성 합니다.

package foo;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class SimpleTagTest1 extends SimpleTagSupport {
 // 태그 핸들러 코드 삽입
}

  1. doTag() 메소드를 구현 합니다.

public void doTag() throws JspException, IOException {
 getJspContext().getOut().print(“이것은 컴스텀 SimpleTag입니다.”);
}

  1. 태그를 위해서 TLD를 작성 함.

<taglib …>
 <tlib-version>1.2</tlib-version>
 <uri>simpleTags</uri>
 <tag>
   <description>This is Simple Tag Example</description>
   <name>simple1</name>
   <tag-class>foo.SimpleTagTest1</tag-class>
   <body-content>empty</body-content>
 </tag>
</taglib>

  1. 태그 핸들러와 TLD를 배포 합니다.
    • WEB-INF 디렉토리나 WEB-INF의 하위 디렉토리에 TLD 파일 복사
    • 태그 핸들러 class 파일은 WEB-INF/classes 디렉토리에 패키지 구조에 맞게 복사
  2. 태그를 사용할 JSP를 작성함.

<%@ taglib prefix=”myTags” uri=”simpleTags” %>
<html><body>
<myTags:simple1 />
</body></html>

몸체가 있는 심플 태그
  • 태그를 사용할 JSP

<myTags:simple2>
This is the body
</myTags:simple2>

  • 태그 핸들러 클래스

public void doTag() throws JspException,IOException{
 getJspBody().invoke(null);
// 태크 몸체를 읽은 다음 응답(Response)에 출력해주세요. 인자가 널이라는 것은 다른 Writer로 말고 Response로 출력하는 의미
}

  • TLD 파일

<tag>
 <body-content>scriptless</body-content>
               ----------
 몸체는 가질 수 있지만 스크립팅은 안돼요라는 의미
</tag>

심플 태그 API : SimpleTagSupport 상속 받으면 거의 모든 구현이 가능 하다.
심플 태그 핸들러 일생
  1. 웝 컨데이너가 심플 태그 클래스 로딩함.
  2. 클래스 인스턴스화 함. (인자 없는 디폴트 생성자 겠죠)
  3. setJspContext(JspContext) 메소드 호출 : 단 empty 이외값이 설정되고 몸체를 가지고 있는 경우
  4. 다른 태크 내부에서 태그를 호출 했으면 (내장 태그라고 함.), setParent(JspTag) 메소드 호출
  5. 속성이 있다면, 속성 설정자(setter)를 호출
  6. (body-content)값이 “empty”가 아니고 몸체가 있다면 setJspBody(JspFragment) 메소드 호출
  7. doTag() 메소드 호출

태그 몸체에 표현식이 있다면
JSP에서 태그 호출
<myTags:simple3>
 Message is : ${message}
</myTags:simple3>

태그 핸들러 doTag() 메소드
public void doTag() throws ..Exception{
 getJspContext().setAttribute(“message”,”이것은 메시지요..”);
 getJspBody().invoke(null);
}

동적으로 데이블 행을 출력하는 태그 : 몸체 루핑
JSP에서 태그 호출
<table>
<myTags:simple4>
 <tr><td>${movie}</td></tr>
</myTags:simple4>
</table>
태그 핸들러 doTag() 메소드
String[] movies = {“Superman”,”kill bill”,”spider man”};
public void doTag() throws ..Exception{
 for(int i = 0 ; i < movies.length;i++){
   getJspContext().setAttribute(“movie”,movies[i]);
   getJspBody().invoke(null);
 }
}

태그 속성으로 예제를 수정하기
JSP에서 태그 호출
<table>
 <myTags:simple5 movieList=”${movieCollection}”>
   <tr>
     <td>${movie.name}</td>
     <td>${movie.genre}</td>
   </tr>
 </myTags:simple5>
</table>
태그 핸들러 doTag() 메소드
public class SimpleTagTest5 extends SimpleTagSupport{
private List movieList;
public void setMovieList(List movieList){
  this.movieList = movieList;
}
public void doTag() throws ..Exception{
 Iterator i = movieList.iterator();
 while(i.hasNext()){
  Movie movie = (Movie)i.next();
  getJspContext().setAttribute(“movie”,movie);
  getJspBody().invoke(null);
 }
}
}
TLD 파일
<tag>
 <description> 속성을 사용하여 body 내용을 반복적으로 채우기</description>
 <name>simple5</name>
 <tag-class>foo.SimpleTagTest5</tag-class>
 <body-content>scriptless</body-content>
 <attribute>
   <name>movieList</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
 </attribute>
</tag>

JspFragmenet가 정확히 뭐죠?
  • 설명 : JSP 코드를 나타내는 객체,호출하면 실행한 뒤 출력을 만들어냄.
  • 예 : 심플 태그 핸들러가 호출하는 태그 몸체가 대표적 인데, 몸체 내용을 JspFragment 객체로 갭슐화
    하고 setJspBody() 메소드로 설정하여 내부에서 사용합니다.
  • 기억해야 할 사항 : 스크립팅(스크립틀릿,선언문,스크립팅 표현식)을 사용할 수 없다. 템플릿 텍스트, 표준 액션, 컴스텀 액션, EL 표현식은 가능함
  • 객체 장점을 가지고 있음.(Helper 객체로 넘길수 있음, 필요한 정보를 얻을 수도 있음(getJspContext()도 사용가능).)
  • 작업 예 : 응답에 태그 몸체 내용을 출력하는 것 , 만약 몸체 내용을 새로 작성하려면 java.io.Writer를 넘겨 invoke()메소드를 호출한 뒤, 필요한 작업을 하면됩니다.

SkipPageException : 페이지 작업 중지
  • 오류가 발생하더라도 지금까지 처리된 내용을 그대로 나오게 할 수 있다.
  • 단 만약 다른 페이지가 삽입된다면 태그를 직접 호출한 페이지만 작업 중지 합니다.

태그 핸들러 doTag() 메소드
public void doTag() throws …{
 getJspContext().getOut().print(“Message from within doTag().<br/>”);
 if(thingsDonWork){
   throw new SkipPageException();
 }
}
태그를 호출하는 JSP
<%@ taglib prefix=”myTags” uri=”SimpleTags” %>
<html><body>
여기서 SkipPage Exception이 발생한다.<br/>
<myTags:simple6/>
여기 부터는 출력되지 않아요..
</body></html>

클래식 태그 핸들러

** 클래식 태그 핸들러는 재활용품입니다. 주의 하세요
태그 핸들러 API
초 간단 클래식 태그 핸들러
클래식 태그를 호출하는 JSP
<%@ taglib prefix=”mine” uri=”KathyClassicTags” %>
<html><body>
Classic Tag one:<br/> <mine:classicOne />
</body></html>
클래식 태그용 TLD <tag> 항목
<tag>
 <description>Classic Tag</description>
 <name>classicOne</name>
 <tag-class>foo.Classic1</tag-class>
 <body-content>empty</body-content>
</tag>
클래식 태그 핸들러
package foo;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class Classic1 extends TagSupport{
 public int doStartTag() throws JspException{
   JspWriter out = pageContext.getOut();
   ------------------------------------
   pageContext 를 사용합니다.
   try{
     out.println(“classic tag output”);
   }catch(java.io.IOException ex){
     throw new JspException(“IOException- “+ex.toString());
   }
   return SKIP_BODY;    
 }
}

클래식 태그 핸들러, 이제 메소드 2개를 재정의 합니다.
클래식 태그를 호출하는 JSP
<%@ taglib prefix=”mine” uri=”KathyClassicTags” %>
<html><body>
Classic Tag two:<br/> <mine:classicTwo />
</body></html>
클래식 태그 핸들러
public class Classic2 extends TagSupport{
 JspWriter out;
 public int doStartTag() throws JspException{
   out = pageContext.getOut();
   try{
     out.println(“in doStartTag()”);
   }catch(java.io.IOException ex){
     throw new JspException(“IOException- “+ex.toString());
   }
   return SKIP_BODY;   // 몸체가 있더라도 실행하지 말라는 뜻
 }
 
 public int doEndTag() throws JspException{
   try{
     out.println(“in doEndTag()”);
   }catch(java.io.IOException ex){
     throw new JspException(“IOException- “+ex.toString());
   }
   return EVAL_PAGE; // 이 페이지 뒷 부분을 실행하라는 뜻    
 }
}

클래식 태그 몸체 처리
클래식 태그를 호출하는 JSP
<%@ taglib prefix=”mine” uri=”KathyClassicTags” %>
<html><body>
 <mine:classicBody>
   This is the body
 </mine:classicBody>
</body></html>
클래식 태그 핸들러
public class Classic2 extends TagSupport{
 JspWriter out;
 public int doStartTag() throws JspException{
   out = pageContext.getOut();
   try{
     out.println(“Before body”);
   }catch(java.io.IOException ex){
     throw new JspException(“IOException- “+ex.toString());
   }
   return EVAL_BODY_INCLUDE;   // 몸체를 실행하는 뜻
 }
 
 public int doEndTag() throws JspException{
   try{
     out.println(“After body”);
   }catch(java.io.IOException ex){
     throw new JspException(“IOException- “+ex.toString());
   }
   return EVAL_PAGE; // 이 페이지 뒷 부분을 실행하라는 뜻    
 }
}

클래식 태그 생명주기
  1. 웝 컨데이너가 심플 태그 클래스 로딩함.
  2. 클래스 인스턴스화 함. (인자 없는 디폴트 생성자 겠죠)
  3. setPageContext(PageContext) 메소드 호출
  4. 다른 태크 내부에서 태그를 호출 했으면 (내장 태그라고 함.), setParent(Tag) 메소드 호출
  5. 속성이 있다면, 속성 설정자(setter)를 호출
  6. doStartTag() 메소드 호출
  7. 태그 몸체가 empty가 아니고 태그 몸체가 있을 때,doStartTag() 메소드가 EVAL_BODY_INCLUDE를 리턴할 때
  8. 몸체를 실행했다면 doAfterBody() 메소드 호출
  9. doEndTag() 메소드 호출

클래식 태그 생명주기는 리턴값에 따라 달라집니다.
TagSupport를 상속받는 경우 사용 가능한 리턴값
  • doStartTag()
    • SKIP_BODY
    • EVAL_BODY_INCLUDE
  • doAfterBody()
    • SKIP_BODY
    • EVAL_BODY_AGAIN
  • doEndTag()
    • SKIP_PAGE
    • EVAL_PAGE

IterationTag에 몸체를 반복할 수 있는 기능이 들어 있음.

태그 핸들러 클래스
public class MyIteratorTag extends TagSupport{
 String[] movies = {“SpiderMan”,”Saved!”,”Aemlie”};
 int movieCounter;
 public int doStartTag() throws JspException{
   movieCounter = 0;
   pageContext.setAttribute(“movie”,movies[moviceCounter++]);
   return EVAL_BODY_INCLUDE;
 }
 // doAfterBody()는 몸체를 한번 실행하고 나서 실행 하므로 위 doStartTag()에서
 // 선행 처리가 필요하다.
 public int doAfterBody() throws JspException{
   if(movieCounter < movies.length){
     pageContext.setAttribute(“movie”,movies[movieCounter++]);
     return EVAL_BODY_AGAIN;
   }else return SKIP_BODY;
 }
 public int doEndTag() throws JspException{
   return EVAL_PAGE;
 }  
}
태그를 호출하는 JSP
<%@ taglib prefix=”mine” uri=”KathyClassicTags” %>
<html><body>
 <table boder=”1”>
   <mine:iterateMovies><tr><td>${movie}</td></tr></mine:iterateMovies>
 </table>
</body></html>

클래식 태그 : 태그 몸체 내용을 어떻게 해보고 싶을 때
  • 몸체에 표현식이나 필터를 넣는다는 든지 , 내용을 수정한다는 든지 하는 작업을 하고 싶을 때
  • setBodyContent() , doInitBody() : 이용하면 가능

BodyTag를 구현한 태그 생명주기
  • doStartTag() 디폴트 리턴값이 변경됨.
    • SKIP_BODY
    • EVAL_BODY_INCLUDE
    • EVAL_BODY_BUFFERED(기본값)
BodyTag는 몸체 내용을 버퍼링할 수 있음.
  • setBodyContent()의 인자인 BodyContent는 java.io.Writer 입니다. 몸체 내용을 다른 IO 스트림이나 로우 바이트를 연결해서 작업을 할 수 있다는 의미
  • 태그의 몸체가 없다면 EVAL_BODY_BUFFERED를 호출해도 setBodyContent(),doInitBody()가 호출되지 않음. 그리고 만약 TLD에 empty라고 선언되어 있다면 EVAL_BODY_INCLUDE 나 EVAL_BODY_BUFFERED를 호출 할 수 없습니다.받드시 SKIP_BODY를 호출 해야 합니다.

클래식 태그 생명주기 메소드
생명주기
메소드
BodyTagSupport
TagSupport
doStartTag()
가능한 리턴값
SKIP_BODY
EVAL_BODY_INCLUDE
EVAL_BODY_BUFFERED
SKIP_BODY
EVAL_BODY_INCLUDE
구현 클래스별 디폴트 리턴값
EVAL_BODY_BUFFERED
SKIP_BODY
호출되는 회수
단 한번만
단 한번만
doAfterBody()
가능한 리턴값
SKIP_BODY
EVAL_BODY_AGAIN
SKIP_BODY
EVAL_BODY_AGAIN
구현 클래스별 디폴트 리턴값
SKIP_BODY
SKIP_BODY
호출되는 회수
0 .. *
0 .. *
doEndTag()
가능한 리턴값
SKIP_PAGE
EVAL_PAGE
SKIP_PAGE
EVAL_PAGE
구현 클래스별 디포트 리턴값
EVAL_PAGE
EVAL_PAGE
호출되는 회수
단 한번만
단 한번만
doInitBody()와
setBodyContent()
어떤 경우 호출되는지와 호출되는 회수
doStartTag()가
EVAL_BODY_BUFFERED를 리턴할 때
딱 한번 호출 됨.
절대 호출 않됨.

태그와 태그 협업해야 할 때
  • 상호 정보를 교환 이 가능 하다.

태그는 자신의 부모 태그를 호출할 수 있다.
  • 각각 getParent() 메소드를 사용.

내장 태그는 부모 태그에 접근 할 수 있습니다.
<mine:OuterTag>
 <mine:InnerTag>
</mine:OuterTag>
클래식 태그 핸들러인 경우 부모 태그에 접근하기
public int doStartTag() throws JspException{
 OuterTag parent = (OuterTag) getParent();
 // 필요한 코딩을 합니다.
 return EVAL_BODY_INCLUDE;
}
심플 태그 핸들러인 경우 부모 태그에 접근하기
public void doTag() throws JspException,IOException {
 OuterTag parent = (OuterTag) getParent();
 // 필요한 코딩을 합니다.
}

얼마나 깊이 들어갔나 알아보기
  • ** getParent() 태그가 더 이상 존재하지 않으면 null 리턴 함을 이용
  • 추후 심플태그에서 가능한 방법을 알아보자

JSP 코드
<mine:NestedLevel>
 <mine:NestedLevel>
   <mine:NestedLevel/>
 <mine:NestedLevel>
</mine:NestedLevel>
클래식 태그 핸들러
private int nestLevel = 0;
public int doStartTag() throws JspException{
 nestLevel = 0;
 Tag parent = getParent();
 while(parent != null){
   parent = parent.getParent();
   nestLevel++;
 }
 //출력 함.
 return EVAL_BODY_INCLUDE;
}

**심플 태그가 부모로 클래식 태그를 가질 수 있습니다.

부모태그가 자식 태그 정보 읽기
필요한 경우
  1. 자식 태그가 부모 태그 정보를 읽고자 할 때
    • getParent() 이용
  2. 부모 태그가 자식 태그 정보를 읽고자 할 때
    • 부모 태그에 참조에 자신을 등록하는 방법

JSP 코드
<mine:Menu>
 <mine:MenuItem itemValue=”Dogs”/>
 <mine:MenuItem itemValue=”Cats”/>
</mine:Menu>
자식 태그 핸들러 : MenuItem
public class MenuItem extends TagSupport{
 private String itemValue;
 public void setItemValue(String value){itemValue = value;}
 public int doStartTag() throws JspException{
   return EVAL_BODY_INCLUDE;
 }
 public int doEndTag() throws JspException{
   Menu parent = (Menu) getParent();
   parent.addMenuItem(itemValue);
   return EVAL_PAGE;
 }
}
부모 태그 핸들러 : Menu
public class Menu extends TagSupport{
 private ArrayList items;
 public void addMenuItem(String item){ items.add(item);}
 public int doStartTag() throws JspException{
   item = new ArrayList();
   return EVAL_BODY_INCLUDE;
 }
 public int doEndTag() throws JspException{
   // 출력 items 이름
   // 복잡한 메뉴를 만드는 코드는 여기 들어 감.
   return EVAL_PAGE;
 ]
}

몇 대 위의 조상이라도 찾기
  • findAncestorWithClass() 메소드를 이용함.
  • 예) WayOuterTag ancestor = (WayOuterTag)findAncestorWithClass(this,WayOuterTag.class);
    • this --> 시작 태그
    • WayOuterTag.class --> 찾고자하는 태그 핸들러 클래스

심플 태그 와 클래식 태그와 주요 차이점
항목
심플 태그
클래식 태그
Tag 인터페이스
SimpleTag(JspTag 상속)
Tag(JspTag 상속)
IterationTag(Tag 상속)
BodyTag(IterationTag 상속)
지원(Support)
클래스
SimpleTagSupport(SimpleTag 구현)
TagSupport(IterationTag 구현)
BodyTagSupport(TagSupport 상속,BodyTag 구현)
구현 해야 하는 주요 생명 주기 메소드
doTag()
doStartTag()
doEndTag()
doAfterBody()
(BodyTag인 경우
 doInitBody(), setBodyContent())
쓰기 작업을 할 응답 출력을 리턴 받으려면, 사용법

getJspContext().getOut().println
pageContext.getOut().println
지원 클래스에서 내장 객체, 생존 범위에 묶여 있는 속성에 접근 하려면
getJspContext() 메소드로 JspContext 참조를 얻어서
내장변수 pageContext로
결과적으로 몸체가 실행되게 하려면
getJspBody().invoke(null)
doStartTag()에서 EVAL_BODY_INCLUDE를 리턴하거나
BodyTag를 구현한 경우 EVAL_BODY_BUFFERED를 리턴하는 경우
현재 페이지 실행을 중지 하려면
SkipPageException을 던짐
doEndTag()에서 SKIP_PAGE를 리턴

댓글 없음: