JSP2.1の遅延評価がなんとなくわかった。

開眼!!


えー,ぶっちゃけて言えば,

  • 即時評価式:${} ... タグには式を評価した結果が渡される。
  • 遅延評価式:#{} ... タグには式そのものが渡される。

ということらしい。


試しにこんなサンプル作って検証。

<%@ page contentType="text/html; charset=Windows-31J" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="t" uri="/WEB-INF/test-tag.tld" %>
<jsp:useBean id="list" class="java.util.ArrayList" />
<%
  list.add("aaa");
  list.add("bbb");
  list.add("ccc");
%>
<html>
<body>
<c:forEach var="item" items="${list}" >
  <%-- 即時評価式 --%>
  <t:test normal="${item}" />,
  <%-- 遅延評価式 --%>
  <t:test deferred="#{item}" /><br>
</c:forEach>
</body>
</html>

TLDは,こんなので,

<taglib>
  <tlib-version>1.0</tlib-version>
  <jsp-version>2.1</jsp-version>
  <short-name>TestTagLib</short-name>

  <tag>
    <name>test</name>
    <tag-class>example.TestTag</tag-class>
    <body-content>EMPTY</body-content>
    <attribute>
      <name>normal</name>
      <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
      <name>deferred</name>
      <deferred-value>
        <type>java.lang.String</type>
      </deferred-value>
    </attribute>
  </tag>
</taglib>

カスタムタグ本体はこんなの。

package example;

import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.JspException;
import javax.el.ValueExpression;
import javax.el.ELContext;
import java.io.IOException;

public class TestTag  extends SimpleTagSupport {
    private String normal;
    private ValueExpression deferred;

    public void setNormal(String _normal) {
        System.out.println("_normal = " + _normal);
        normal = _normal;
    }

    public void setDeferred(ValueExpression _deferred) {
        System.out.println("_deferred1 = " + _deferred.getExpressionString());
        System.out.println("_deferred2 = " + _deferred.getExpectedType());
        deferred = _deferred;
    }

    public void doTag() throws JspException, IOException {
        String output = "**EMPTY**";
        if (normal != null) {
            output = normal;
        }
        if (deferred != null) {
            ELContext ctx = getJspContext().getELContext();
            output = (String)deferred.getValue(ctx);
        }
        getJspContext().getOut().print(output);
    }
}

表示される結果は同じなんだけど,評価のされかたが異なる。


即時評価式を受け付ける属性:normalは,まず即時評価式を受け入れることをTLDで宣言しておく(rtexpvalue)。normal属性のミューテータ:setNormal()は,評価結果を受け入れるのでStringとかObjectを受け取る(正しくは,TLDのtypeに従う)。
これは,JSP1.2からある仕組みなので,いまさらどうでもいい。


問題は,遅延評価式を受け付ける属性:deferred。遅延評価式の受け入れ宣言は,TLDのdeferred-valueで行う(deferred-methodってのもあるけど,今回は割愛)。
deferred-valueの子要素typeで,評価結果の型を指定するが,デフォルト(Object)でも特に問題ないだろう。


deferred属性のミューテータ:setDeferred()は,遅延評価式を受け入れるため,ValueExpressionじゃないとイケナイ。このValueExpressionに遅延評価式を含む属性値がそのまま格納されるの,あとは必要なときに評価するなり,他の利用をするなり好きにしてくれ,となる。


ちなみに,上記JSPを実行すると,こんなデバッグライトが出力される。

_normal = aaa
_deferred1 = #{item}
_deferred2 = class java.lang.String
_normal = bbb
_deferred1 = #{item}
_deferred2 = class java.lang.String
_normal = ccc
_deferred1 = #{item}
_deferred2 = class java.lang.String


ここで問題。
遅延評価式の受け入れが可能な属性:deferredに,以下のような記述をおこなったら,どうなるだろう。

<t:test deferred="${item}" />

答えは,エラーでこける。


なぜか?


属性:deferredは,遅延評価式の受け入れは宣言してるが,即時評価式の受け入れを宣言していないからだ。意味あるかどうかは別だけど,こんなのは可。

<t:test deferred="こんにちは" />


まー,当たり前と言えば,当たり前な結末だナー。
もし,ある属性が即時評価式・遅延評価式の両方の受け入れを行うのであれば,こんな感じにしないとダメっぽい。


まずTLD

  <tag>
    <name>test</name>
       :
    <attribute>
      <name>deferred</name>

      <rtexprvalue>true</rtexprvalue>
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      <deferred-value>
        <type>java.lang.String</type>
      </deferred-value>
    </attribute>
  </tag>

続いてカスタムタグ。

    public void setDeferred(Object _deferred) {
        System.out.println("_DEFERRED = " + _deferred);
        normal = String.valueOf(_deferred);
    }
    public void setDeferred(ValueExpression _deferred) {
        :

不思議なことに,deferred-valueとrtexprvalueが共存する場合,attributeの子要素typeは無視されるみたいだ。実際,typeにStringと宣言して「setDeferred(String _deferred)」って作ったら,「Objectを受けるミューテータがない」って怒られた。:-(


これらの仕込みをして,ようやっとこんなJSPが記述可能になるのだ。

<c:forEach var="item" items="${list}" >
  <t:test deferred="${item}" />
  <t:test deferred="#{item}" /><br>
</c:forEach>

...こんな仕込みは,やっぱりイヤだ。


んで結論。
タグ作る側にとってみれば,遅延評価式のほうが使いでがある。そう思うに,今さら感満々なんだが,JSP1.2で式言語が登場したときに遅延評価式を採用していれば,こんな混迷状況にはならなかったのに,と思う。
#とは言え,その前にプリントレットがあったからナー。