S-JIS[2005-08-07/2009-12-05] 変更履歴

JSPのtaglib作成

JSPの独自タグ(カスタムタグ)を作るには、taglib(タグライブラリー/タグリブ)を作成することになる。

ここにメモしてある方法とは別に、タグファイルというものを使う新しい方法もある。[2007-06-17]


taglibの使用法

jspファイル

独自タグを組み込んだjspは、以下のような感じになる。[/2007-07-15]

taglib_test.jsp

<%@ page language="java" contextType="text/html;charset=Shift_JIS" %>
<%@ taglib uri="/WEB-INF/sample.tld" prefix="sample" %>	←使用する独自タグの宣言

<html>
<head>〜</head>
<body>
	<sample:empty1 />	←こういう感じで独自タグを使う
</body>
</html>

sampleは、jspファイルの先頭のprefixで定義したもの。
emptyは、jspファイルの先頭のuriで指定したtldファイル(ここではsample.tld)の中で定義したタグ


web.xmlに実際のtldファイルの場所を書き、jspファイル上での宣言を短縮することも出来る。[2008-09-28]

taglib_test.jsp:

<%@ page language="java" contextType="text/html;charset=Shift_JIS" %>
<%@ taglib uri="/sample-taglib" prefix="sample" %>
<html>
〜
</html>

web.xml:

<?xml version="1.0" encoding="Shift_JIS"?>
<web-app>

	<!-- バージョン2.3での指定方法 -->
	<taglib>
		<taglib-uri>/sample-taglib</taglib-uri>
		<taglib-location>/WEB-INF/sample.tld</taglib-location>
	</taglib>
</web-app>

サーブレット2.4でのtaglibの指定方法 [2009-10-09]


tldファイル

tldファイル(タグライブラリーディスクリプター/タグリブディスクリプター)で、どんなタグかという定義を行う。
tldファイルは適当な場所に置いて、jspのtaglib宣言でその場所を指定する。

WEB-INF/sample.tld

<?xml version="1.0" encoding="Shift_JIS" ?>
<!DOCTYPE taglib
	PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
	"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
	<tlibversion>1.0</tlibversion>
	<jspversion>1.1</jspversion>
	<shortname>sample</shortname>		←名前を付ける
	<info>タグの実験</info>			←説明を書く

	<tag>
		<name>empty1</name>				←JSPファイル内で使うタグ名
		<tagclass>jp.hishidama.taglib.EmptyTag</tagclass>	このタグの実装のjavaクラス名をパッケージ付きで
		<bodycontent>empty</bodycontent>			←JSPファイル内で このタグに囲まれた部分の処理
		<info>ボディー部が無いタグのサンプル</info>
	</tag>
</taglib>

tldファイル内の書き方はXMLそのものだが、タグはバージョンによって微妙に異なる。[2008-11-01]

DOCTYPE
PUBLIC 1.1 -//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN
1.2 -//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN
SYSTEM 1.1 http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd
1.2 http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd

<taglib>の中に書く入れ子タグ
1.1 1.2 説明
タグ タグ
tlibversion     tlib-version      
jspversion ? 1.1 jsp-version   1.2 タグリブが依存するJSPのバージョン
shortname     short-name     jspファイルを変換した後のサーブレット内でタグを使う際に使われる名前
uri ?   uri ?    
      display-name ?   ツールによって使われる名前
      small-icon ?   ツールによって使われるアイコン
      large-icon ?   ツールによって使われるアイコン
info ?   description ?   タグリブの(使用者向けの)説明
      validator ?    
      listener *    
tag +   tag +    

<tag>を複数書くことにより、1つのtldファイルの中で複数のタグを定義できる。

<tag>の中に書く入れ子タグ
1.1 1.2 説明 値の例
name   name   カスタムタグの名前 empty1
tagclass   tag-class   タグの実際の処理を行うクラス名 jp.hishidama.taglib.EmptyTag
teiclass ? tei-class ? TagExtraInfo  
bodycontent   body-content ? ボディー部の扱いを指定
empty ボディー部無し
JSP JSPやHTML等
tagdependent 何でも可
empty
    display-name ?    
    small-icon ?    
    large-icon ?    
