JSPでカスタムタグを作成する方法

なぜシンプルなタグが必要なのか?

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などの変数を使って以下のような機能を実現していました:

  1. JSPページの一部(タグ体)の実行を制御
  2. 全体のJSPページの実行を制御
  3. JSPページ内容の繰り返し実行
  4. 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でない状態に変更するとブラウザに表示されなくなります

タグ: JSP SimpleTag カスタムタグ タグハンドラ TLD

5月31日 01:48 投稿