JSPの独自タグ(カスタムタグ)を作るには、taglib(タグライブラリー/タグリブ)を作成することになる。
|
ここにメモしてある方法とは別に、タグファイルというものを使う新しい方法もある。[2007-06-17]
独自タグを組み込んだjspは、以下のような感じになる。[/2007-07-15]
<%@ 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ファイルは適当な場所に置いて、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]
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 |
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ファイルの中で複数のタグを定義できる。
1.1 | 1.2 | 説明 | 値の例 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
name | name | カスタムタグの名前 | empty1 |
||||||||||
tagclass | tag-class | タグの実際の処理を行うクラス名 | jp.hishidama.taglib.EmptyTag |
||||||||||
teiclass | ? | tei-class | ? | TagExtraInfo | |||||||||
bodycontent | body-content | ? | ボディー部の扱いを指定
|
empty |
|||||||||
display-name | ? | ||||||||||||
small-icon | ? | ||||||||||||
large-icon | ? | ||||||||||||
info | ? | description | ? | ||||||||||
variable | * | ||||||||||||
attribute | * | attribute | * | タグの属性 | |||||||||
name | name | 属性名 | |||||||||||
required | ? | required | ? | その属性が必須かどうか | false |
||||||||
rtexprvalue | ? | rtexprvalue | ? | 値をJSP式として解釈するかどうか | true |
||||||||
example | ? |
実行に使用するclassファイルは、J2EEサーバーが認識できる場所に置かれていればいい。WEB-INF/classesやWEB-INF/libがよく使われる。
上の例だと、WEB-INF/classes/jp/hishidama/taglib/EmptyTag.class
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
doStartTag()等のメソッドは、「throws JspException」が宣言されている。[2009-12-05]
予期せぬ例外が起きたときはJspExceptionをスローすればいい。
が、JspTagExceptionという、カスタムタグ専用の例外も存在するので、実際にスローするのはこちらを使う方がよいと思われる。
JSP2.0より前のJspTagExceptionでは コンストラクターに他の例外を指定するものが無かったので不便だったが、
JSP2.0以降のJspTagExceptionではコンストラクターに例外を指定できるようになっている。
JSP2.0(J2EE1.4)では、SimpleTagインターフェース(SimpleTagSupportクラス)という新しいものが用意されたらしい。[2009-11-26]
Tag(TagSupport)と異なり、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を使って自動的にタグクラスのインスタンスにセットされる。
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が出る。
Tomcat(jasperコンパイラー)の場合、この変換方法の管理には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>
変換前のJSP <test:radio 〜 onchange="<%= hoge() %>"/> 変換されたHTML <input type="radio" 〜 onchange="<%= hoge() %>">
変換前のJSP <test:radio 〜 onchange="setPos(<%= 100 %>)"/> 変換されたHTML <input type="radio" 〜 onchange="setPos(<%= 100 %>)">
変換前のJSP <test:radio 〜 onchange="<%= "setPos(" + 100 + ")" %>"/> 変換されたHTML <input type="radio" 〜 onchange="setPos(100)">
Tagインターフェース(の実装TagSupportやBodyTagSupport等)は、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は、カスタムタグでの例外発生時および処理終了後に必ず呼ばれるメソッドが定義されているインターフェース。[2009-11-26]
カスタムタグのインスタンスはプールされて使い回されるので、使われる度にフィールドを初期化したい。
release()メソッドは
タグの処理(HTML生成処理)が終わる毎に呼ばれる訳では無い為、毎回のフィールド初期化を行う目的では使えない。
TryCatchFinallyインターフェースを実装しておくと、処理が終わる度にdoFinally()メソッドが呼ばれるので、初期化を行うことが出来る。
TryCatchFinallyには、doCatch()というメソッドもある。こちらは、処理中に例外が発生した場合に呼ばれる。
doFinally()は、例外が発生した場合も(doCatch()の後で)呼ばれる。
終了タグを出力する処理であるTag#doEndTag()の最後で初期化しても良さそうな気がするが、途中(doStartTag()とか)で例外が発生した場合は最後まで処理されないので、確実性という意味ではいまいち。
その為にdoFinally()があるのではないかと思う。
※SimpleTagの場合は、TryCatchFinallyを実装していても無視される。doTag()しか無いので、その中で普通にtry〜catch〜finallyを使えばいいからだろう。[2009-11-28]
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]
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()で例外が発生したら、インスタンスがタグプールに戻されないな…。