info ? description ?    
    variable *    
attribute * attribute * タグの属性  
  name     name   属性名  
required ? required ? その属性が必須かどうか false
rtexprvalue ? rtexprvalue ? 値をJSP式として解釈するかどうか true
    example ?    

javaファイル(Tagクラス)

実行に使用するclassファイルは、J2EEサーバーが認識できる場所に置かれていればいい。WEB-INF/classesやWEB-INF/libがよく使われる。
上の例だと、WEB-INF/classes/jp/hishidama/taglib/EmptyTag.class

EmptyTag.java

package jp.hishidama.taglib;

import java.io.IOException;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class EmptyTag extetnds TagSupport {

	public int doStartTag() throws JspException {
		JspWriter out = pageContext.getOut();	//pageContext:TagSupportで定義されている変数
		try {
			out.print("<strong>");		//printによって出力される文字列が、JSPファイルのタグの位置に展開される
			out.print("テストタグの開始");
			out.println("</strong>");
		} catch (IOException e) {
			throw new JspTagException(e);
		}

		return SKIP_BODY;
	}
}

タグが開始される場所(<sample:empty>)でdoStartTag()が呼ばれる。
タグが閉じる場所(</sample:empty>)ではdoEndTag()が呼ばれる。

オーバーライド 返せる値 説明
doStartTag() EVAL_BODY_INCLUDE (後続の)ボディー部の評価を行う。
SKIP_BODY (後続の)ボディー部をスキップ(無視)する。
doAfterBody() EVAL_BODY_AGAIN ボディー部を再評価する。(繰り返し用)
SKIP_BODY 終了タグの評価へ進む。
doEndTag() EVAL_PAGE タグの続き(後続のタグ)を評価する。
SKIP_PAGE タグの続き(後続のタグ)をスキップ(無視)する。

コンパイルするにはTagSupportクラスが入っているライブラリが必要。
Tomcat5.5の場合、CATALINA_HOME/common/lib/jsp-api.jar
JBoss4.2.3の場合、JBOSS_HOME/server/default/lib/jsp-api.jar


JspTagException

doStartTag()等のメソッドは、「throws JspException」が宣言されている。[2009-12-05]
予期せぬ例外が起きたときはJspExceptionをスローすればいい。

が、JspTagExceptionという、カスタムタグ専用の例外も存在するので、実際にスローするのはこちらを使う方がよいと思われる。

JSP2.0より前のJspTagExceptionでは コンストラクターに他の例外を指定するものが無かったので不便だったが、
JSP2.0以降のJspTagExceptionではコンストラクターに例外を指定できるようになっている。


SimpleTag

JSP2.0(J2EE1.4)では、SimpleTagインターフェースSimpleTagSupportクラス)という新しいものが用意されたらしい。[2009-11-26]
TagTagSupport)と異なり、doStartTag()やdoEndTag()といった複数のメソッドを持たず、doTag()しか無い。全てのHTML出力処理をそのメソッドで記述する。
また、SimpleTagは今までのTagと異なり、インスタンスのプーリング(キャッシュ)や再利用は行われないらしい。まさにシンプル(笑)


属性の定義方法

独自タグで属性を使いたい場合は、tldファイルにattributeを定義し、Javaソースにはそのsetter/getterメソッドを用意する。[2007-07-15]

WEB-INF/taglib/sample.tld

〜
	<tag>
		<name>attr</name>
		<tagclass>jp.hishidama.taglib.AttrTag</tagclass>
		<attribute>
			<name>message</name>		←属性の名前
			<required>true</required>		←この属性が必須かどうか
			<rtexprvalue>true</rtexprvalue>	←属性に書かれた値をJSP式として解釈するかどうか
		</attribute>
	</tag>
〜

sample.jsp:

<%@ taglib uri="/WEB-INF/taglib/sample.tld" prefix="test" %>
〜
	<test:attr message="メッセージ"/>
〜

属性で指定した値は、setterを使って自動的にタグクラスのインスタンスにセットされる。

AttrTag.java

