カスタムタグライブラリ

2008年02月09日

カスタムタグライブラリ

JSP ではプログラマがカスタムタグライブラリ (Custom Tag Library; 拡張タグライブラリ) を作成する事で <jsp:include> のような機能を自由に定義することができます。

カスタムタグは Java コード挿入による可視性低下を防ぐのに大変有用ですが、ページ固有の処理までカスタムタグ化するのは却って手間と管理が膨大になり大変です。一般的には以下のような局面での効果が期待できます。

定型記述の簡略化
共通のページヘッダ、ページフッタ、スタイルシートや JavaScript の取り込み、キャッシュ制御命令といった記述をカスタムタグ化しておくことで JSP が簡素になり後々のメンテナンスも楽になります。
汎用機能の部品化
タブ切り替えやツリー表示などの汎用的な機能を部品化したり、二度押し防止、バックスペース無効化、自動入力値チェックなどを追加したコンポーネントを作成することができます。
フレームワークとの統合
フレームワークが管理するリソースやアプリケーションデータへの JSP からのアクセス手段として使用することができます。

JSTLJSFGoogle I'm Feeling Lucky™ などでは汎用的なカスタムタグの多くを既に実装していますので、実際に作成する前にこれらの組み合わせで解決できないか調べてみる事をお勧めします。

呼び出し構造

カスタムタグは XML に似た接頭辞 (名前空間) を付けた記述を行うことで示されます。 JSP プロセッサはその JSP で宣言されている <%@taglib%> ディレクティブに基づいて接頭辞に対応するTLD ファイル (Tag Library Definition File) を参照し、どのタグハンドラ(Tag Handler) を呼び出せばよいかを決定します。

概要

TLD とは「どのタグ名 (ローカル名) が使用された時にどのタグハンドラを呼び出すか」を定義したファイルです。クラス名の他に、タグがどういった属性を取り、どういった内容を持つかなどを定義しています。

タグハンドラ

カスタムタグライブラリは Tag インターフェースを実装した タグハンドラ (Tag Handler) と呼ばれるクラスが処理を行います。拡張タグを作成するときに使用するスーパークラス/インターフェースは以下の特徴があります。

インターフェース Tag IterationTag BodyTag SimpleTag
開始位置での処理
終了位置での処理
タグ内部のスキップ
ページ評価の終了
タグ内部の繰り返し評価 ×
タグ内部評価結果の取得 × ×
フラグメントによる実装の簡略化 × × ×
タグ内部でのスクリプトレット使用 ×

どのインターフェースを実装するかによって可能なことが違ってきます。また SimpleTag と他のタグハンドラは設計が大きく異なります。これから覚えるのであれば安全で扱いやすい SimpleTag をお勧めします。

それぞれのインターフェースに対して定型メソッドを実装した XxxTagSupport クラスが提供されています。大抵は上記のインターフェースから実装するよりこれらのクラスのサブクラスを作成したほうが楽です。

タグハンドラクラス図

実装ガイダンス

インスタンスの同期化は不要

タグハンドラのインスタンスがマルチスレッドで実行されることはありません。これはインスタンスごとに設定される PageContext 自体がリクエスト間で共有できるオブジェクトでないことからも分かります。

ただし static 宣言したクラス変数は従来のクラスと同様に複数のスレッドからの同時アクセスが行われますので注意して下さい。

doEndTag(), release() の呼び出しを期待しない

タグの開始やタグ内部の処理で例外が発生した場合 doEndTag(), release() は呼び出されません。リソースの開放などの確実なクリーンアップ処理が必要なタグハンドラは TryCatchFinallyJava™ API リファレンス を実装してください。

SimpleTag は 1 メソッド内でタグの処理が完結するため、通常の Java コードと同じように try-finally を利用したリソース開放が可能です。

インスタンスプールによる再利用に注意

最近のサーブレットコンテナのほとんどはインスタンス構築によるパフォーマンス劣化を防ぐ目的で、処理の終わったタグハンドラを次回以降の処理でも再利用しています。このため、タグの処理で使用する内部状態 (インスタンス変数) を持つタグハンドラは必ず release() メソッドをオーバーライドして初期状態に戻す必要があります。

プールされているインスタンスは setParent() などのいくつかの初期化メソッドが省略される可能性があります。同様に、属性を静的に指定した (つまりスクリプトレットや EL を使用していない) タグハンドラは setter の再呼び出しが行われることなく処理が開始される可能性があります。

SimpleTag はフールプルーフを意識しておりインスタンスがキャッシュされない事が保障されています。実装に自信がないのであればこちらを使用してください。

バッファリングの使用に注意

BodyTag のバッファリング機能を使用するとタグの内容を文字列として取得できますが、これはタグ内の出力がいったん全てメモリに保管されるため乱用は禁物です。またバッファリングを行っている間は flush() が行えません。

