4 タグ・ハンドラの実装
シンプル・タグ・ハンドラのライフサイクル(SimpleTagインタフェース)
シンプル・タグ・ハンドラでは、従来のタグ・ハンドラと比べて、大幅に簡素化された呼出しプロトコルが提示されます。タグ・ライブラリ記述子は、物理的な基底の実装に対して、タグ・ライブラリ宣言をマップします。シンプル・タグ・ハンドラは、SimpleTag
インタフェースを実装するクラスによってJavaで表現されます。
シンプル・タグ・ハンドラのライフサイクルは簡単明瞭なものであり、キャッシングのセマンティクスによって複雑化されていません。一旦コンテナによってインスタンス化されたシンプル・タグ・ハンドラは、実行され、その後破棄されます。同じインスタンスをキャッシュして再利用することはできません。初期パフォーマンスのメトリックにより、タグ・ハンドラ・インスタンスをキャッシュしても、必ずしもパフォーマンスが向上するわけではないことがわかります。そのようなキャッシングに対応しようとすると、ポータブル・タグ・ハンドラの記述が困難になり、タグ・ハンドラでエラーが生じやすくなります。
扱いが簡単であることに加えて、シンプル・タグの拡張では、いかなるサーブレットAPIにも直接的な依存がないため、将来的な他の技術との統合の可能性にも対応できます。この機能は、Page-Context
によって拡張されたJspContext
クラスで促進されています。JspContext
は、JspWriter
の格納やスコープ指定された属性の追跡など、一般的なサービスを提供します。一方、PageContext
はサーブレットのコンテキストにおけるJSPの処理に特化した機能を備えています。TagインタフェースはPageContext
に依存していますが、SimpleTag
はJspContext
のみに依存しています。SimpleTag
の本体は、もしあれば、JSPフラグメントに変換され、setJspBody
メソッドに渡されます。タグはその後、必要な回数、そのフラグメントを実行できます。
「JSPフラグメント」を参照してください。
従来のタグ・ハンドラとは異なり、SimpleTag
インタフェースは、Tag
を拡張したものではありません。doStartTag()
およびdoEndTag()
をサポートするのではなく、SimpleTag
インタフェースは単純なdoTag()
メソッドを提供します。このメソッドは任意のタグ呼出しごとに一度だけ呼び出されます。すべてのタグのロジック、反復、本体評価などが、この単一のメソッド内で実行されます。したがって、シンプル・タグ・ハンドラはBodyTag
と同等の能力を持ちますが、そのライフサイクルとインタフェースはずっと単純です。
本体のコンテンツをサポートするために、setJspBody()
メソッドが提供されています。コンテナは、JspFragment
オブジェクトがタグの本体をカプセル化している状態でsetJspBody()
メソッドを呼び出します。タグ・ハンドラの実装は、そのフラグメントに対してinvoke()
を呼び出し、本体を評価できます。SimpleTagSupport
コンビニエンス・クラスでは、これをさらに容易に行うための、getJsp-Body()
やその他の有用なメソッドが用意されています。
シンプル・タグ・ハンドラのライフサイクルにおけるイベント
以下では、作成から呼出しに至るまでのシンプル・タグ・ハンドラのライフサイクルを説明します。シンプル・タグ・ハンドラが呼び出されると、次のイベントが順番に発生します。
-
シンプル・タグ・ハンドラが、最初は対応する実装クラスに対して0の引数コンストラクタを使用して作成されます。従来のタグ・ハンドラとは異なり、シンプル・タグ・ハンドラ・インスタンスは、決してコンテナにプールされることがありません。タグ呼出しごとに、新しいインスタンスを作成する必要があります。
-
setJspContext()
およびsetParent()
メソッドが、タグ・ハンドラに対して呼び出されます。渡されている値がnull
の場合には、setParent()
メソッドを呼び出す必要はありません。タグ・ファイルの場合、JspContext
ラッパーが作成されるので、タグ・ファイルは独自のページ・スコープを備えていると認識されます。getJspContext()
は、ラップされたJsp-Context
を返す必要があります。 -
次に、次のルールに従って、XML要素の属性として指定される属性(存在する場合)が宣言された順序で評価されます(これを「XML要素属性の評価」と称します)。適切なBeanプロパティ・セッターが、各々に対して呼び出されます。指定された属性に対してセッターが定義されておらず、しかしタグでは動的な属性を受け付けている場合、
setDynamicAttribute()
メソッドがセッターとして呼び出されます。属性がスクリプト記述式(たとえば、JSP構文の
「<%= 1+1 %>」
またはXML構文の「%= 1+1 %」
)である場合、式は評価され、その結果が変換されてセッターに渡されます。また、属性に何らかの式言語表現(たとえば、
「Hello ${name}」
)が含まれていると、式は評価され、その結果が変換されてセッターに渡されます。それ以外の場合、属性値は逐語的に取得され、変換されてセッターに渡されます。
-
各
<jsp:attribute>
要素の値が評価され、タグの本体に登場する順序に従って、対応するBeanプロパティ・セッター・メソッドが各々について呼び出されます。指定された属性に対してセッターが定義されておらず、しかしタグでは動的な属性を受け付けている場合、setDynamicAttribute()
メソッドがセッターとして呼び出されます。また、属性のタイプが
JspFragment
ではない場合、コンテナは<jsp:attribute>
要素の本体を評価します。この評価は、コンテナ固有のものとして行うことができます。コンテナの実装者は、この本体評価プロセスにおいて、他のカスタム・アクションが呼び出される可能性があることに留意してください。また、属性のタイプが
JspFragment
である場合、Jsp-Fragment
オブジェクトのインスタンスが作成されて渡されます。 -
タグ本体の値が決定され、本体が存在する場合は、
setJsp-Body()
メソッドがタグ・ハンドラに対して呼び出されます。本体のコンテンツ(
body-content
)が空である、本体が存在しない、またはこの呼出しに対して空の本体が渡されたとタグで宣言されている場合には、setJspBody()
は呼び出されません。また、
<jsp:body>
または<jsp:attribute>
要素が存在しない場合は、タグの本体は<jsp:body>
要素の本体であるか、カスタム・アクション呼出しの本体であるかのいずれかです。この場合、Jsp-Fragment
オブジェクトのインスタンスが作成され、セッターに渡されます。タグが、tagdependent
という本体コンテンツ(body-content
)を有すると宣言された場合、JspFragment
は本体のコンテンツを逐語的に返す必要があります。また、タグが、
scriptless
というタイプの本体コンテンツ(body-content
)を有すると宣言された場合、JspFragment
は本体のコンテンツをJSPのスクリプトなし本体として評価する必要があります。 -
doTag()
メソッドが呼び出されます。 -
doTag()
の実装により、潜在的に(タグ・ハンドラがタグ・ファイルとして実装されている場合は)他のタグ・ハンドラを呼び出し、フラグメントを呼び出して、機能が実行されます。 -
doTag()
メソッドが返り、タグ・ハンドラ・インスタンスが破棄されます。Skip-PageException
が送出されると、ページの残りの部分は評価されず、要求が完了します。この要求が別のページ(またはサーブレット)から転送されている、または組み込まれている場合、現在のページの評価だけが停止します。 -
AT_BEGIN
またはAT_END
のスコープで宣言されたタグ・スクリプト変数の各々について、クラシック・タグ・ハンドラの場合と同じく、適切なスクリプト変数とスコープ指定された属性が宣言されます。
JSPフラグメント
JSPフラグメントとは、必要に応じて何度でも呼び出せる、JSPコードのタグ・ハンドラに渡される部分です。フラグメントは、カスタマイズされたコンテンツを生成するためにタグ・ハンドラによって使用されるテンプレートであると考えることができます。したがって、コンテナによって評価される単純な属性とは異なり、フラグメント属性はタグ呼出し中にタグ・ハンドラによって評価されます。
JSPフラグメントは、フラグメントを構成するJSPコード内のJSP式言語(JSP EL)変数を使用して、パラメータ化できます。JSP EL変数はタグ・ハンドラによって設定されるため、ハンドラはフラグメントが呼び出される度に、そのフラグメントをカスタマイズできます。
『Oracle WebLogic Server Webアプリケーション、サーブレット、JSPの開発』の「WebLogic JSPリファレンス」を参照してください。
シンプル・タグ・ハンドラのライフサイクルの期間中、SimpleTag
の本体は、もしあれば、JSPフラグメントに変換され、setJspBody
メソッドに渡されます。変換フェーズの期間中、ページの様々な部分が、タグ・ハンドラに渡される前にjavax.servlet.jsp.tagext.JspFragment
抽象クラスの実装に変換されます。これは、タグ・ライブラリ記述子(TLD)内で、フラグメントとして宣言された、またはJspFragment
タイプである、名前付き属性の本体における任意のJSPコード(<jsp:attribute>
で定義される)に対して自動的に行われます。また、シンプル・タグ・ハンドラによって処理されるどのタグの本体に対しても、自動的に行われます。一度渡されると、タグ・ハンドラはフラグメントを必要に応じて何度でも評価および再評価でき、タグ・ファイルの場合にはさらに他のタグ・ハンドラに渡すことさえできます。
JSPフラグメントは、フラグメントと関連付けられたJspContext
内のページ・スコープ属性を設定することで、タグ・ハンドラによってパラメータ化できます。これらの属性にはその後、式言語を利用してアクセスできるようになります。
JSPフラグメントに変換されるはずのJSPコードの一部に、スクリプトレットまたはスクリプトレット式が含まれていた場合、変換エラーが発生します。
タグ・ハンドラのライフサイクル(TagおよびBodyTagインタフェース)
Tag
インタフェースまたはBodyTag
インタフェースから継承され、そのタグ・ハンドラ・クラスによって実装されたメソッドは、JSPページの処理中の特定の時点でJSPエンジンによって呼び出されます。これらのメソッドは、タグのライフサイクルにおける様々なポイントを表し、以下の順序で実行されます。
-
JSPエンジンがJSPページの中でタグを見つけると、新しいタグ・ハンドラが初期化されます。
javax.servlet.jsp.tagext.Tag
インタフェースのsetPageContext()
メソッドとsetParent()
メソッドが呼び出されて、そのタグ・ハンドラの環境コンテキストが設定されます。タグ開発者は、ベース・クラスTagSupport
またはBodyTagSupport
を拡張する場合、これらのメソッドを実装する必要はありません。 -
タグ属性ごとに
setXXXX()
というJavaBeanのようなメソッドが呼び出されます。 -
doStartTag()
メソッドが呼び出されます。タグ・ハンドラでこのメソッドを定義すると、タグ・ハンドラを初期化したり、データベースなどの必要なあらゆるリソースへの接続を開くことができます。doStartTag()
メソッドの終わりには、タグ本体を評価すべきかどうかを、以下の値定数のうちの1つをタグ・ハンドラ・クラスから返すことで指定できます。SKIP_BODY
- タグの本体をスキップするようJSPエンジンに指示します。タグが本体のないタグ(空タグ)である場合、この値を返します。タグのライフサイクルのうち本体に関係のある部分はスキップされ、次に呼び出されるメソッドはdoEndTag()
となります。EVAL_BODY_INCLUDE
- タグ本体のコンテンツを評価して組み込むようJSPエンジンに指示します。タグのライフサイクルのうち本体に関係のある部分はスキップされ、次に呼び出されるメソッドはdoEndTag()
となります。この値を返すことができるのは、
Tag
インタフェースを実装するタグの場合だけです。この値を使用すると、本体を組み込むかどうかは決定できるが、そのコンテンツには関知しないタグを記述できます。タグがBodyTag
インタフェースを実装する(またはBodyTagSuport
クラスを拡張する)場合、この値を返すことはできません。EVAL_BODY_TAG
- タグ本体を評価してからdoInitBody()
メソッドを呼び出すようJSPエンジンに指示します。この値は、タグがBodyTag
インタフェースを実装する(またはBodyTagSupport
クラスを拡張する)場合にだけ返すことができます。 -
setBodyContent()
メソッドが呼び出されます。この時点では、タグからの出力はBodyContent
という特殊なJspWriter
にリダイレクトされ、クライアントには送られません。本体を評価して得られるすべてのコンテンツはBodyContent
バッファに追加されます。このメソッドにより、タグ・ハンドラはBodyContent
バッファへの参照を格納してdoAfterBody()
メソッドで評価後処理のために使えるようにすることができます。タグが出力をJSPページ(ネスト・タグの場合はその親スコープ)に渡す場合、タグはタグ・ライフサイクルのこの時点から
doEndTag()
メソッドが終わるまでに、そのタグの出力を親スコープのJspWriter
に明示的に書き出さなければなりません。タグ・ハンドラは、getEnclosingWriter()
メソッドを使用して、親スコープの出力にアクセスできます。コンビニエンス・クラスの
BodyTagSupport
を使用している場合、このメソッドを実装する必要はありません。これは、タグがBodyContent
への参照を保持し、BodyContent
メソッドを介してその参照を使用できるようにするからです。 -
doInitBody()
メソッドが呼び出されます。このメソッドを使用すると、タグ本体が初めて評価される直前に何らかの処理を実行できます。ここでは、スクリプト変数を設定したり、タグ本体の前のBodyContent
に何らかのコンテンツを挿入したりできます。付加したコンテンツは、JSPページのタグ本体のコンテンツとは異なり、JSPとして評価されることはありません。このメソッドで実行する処理と
doStartTag()
メソッドの終わりに実行する処理との大きな違いは(EVAL_BODY_TAG
を返そうとしていることがわかった場合)、このメソッドでは、タグの出力のスコープはネストされていて、JSPページ(または親タグ)には直接向けられないということです。すべての出力は、BodyContent
という特殊なJspWriter
に書き込まれます。 -
doAfterBody()
メソッドが呼び出されます。このメソッドは、タグの本体が評価されBodyContent
バッファに追加された後に呼び出されます。タグ・ハンドラは、評価済みタグ本体に基づいて何らかの処理を行うためにこのメソッドを実装する必要があります。ハンドラがコンビニエンス・クラスのBodyTagSupport
を拡張する場合、getBodyContent()
メソッドを使用して評価済みタグ本体にアクセスできます。単にBodyTag
インタフェースを実装するだけであれば、setBodyContent()
メソッドを定義して、そこにBodyContent
インスタンスへの参照を格納しておく必要があります。doAfterBody()
メソッドの終わりには、タグのライフサイクルを、前と同じように以下の値定数のうちの1つを返すことで決定できます。SKIP_BODY
- 処理を続行し、本体を再び評価しないようJSPエンジンに指示します。タグのライフサイクルはdoEndTag()
メソッドに進みます。EVAL_BODY_TAG
- 本体を再び評価するようJSPエンジンに指示します。評価済み本体がBodyContent
に追加され、doAfterBody()
メソッドが再び呼び出されます。この時点で、タグ・ハンドラが親スコープに出力を書き出すよう指定できます。親スコープへのライターを取得するには、
BodyTagSupport.getPreviousOut()
メソッドまたはBodyContent.getEnclosingWriter()
メソッドを使用します。どちらのメソッドでも、同じ親ライターが取得されます。タグ・ハンドラは評価済み本体のコンテンツを親スコープに書き出すこともあれば、その評価済み本体をさらに処理して他の何らかの出力を書き出すこともあります。本体の反復処理ごとに
BodyContent
が既存のBodyContent
に追加されるので、SKIP_BODY
を返すよう決定した場合、反復処理された本体のコンテンツ全体だけを書き出します。そのようにしないと、後続の各反復処理のコンテンツが何度も出力に現れることになります。 -
pageContext
内のout
ライターが親のJspWriter
に復元されます。このオブジェクトは実際にはスタックであり、pushBody()
メソッドとpopBody()
メソッドを使用してpageContext
上のJSPエンジンによって処理されます。ただし、タグ・ハンドラの中でこれらのメソッドを使用してこのスタックを処理しようとしてはいけません。 -
doEndTag()
メソッドが呼び出されます。タグ・ハンドラはこのメソッドを実装して、タグ処理後のサーバー側作業を実行し、出力を親スコープJspWriter
に書き出し、データベース接続などのリソースをクローズできます。タグ・ハンドラは、
doEndTag()
メソッド内でpageContext.getOut()
を実行して得られるJspWriter
を使用して、親スコープに出力を直接書き出します。なお、pageContext.out
は、前のステップでpopBody()
がpageContext
上で呼び出されたときに親ライターに復元されています。doEngTag()
メソッドから以下の値のうちの1つを返すことで、JSPページの残りの部分の評価フローを制御できます。EVAL_PAGE
- JSPページの残りの部分の処理を続行するようJSPエンジンに指示します。SKIP_PAGE
- JSPページの残りの部分をスキップするようJSPエンジンに指示します。 -
release()
メソッドが呼び出されます。この呼出しは、タグ・ハンドラ・インスタンスが逆参照され、ガベージ・コレクション用に使用できるようになる直前に行われます。
タグ本体の反復処理(IterationTagインタフェース)
javax.servlet.jsp.tagext.IterationTag
インタフェースを実装するタグでは、タグの本体を条件付きで再評価できるdoAfterBody()
というメソッドを使用できます。doAFterBody()
がIterationTag.EVAL_BODY_AGAIN
を返す場合は、本体が再評価されます。doAFterBody()
がTag.SKIP_BODY
を返す場合は、本体はスキップされてdoEndTag()
メソッドが呼び出されます。このインタフェースについては、Java EE Javadocを参照してください。
Javadocs (http://www.oracle.com/technetwork/java/javaee/jsp/index.html
)をダウンロードできます。
タグ本体内の例外処理
javax.servlet.jsp.tagext.TryCatchFinally
インタフェースのdoCatch()
およびdoFinally()
メソッドを実装すると、タグ内から送出される例外を捕捉できます。このインタフェースについては、Java EE Javadocを参照してください。
Javadocs (http://www.oracle.com/technetwork/java/javaee/jsp/index.html
)をダウンロードできます。
タグ属性の使い方
カスタム・タグでは、JSPページから指定できる属性を何個でも定義できます。これらの属性は、タグ・ハンドラに情報を渡してその動作をカスタマイズするために使用できます。
各属性名は、TLDの中で<attribute>
要素を使用して宣言します。この要素は、属性の名前とその他の属性プロパティを宣言します。
JavaBean規約と同じように、タグ・ハンドラは属性名に基づいてセッター・メソッドとゲッター・メソッドを実装する必要があります。たとえば、foo
という属性名を宣言する場合、タグ・ハンドラは以下のパブリック・メソッドを定義しなければなりません。
public void setFoo(String f); public String getFoo();
属性名の先頭の文字は、接頭辞のsetまたはgetの後では大文字になります。
JSPエンジンは、タグ・ハンドラが初期化されてからdoStartTag()
メソッドが呼び出されるまでの間に、各属性のセッター・メソッドを呼び出します。一般に、タグ・ハンドラの他のメソッドからアクセスできるメンバー変数に属性値を格納するには、セッター・メソッドを実装しなければなりません。
新しいスクリプト変数の定義
タグ・ハンドラは、様々なスコープでJSPページから参照できる新しいスクリプト変数を使用できます。スクリプト変数は、それらの定義済みスコープの内部で暗黙的なオブジェクトのように使用できます。
javax.servlet.jsp.tagext.TagExtraInfo
を拡張するJavaクラスを識別するための新しいスクリプト変数は、<tei-class>要素を使用して定義します。たとえば:
<tei-class>weblogic.taglib.session.ListTagExtraInfo</tei-class>
次に、TagExtraInfo
クラスを作成します。
たとえば:
package weblogic.taglib.session; import javax.servlet.jsp.tagext.*; public class ListTagExtraInfo extends TagExtraInfo { public VariableInfo[] getVariableInfo(TagData data) { return new VariableInfo[] { new VariableInfo("username","String",true,VariableInfo.NESTED) new VariableInfo("dob","java.util.Date",true,VariableInfo.NESTED) }; } }
上の例では、VariableInfo
要素の配列を返すgetVariableInfo()
という1つのメソッドが定義されています。各要素は、新しいスクリプト変数を定義します。上記のサンプルでは、java.lang.String
型のusername
とjava.util.Date
型のdob
という2つのスクリプト変数が定義されています。
VariableInfo()
のコンストラクタは、以下の4つの引数を取ります。
-
新しい変数の名前を定義する
String
。 -
その変数のJavaデータ型を定義する
String
。java.lang
パッケージ以外のパッケージに含まれる型の完全パッケージ名を指定します。 -
その変数を使う前にインスタンス化しなければならないかどうかを宣言する
boolean
。タグ・ハンドラがJava以外の言語で書かれている場合を除き、この変数は「true」に設定します。 -
その変数のスコープを宣言する
int
。以下に示すVariableInfo
に定義されている静的フィールドを使用します。VariableInfo.NESTED
- タグの開始タグと終了タグの間でだけ使用できます。VariableInfo.AT_BEGIN
- 開始タグからページの最後までの間で使用できます。VariableInfo.AT_END
- 終了タグとページの最後の間で使用できます。
タグ・ハンドラは、ページのコンテキストを介してスクリプト変数の値を初期化しなければなりません。たとえば、上で定義したスクリプト変数の値を初期化するには、doStartTag()
メソッドの中で以下のJavaソースを使用します。
pageContext.setAttribute("name", nameStr); pageContext.setAttribute("dob", bday);
ここで、最初のパラメータはスクリプト変数の名前を指定し、2番目のパラメータは代入される値を示します。なお、ここではJava変数のnameStr
はString
型で、bday
はjava.util.Date
型です。
また、TagExtraInfo
クラスを使用して作成された変数にアクセスするには、useBean
を使用して作成されたJavaBeanにアクセスするときと同じようにそれを参照します。
動的名前付きスクリプト変数
タグの属性から、新しいスクリプト変数の名前を定義できます。このように定義することで、1つのスクリプト変数を定義するタグの複数のインスタンスを同じスコープで使用しつつ、タグのスクリプト変数名の衝突を避けることができます。TagExtraInfo
を拡張するクラスからこれを実行するには、getVariableInfo()
メソッドに渡されるTagData
からスクリプト変数の名前を取得する必要があります。
TagData
からは、getAttributeString()
メソッドを使用して、スクリプト変数の名前を指定する属性の値を検索できます。さらに、id
属性の値を返すgetId()
メソッドも存在します。これは、JSPタグからもたらされる新しい暗黙的オブジェクトに名前を付けるのによく使用されます。
協調的ネスト・タグの記述
ネストされているタグが、その親タグに定義されているプロパティを暗黙的に使用するよう設計できます。たとえば、サンプル・コードの「SQL Query」(WebLogic Serverのsamples/examples/jsp/tagext/sqlディレクトリを参照)では、<sql:query>
タグが<sql:connection>
タグの内部にネストされています。queryタグは親スコープのconnectionタグを検索して、そのタグによって確立されたJDBC接続を使用します。
親スコープのタグを見つけるために、ネスト・タグはTagSupport
クラスの静的メソッドであるfindAncestorWithClass()
を使用します。次に、QueryTag
のサンプルから抜粋したコードを示します。
try { ConnectionTag connTag = (ConnectionTag) findAncestorWithClass(this, Class.forName("weblogic.taglib.sql.ConnectionTag")); } catch(ClassNotFoundException cnfe) { throw new JspException("Query tag connection "+ "attribute not nested "+ "within connection tag"); }
この例では、与えられたクラスに一致するタグ・ハンドラ・クラスを持つ最も近い親タグ・クラスが返されます。直系の親タグがこのタイプでなければ、さらにその親が調べられます。一致するタグが見つかるまでこの処理が繰り返され、それでも見つからない場合はClassNotFoundException
が送出されます。
カスタム・タグでこの機能を使用すれば、JSPページでのタグの構文と使い方を簡素化できます。