public class AttrTag extends TagSupport {

	private String message;

	public void setMessage(String message) {
		this.message = message;
	}

	public String getMessage() {
		returm message;
	}

	@Override
	public int doStartTag() throws JspException {
		JspWriter out = pageContext.getOut();
		try {
			String msg = getMessage();
			out.println("<b>" + msg + "</b>");
		} catch(IOException e) {
			throw new JspTagException(e);
		}
}

セッターの型

普通、タグクラスに値をセットするセッターメソッドは、String型で扱う。[2009-10-09]
しかしbooleanやintといったプリミティブ型も指定することが出来る。

@SuppressWarnings("serial")
public class ArgTag extends TagSupport {
〜
	public void setBoolValue(boolean b) {
		this.boolValue = b;
	}

	public void setIntValue(int n) {
		this.intValue = n;
	}
〜
}

tldファイル上では型(クラス)を指定する箇所は無いので、特別な記述は必要ない。

booleanの場合、JSPファイル上で「true」という文字列が指定された場合にtrueになり、それ以外はfalseとなる。(例えば「yes」という文字列はfalse)
intの場合、Integer#parseInt()で変換できない文字列が指定された場合はJSPのコンパイル時にNumberFormatExceptionが出る。

Tomcatjasperコンパイラー)の場合、この変換方法の管理にはPropertyEditorManagerというクラスが使われていて、
PropertyEditorというインターフェースを実装したクラス(例えばIntEditor)が存在している型では変換が出来るらしい。

変わったところでは、java.awt.Colorもセッターで使うことが出来る。

	public void setColor(java.awt.Color c) {
		〜
	}
<test:arg color="255,128,63" />	…カンマ区切りでR,G,Bを指定する

けどColorをJSPでセットするなんて事があるとは思えん!(爆) でもWebLogicでもちゃんと使えるようだ^^;


属性にスクリプトレットを使用する方法

独自タグリブの属性の値として、JSP式を使用するかどうかを指定できる。[2005-09-18]
tldファイルのrtexprvalueにtrueをセットすると、jsp上のその属性ではJSP式が使用できる。

taglib.tld:

	<tag>
		<name>radio</name>
		<tagclass>jp.hishidama.taglib.RadioTag</tagclass>
		<bodycontent>JSP</bodycontent>
〜
		<attribute>
			<name>onchange</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>

release()メソッド

Tagインターフェース(の実装TagSupportBodyTagSupport等)は、release()というメソッドを持っている。[2008-10-29]

作ったタグ(タグリブ)はJSP(をコンパイルしたサーブレット)内でインスタンス化されて使われるが、そのインスタンス(タグハンドラー)は同一JSP内で使い回されることがある。

タグハンドラー内の属性(フィールド)は、doStartTag()が呼ばれる前にセッターメソッドによってセットされ、doEndTag()が呼ばれるまで外部から変更されないことは仕様として保証されている。が、セッターメソッドが呼ばれない属性(必須でない属性)については、前のインスタンスの値がそのまま残っている可能性がある。
→Tagインターフェースの冒頭の説明『メソッド

release()メソッドは、JSP内でそのタグハンドラーが使われなくなる時に呼ばれる。
注意点として、例えば1つのタグハンドラーがJSP内の2箇所で使われていたとして、1回目の使用と2回目の使用の間でrelease()が呼ばれるわけではなく、また2回目の使用が終わったからと言ってすぐに呼び出されるわけでもない。
つまり、release()はあくまで後始末をする為のものであり、タグハンドラーの初期化には使えない。

どういうタイミングでrelease()が呼ばれるかは実装(JSPからコンパイルされたサーブレット)次第。
例えばWebLogic10ではタグハンドラーは使い回されず、毎回インスタンスが生成されている模様。
(したがって毎回release()が呼ばれる)→wljspcによるサーブレット確認
JBoss4.2.3ではJSP(のサーブレット)が呼ばれる度にインスタンスが生成され、使い回されている模様。
(JSPの呼び出しが終わるか、保持しているタグハンドラーが上限数を超えない限りrelease()は呼ばれない模様)→jasperによるサーブレット確認