Tag ハンドラ

Tag シーケンス

TagJava™ API リファレンス のサブクラスはタグの開始位置、終了位置で処理を行うだけのタグハンドラとして機能します。

doStartTag()/doEndTag() の返値で何を返すかによって、タグの内部を評価するかどうか、ページの評価を続行するかどうかをサーブレットコンテナに知らせることができます。


メソッド 返値 意味
doStartTag()Java™ API リファレンス EVAL_BODY_INCLUDE このタグの内部を評価します。
SKIP_BODY このタグの内部をスキップします。
doEndTag()Java™ API リファレンス EVAL_PAGE このタグより後ろの評価を続行します。
SKIP_PAGE このタグより後ろのページの処理を終了します。

IterationTag ハンドラ

IterationTag シーケンス

IterationTagJava™ API リファレンス、はタグの内部を繰り返し評価するタグハンドラとして機能します。なお IterationTagSupport というクラスは存在せず、 TagSupportJava™ API リファレンスIterationTag も実装しています。

JSP はタグ内部の評価が終了した後に doAfterBody() を呼び出して繰り返しを続行するかどうかを判断します。doStartTag()doEndTag() の返値は Tag のそれと同じです。


メソッド 返値 意味
doStartTag()Java™ API リファレンス EVAL_BODY_INCLUDE このタグの内部を評価します。
SKIP_BODY このタグの内部をスキップします。
doAfterBody()Java™ API リファレンス EVAL_BODY_AGAIN このタグ内部をもう一度評価します。
SKIP_BODY このタグ内部の評価を終了します。
doEndTag()Java™ API リファレンス EVAL_PAGE このタグより後ろの評価を続行します。
SKIP_PAGE このタグより後ろのページの処理を終了します。

BodyTag ハンドラ

BodyTag シーケンス

BodyTagJava™ API リファレンスBodyTagSupportJava™ API リファレンス はタグ内部の評価結果 (出力内容) をタグハンドラで取得することができるタグです。内部の文字列を加工して出力したり、SQL のような HTML 以外の言語を解釈できるタグハンドラで使用します。

doStartTag()EVAL_BODY_BUFFERED を返した場合、タグ内の出力用 JspWriter がバッファ出力用の BodyContentJava™ API リファレンス と置き換えられます。doEndTag() ではこの BodyContent からタグ内の評価結果を参照することができます。


メソッド 返値 意味
doStartTag()Java™ API リファレンス EVAL_BODY_INCLUDE このタグの内部を評価します。
SKIP_BODY このタグの内部をスキップします。
EVAL_BODY_BUFFERED BodyContent を使用してタグ内をバッファリングします。
doAfterBody()Java™ API リファレンス EVAL_BODY_AGAIN このタグ内部をもう一度評価します。
SKIP_BODY このタグ内部の評価を終了します。
doEndTag()Java™ API リファレンス EVAL_PAGE このタグより後ろの評価を続行します。
SKIP_PAGE このタグより後ろのページの処理を終了します。

ほとんどのサーブレットコンテナはメモリ上でバッファリングを行うため、不必要に EVAL_BODY_BUFFERED を使用すべきではありません。特にページ全体を囲うようなタグや内部で大量の繰り返し出力が想定されるタグで使わなければならないなら設計を再考する必要があります。

またバッファリングを行っている状態は裏でストリームに繋がっていないためフラッシュに失敗します。これは <jsp:include>Throwable#printStackTrace(out)TransformerJava™ API リファレンス を使用した XSL 変換など、暗黙的なフラッシュを伴う処理をタグの内部で行えない事を意味します。

SimpleTag ハンドラ

SimpleTag シーケンス

SimpleTagJava™ API リファレンスSimpleTagSupportJava™ API リファレンス は JSP 2.0 で導入された、従来のタグハンドラより使いやすく安全な設計に変更されたタグハンドラです。

SimpleTag は返値による煩わしいフロー制御を行う必要がありません。 doTag() メソッド一つをオーバーライドし、この中の好きな位置でタグ内を評価することができます。もちろん if や for で内容をスキップしたり繰り返したり、また評価結果を文字列として取得する事も可能です。

しかしこの動作は JSP 2.0 で導入された JspFragmentJava™ API リファレンス という機能を利用して実現しているため、フラグメント内、つまり SimpleTag で実装したタグ内では スクリプトレットを記述することができません (EL やカスタムタグは利用可能です; またタグ外であればスクリプトレットも利用可能です)。

最近ではスクリプトレットをなるべく使わない風潮ではありますが、任意の場所で使えないという制約となってしまうと少々不便です。製品やオープンソースのようにどう使われるか想定できない場合や、<html>, <body> に置き換わるような大域を囲むタグでの利用は一考に値します。


CVS 2008/03/09