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で式言語が登場したときに遅延評価式を採用していれば,こんな混迷状況にはならなかったのに,と思う。
#とは言え,その前にプリントレットがあったからナー。