毎回初期化する為にはTryCatchFinally#doFinally()を使う [2009-11-26]


TryCatchFinallyインターフェース

TryCatchFinallyは、カスタムタグでの例外発生時および処理終了後に必ず呼ばれるメソッドが定義されているインターフェース。[2009-11-26]

カスタムタグのインスタンスはプールされて使い回されるので、使われる度にフィールドを初期化したい。
release()メソッドは タグの処理(HTML生成処理)が終わる毎に呼ばれる訳では無い為、毎回のフィールド初期化を行う目的では使えない。
TryCatchFinallyインターフェースを実装しておくと、処理が終わる度にdoFinally()メソッドが呼ばれるので、初期化を行うことが出来る。

TryCatchFinallyには、doCatch()というメソッドもある。こちらは、処理中に例外が発生した場合に呼ばれる。
doFinally()は、例外が発生した場合も(doCatch()の後で)呼ばれる。

終了タグを出力する処理であるTag#doEndTag()の最後で初期化しても良さそうな気がするが、途中(doStartTag()とか)で例外が発生した場合は最後まで処理されないので、確実性という意味ではいまいち。
その為にdoFinally()があるのではないかと思う。

SimpleTagの場合は、TryCatchFinallyを実装していても無視される。doTag()しか無いので、その中で普通にtry〜catch〜finallyを使えばいいからだろう。[2009-11-28]

TryCatchFinallyを実装したタグクラスの例:

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.TryCatchFinally;
@SuppressWarnings("serial")
public class SampleTag extends BodyTagSupport implements TryCatchFinally {

	// フィールド(属性))
	private String value1, value2, value3;

	public void setValue1(String value) {
		this.value1 = value;
	}
	…
	@Override
	public int doStartTag() throws JspException {
		JspWriter out = pageContext.getOut();
		try {
			out.print(value1);
			out.print(value2);
			out.print(value3);
		} catch (IOException e) {
			throw new JspTagException(e);
		}
		return EVAL_BODY_INCLUDE;
	}
	// 毎回呼ばれる訳では無い
	@Override
	public void release() {
		value1 = null;
		value2 = null;
		value3 = null;
		super.release();
	}
	@Override
	public void doCatch(Throwable t) throws Throwable {
		throw t;
	}

	// 毎回呼ばれる
	@Override
	public void doFinally() {
		value1 = null;
		value2 = null;
		value3 = null;
	}
}

release()は親クラスで定義されているメソッドなので、super.release()を呼び出して親クラスのリソースも解放する必要がある。
doFinally()は個別に実装するメソッドなので、親クラスにdoFinally()があるとは限らない。
「doFinally()を実装したカスタムタグ」を継承してさらにカスタムタグを作った場合は、super.doFinally()を呼び出す必要があるだろう。


jspファイルはJavaソースに変換されてコンパイルされて実行されるが、カスタムタグがTryCatchFinallyを実装している場合、そのメソッドを呼び出すようなコードになる。[2009-11-26]

jspから変換されたJavaソースのイメージ:

	SampleTag tag = SampleTagのプール.get();	//タグのプールからインスタンスを取得
	tag.setPageContext(pageContext);
	tag.setParent(null);

	tag.setValue1("hoge");	//jsp上で記述された属性のセッターが呼ばれる
				//jsp上に記述されていない属性は初期化されない
	try {
		if (tag.doStartTag() != SKIP_BODY) {
			〜 //jsp上で記述されたボディー部の出力
		}
		if (tag.doEndTag() == SKIP_PAGE) {
			return;
		}
	} catch (Throwable t) {
		tag.doCatch(t);
	} finally {
		tag.doFinally();
		SampleTagのプール.reuse(tag);	//タグのプールに戻す
	}

※見て分かる通り、finallyブロックで呼ばれるのはdoFinally()であり、release()ではない。
 release()はタグのプールがクリアされる際に呼ばれる。

doFinally()で例外が発生したら、インスタンスがタグプールに戻されないな…。


JSPタグへ戻る / JSPへ戻る / Java目次へ戻る
メールの送信先:ひしだま