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 이후
심플태그
초간단 심플 태그 핸들러 구현중..
- SimpleTagSupport를 상속받아 클래스를 작성 합니다.
package foo;
import javax.servlet.jsp.tagext.SimpleTagSupport;
…
public class SimpleTagTest1 extends SimpleTagSupport {
// 태그 핸들러 코드 삽입
}
|
- doTag() 메소드를 구현 합니다.
public void doTag() throws JspException, IOException {
getJspContext().getOut().print(“이것은 컴스텀 SimpleTag입니다.”);
}
|
- 태그를 위해서 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>
|
- 태그 핸들러와 TLD를 배포 합니다.
- WEB-INF 디렉토리나 WEB-INF의 하위 디렉토리에 TLD 파일 복사
- 태그 핸들러 class 파일은 WEB-INF/classes 디렉토리에 패키지 구조에 맞게 복사
- 태그를 사용할 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 상속 받으면 거의 모든 구현이 가능 하다.
심플 태그 핸들러 일생
- 웝 컨데이너가 심플 태그 클래스 로딩함.
- 클래스 인스턴스화 함. (인자 없는 디폴트 생성자 겠죠)
- setJspContext(JspContext) 메소드 호출 : 단 empty 이외값이 설정되고 몸체를 가지고 있는 경우
- 다른 태크 내부에서 태그를 호출 했으면 (내장 태그라고 함.), setParent(JspTag) 메소드 호출
- 속성이 있다면, 속성 설정자(setter)를 호출
- (body-content)값이 “empty”가 아니고 몸체가 있다면 setJspBody(JspFragment) 메소드 호출
- 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; // 이 페이지 뒷 부분을 실행하라는 뜻
}
}
|
클래식 태그 생명주기
- 웝 컨데이너가 심플 태그 클래스 로딩함.
- 클래스 인스턴스화 함. (인자 없는 디폴트 생성자 겠죠)
- setPageContext(PageContext) 메소드 호출
- 다른 태크 내부에서 태그를 호출 했으면 (내장 태그라고 함.), setParent(Tag) 메소드 호출
- 속성이 있다면, 속성 설정자(setter)를 호출
- doStartTag() 메소드 호출
- 태그 몸체가 empty가 아니고 태그 몸체가 있을 때,doStartTag() 메소드가 EVAL_BODY_INCLUDE를 리턴할 때
- 몸체를 실행했다면 doAfterBody() 메소드 호출
- 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;
}
|
**심플 태그가 부모로 클래식 태그를 가질 수 있습니다.
부모태그가 자식 태그 정보 읽기
필요한 경우
- 자식 태그가 부모 태그 정보를 읽고자 할 때
- getParent() 이용
- 부모 태그가 자식 태그 정보를 읽고자 할 때
- 부모 태그에 참조에 자신을 등록하는 방법
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를 리턴
|
댓글 없음:
댓글 쓰기