JSP式言語の"#{}"ってなんやねん? その2

遅延評価だってのはわかった.もしかして,JSTLでも"method expressions"できるのかと思い,こんなコードを試してみたよ.

<c:forEach var="item" items="#{map.keySet.iterator}">
  ${item}
</c:forEach>

結果は,全然ダメだった.なんだよ,コンチキショウ.:-(


話はちょっと横道に逸れるが,JSFとの兼ね合いで"#{}"なる表記法が出てきたようだが,どうやら"${}"や"#{}"を総称して"Unified EL"と呼ぶらしい.

もっともらしい名前がついたのはイイが,JSP2.0→JSP2.1でヘンなふるいをかけられた感も拭えん.さりげないように大したことしてる気がするのは気のせいか??
未だ"${}"と"#{}"の明確な違いをモデル化出来ずにいるので,他人にJSP2.1を勧めたくないなぁ.
#だって説明できんもん.:-(


話を戻す.どうも,あたしがやりたいことは式言語の表記でなんとかなる問題ではないようだ.だからと言って,まったく出来ないワケでもない.
どうするかっていうと,"Pluggable Resolver Mechanism"ってのに手を出すと出来るようだ.


というワケで早速,ELResolverを自作してみる.超やっつけ実装なので,細かい所は気にしないこと.:-)

public class TestELResolver extends ELResolver {
    public Object getValue(ELContext context, Object base, Object property) {
        if (property.equals("keySet")) {
            // いろいろ面倒なので,"keySet"だけに反応することにした
            context.setPropertyResolved(true);
            return ((Map)base).keySet();
        }
        return null;
    }
:

自作ELResolverの登録はServletContextListenerで行うらしい(初めて使った).

public class TestListener implements ServletContextListener {
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext context = servletContextEvent.getServletContext();

        JspApplicationContext jspContext =
                JspFactory.getDefaultFactory().getJspApplicationContext(context);
        jspContext.addELResolver(new TestELResolver());
    }
:
== web.xml ==
  <listener>
    <listener-class>example.TestListener</listener-class>
  </listener>

んで,こんな風に使う.

<c:forEach var="item" items="${map.keySet}">
  ${item} : ${map[item]}
</c:forEach>

ちなみにこれ,遅延評価(#{})でも即時評価(${})でも同じだった.
ただね,ELResolverの呼ばれ方に違いがあったよ.


${map}には,4つの要素が入っているとしよう.
即時評価(${})の時のELResolverの呼び出し回数.

TestELResolver.getValue:com.sun.el.lang.EvaluationContext@759e62,null,maps|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@759e62,{d=444, a=111, c=333, b=222},keySet|#]
↑
たぶん,ここまでが <c:forEach var="item" items="${map.keySet}"> の評価.
あとは,${item}のループ分(4回)
↓
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1f16253,null,item|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@6e685a,null,item|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@851e2b,null,item|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@17fd2c3,null,item|#]

こっちは遅延評価(#{})の時のELResolverの呼び出し回数.

TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1689fee,null,maps|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1689fee,{d=444, a=111, c=333, b=222},keySet|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1dbeafc,null,maps|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1dbeafc,{d=444, a=111, c=333, b=222},keySet|#]
↑
たぶん,ここまでが <c:forEach var="item" items="${map.keySet}"> の評価(ELContextのインスタンス値に注目).
あとは,${item}のループ分(2セット×4回)
↓
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1207688,null,maps|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1207688,{d=444, a=111, c=333, b=222},keySet|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@45e380,null,maps|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@45e380,{d=444, a=111, c=333, b=222},keySet|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@5b1f02,null,maps|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@5b1f02,{d=444, a=111, c=333, b=222},keySet|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@50c5b8,null,maps|#]
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@50c5b8,{d=444, a=111, c=333, b=222},keySet|#]

なんとなく遅延評価の振る舞いを垣間見た気がする.


まとめよう.
試すだけ試したけど,ELResolverによる解決は意図するところではないヨ.
つうか,こんな地雷をわざわざ仕込む方がどうかしてる.:-P