なぜシンプルなタグが必要なのか?
JSP 2.0では、タグの開発複雑度を簡略化するために、SimpleTagインターフェースというより簡単で使いやすい方法を導入しました。
一般的に、SimpleTagインターフェースを実装したタグをシンプルなタグと呼びます。
SimpleTagインターフェース
- まず、ソースコードを見てみましょう:
public interface SimpleTag extends JspTag {
void doTag() throws JspException, IOException;
void setParent(JspTag var1);
JspTag getParent();
void setJspContext(JspContext var1);
void setJspBody(JspFragment var1);
}
- setParent()やgetParent()メソッドについては説明しません。残りの3つのメソッドを見てみましょう:
void doTag() throws JspException, IOException;
void setJspContext(JspContext var1);
void setJspBody(JspFragment var1);
- 明らかに:
- doTag()はロジックを記述する場所です
- setJspContext(JspContext var1)はPageContextオブジェクトをタグハンドラに渡します(PageContextはJspContextのサブクラス)
- setJspBody(JspFragment var1)はタグ体を表すJspFragmentオブジェクトをタグハンドラに渡します
クイックスタート
- 通常、タグハンドラはSimpleTagSupportクラス(SimpleTagを実装)を継承して実装します
- 以下にクイックスタートを示します:
- 目標:文字列形式で指定された日付フォーマットを表示する
- タグハンドラクラス:
public class DateFormatter extends SimpleTagSupport {
private String pattern;
@Override
public void doTag() throws JspException, IOException {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
getJspContext().getOut().write(sdf.format(new Date()));
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
}
- TLDファイル:
<tag>
<name>formatDate</name>
<tag-class>tag.DateFormatter</tag-class>
<body-content>tagdependent</body-content>
<attribute>
<name>pattern</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
- 効果:
- シンプルなタグの利点は、doStartTagやdoEndTag、SKIP_BODYなどのメソッドやプロパティを気にする必要がないことです
- シンプルなタグではdoTag()メソッドだけで済みます
SimpleTagSupportクラスの実行順序:
- ①WEBコンテナはタグハンドラオブジェクトのsetJspContextメソッドを呼び出し、JSPページを表すpageContextオブジェクトをタグハンドラに渡します
- ②WEBコンテナはタグハンドラオブジェクトのsetParentメソッドを呼び出し、親タグハンドラオブジェクトをこのタグハンドラに渡します(注意:タグに親タグがある場合のみWEBコンテナがこのメソッドを呼び出します)
- ③タグに属性が設定されている場合、コンテナは各属性に対応するsetterメソッドを呼び出し、属性値をタグハンドラに渡します。属性値がEL式やスクリプト式の場合、WEBコンテナはまず式の値を計算し、その値をタグハンドラに渡します
- ④シンプルなタグにタグ体がある場合、コンテナはsetJspBodyメソッドを呼び出し、タグ体を表すJspFragmentオブジェクトを渡します
- ⑤タグを実行する際、コンテナはタグハンドラのdoTag()メソッドを呼び出し、開発者はメソッド内でJspFragmentオブジェクトを操作することで、タグ体の実行、繰り返し、変更を実現できます
シンプルタグの深掘り
従来のタグではSKIP_BODYやSKIP_PAGEなどの変数を使って以下のような機能を実現していました:
- JSPページの一部(タグ体)の実行を制御
- 全体のJSPページの実行を制御
- JSPページ内容の繰り返し実行
- JSPページ内容の出力変更
- シンプルなタグにはこれらの変数はなく、どうやってこれらの機能を実現するのでしょうか?
- doTagメソッド内でjavax.servlet.jsp.SkipPageException例外を投げることで、WEBコンテナにJSPページの終了タグ後の内容を実行しないよう通知できます。これは従来のタグのdoEndTagメソッドでTag.SKIP_PAGE定数を返すのと同じ効果です
以下にテストコードを示します:
throw new SkipPageException();
- 効果:
- 他の機能については後述します
タグ体付きシンプルタグ
-
SimpleTagSupportもタグ体を持つことができますが、従来のタグとの処理方法は異なります
-
従来のタグ:タグ体の内容をsetBodyContent()でBodyContentオブジェクトに注入します
-
シンプルタグ:JspFragmentオブジェクトを使って実現します
JspFragmentオブジェクトのソースコードを見てみましょう:
public abstract class JspFragment {
public JspFragment() {
}
public abstract void invoke(Writer var1) throws JspException, IOException;
public abstract JspContext getJspContext();
}
JspFragmentオブジェクトは非常に単純で、重要なのはinvoke(Writer var1)メソッド(JspContextオブジェクトを取得することは重要ではありません、タグディスクリプタから取得できます)
public abstract void invoke(java.io.Writer out) :
-
JspFragmentオブジェクトが表すJSPコード片を実行します
-
パラメータoutはJspFragmentオブジェクトの実行結果をどの出力ストリームに書き込むかを指定します。outにnullを渡すと、JspContext.getOut()メソッドで返される出力ストリームに書き込まれます(つまり、ブラウザに出力されます)
-
タグハンドラクラスのコード:
public class DateFormatter extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
//タグ体を表すオブジェクトを取得
JspFragment fragment = getJspBody();
//invokeメソッドのパラメータはWriter型、nullの場合、JspWriter()に相当し、タグ体のデータをブラウザに出力します
fragment.invoke(null);
}
}
- 効果:
- タグ体の内容はJspFragmentオブジェクトのinvoke()メソッドを通じてブラウザに出力されるため、invoke()メソッドを制御すれば、何でもできます
- つまり:
- invoke()メソッドを呼び出さない場合、タグ体の内容はブラウザに出力されません
- invoke()メソッドを2回呼び出すと、タグ体の内容が2回実行されます
- タグハンドラ内でタグ体の内容を変更したい場合は、invokeメソッド呼び出し時に結果データを取得できる出力ストリームオブジェクト(StringWriterなど)を指定し、タグ体の実行結果をその出力ストリームに書き込み、その後データを取得して変更し、再び出力すればよいです
実際に試してみましょう:
- invoke()メソッドを呼び出さない場合
public void doTag() throws JspException, IOException {
//タグ体を表すオブジェクトを取得
JspFragment fragment = getJspBody();
//fragment.invoke(null);
}
- タグ体の内容は出力されません
- invoke()メソッドを2回呼び出す場合
public void doTag() throws JspException, IOException {
//タグ体を表すオブジェクトを取得
JspFragment fragment = getJspBody();
fragment.invoke(null);
fragment.invoke(null);
}
- タグ体の内容が2回出力されます!
- invoke()メソッドに別の出力ストリーム(StringWriter)を指定し、タグ体の内容をストリームオブジェクトに書き込み、ストリームオブジェクトからデータを取り出して変更し、再度出力します
//タグ体を表すオブジェクトを取得
JspFragment fragment = getJspBody();
//文字列を保存できるWriterオブジェクトを作成
StringWriter stringWriter = new StringWriter();
//invoke()メソッドでタグ体のデータをストリームオブジェクトに書き込み
fragment.invoke(stringWriter);
//ストリームオブジェクトのデータを取り出し、それはタグ体の内容です
String value = stringWriter.toString();
//データを大文字に変換し、ブラウザに出力
getJspContext().getOut().write(value.toUpperCase());
- タグ体の内容が大文字になりました!
従来のタグが実現できる機能はすべてシンプルタグでも実現でき、さらに簡単です
カスタムタグの応用
シンプルタグを学んだので、シンプルタグを使って開発しましょう!
防止盗リンク
requestオブジェクトの説明で、防止盗リンクの実装方法を学習しました。今回はタグを使って防止盗リンクを実現します!
シナリオ:1.jspページは海賊王リソース、2.jspページは不正アクセスを示す、index1.jspページはホームぺージです。他人が海賊王リソースを閲覧するにはホームぺージからアクセスする必要があります。それ以外は不正アクセスとみなされます
- タグハンドラのコード:
@Override
public void doTag() throws JspException, IOException {
//柔軟性を高めるために、サイト設定やリソース設定をタグ属性として渡すことができます!
//requestオブジェクトを取得するために、JspContextのサブクラスPageContextが必要です
PageContext pageContext = (PageContext) this.getJspContext();
//requestオブジェクトを取得
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
//Refererを取得
String referer = request.getHeader("Referer");
//responseオブジェクトを取得、不正アクセスの場合、他のページにリダイレクトします
HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
//不正アクセス!
if (referer == null || !referer.startsWith("http://localhost:8080/zhongfucheng")) {
//2.jspで不正アクセスを示します
response.sendRedirect("/zhongfucheng/2.jsp");
//ページの下の内容を実行しないようにして、ページを保護します
throw new SkipPageException();
}
}
- 1.jspのコード:
<zhongfucheng:Referer/>
<html>
<head>
<title></title>
</head>
<body>
海賊王最新リソース
</body>
</html>
- index1.jspのコード:
<h1>これはホームぺージです!</h1>
<a href="${pageContext.request.contextPath}/1.jsp"> 海賊王最新リソース</a>
- 2.jspのコード:
<body>
あなたは不正アクセスしています!!!!!!
</body>
- 最初に1.jspを直接アクセスするとRefererは空で不正アクセスになります。次にホームぺージからアクセスすると1.jspが表示されます
ifタグ
JSTLで<c:if/>タグを使用したことがあります。今やカスタムタグを学んだので、JSTLのifタグに似たタグを開発できます!
ifタグなので、属性とタグ体を持つタグ(true/falseを判断してタグ体の内容を実行するかどうか)が必要です
- タグハンドラコード:
public class ConditionalTag extends SimpleTagSupport {
//Boolean型の変数を定義
boolean condition;
@Override
public void doTag() throws JspException, IOException {
//タグ体の内容を表すオブジェクトを取得
JspFragment fragment = this.getJspBody();
//trueの場合のみタグ体の内容を実行
if (condition == true) {
fragment.invoke(null);
}
}
public boolean isCondition() {
return condition;
}
public void setCondition(boolean condition) {
this.condition = condition;
}
}
- TLDファイルのコード:
<tag>
<name>if</name>
<tag-class>tag.ConditionalTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>condition</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
-
userというドメインオブジェクト属性が存在しないので、userはnullになります:
-
userをnullでない状態に変更するとブラウザに表示されなくなります