JSP式言語での発見

イヤ,単にあたしが無知なだけなんだがの.


Mapに対しては,'${map["key"]}'だけじゃなくて,'${map.key}'でもアクセスできる.つまり,

${header["host"]}
${header.host}

は同じ意味.でも,

${header["keep-alive"]}

はイイけど,

${header.keep-alive}

はダメ.
#当たり前だ.


あと,こんなコードは,

for (Iterator it = maps.keySet().iterator(); it.hasNext(); ) {
  Object key = it.next();
  %><%= maps.get(key) %><%
}

こう書ける.

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

でも,こう書いた方が楽.

<c:forEach var="item" items="${header}">
  ${item.key} : ${item.value}
</c:forEach>

JSTL+式言語的には,こっちがスマートっぽいんだが,Map.Entryを使うってのはイディオム的に馴染みがないので,ちょっとイヤだ.

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

結構ノーチェックだけどServlet2.5やJSP2.1って,そこそこ変わってんのね.

なんかもう当たり前のように"Unified EL"って書いてあるし.

JSP2.1の隠れた目玉機能は,pageディレクティブのtrimDirectiveWhitespaces属性と見た.

<%@page trimDirectiveWhitespaces="true"%>

効果のほどは推して知るべし.

Glassfishはなかなか侮れない

というか,Glassfishを選ばない理由がない.
軽いし,速いし,NetBeansにバンドルされてるしで,ダメ出しする理由がないのだ.


難点を言えば,ログのヘッダ部分がクソ長い事くらいだろう.
これ,NetBeansじゃ気にならないけど,IDEAとかだと非常に気になるのよね.
だって,こんなに(↓)長いのだ.

[#|2006-12-13T13:37:40.250+0900|INFO|sun-appserver-pe9.0|javax.enterprise.system.stream.out|_ThreadID=15;_ThreadName=httpWorkerThread-8080-0;|
TestELResolver.getValue:com.sun.el.lang.EvaluationContext@1a8332f,{d=444, a=111, c=333, b=222},keySet|#]

あと,Glassfishって名前が良いよね.
"SunONE Application Server"とか"Sun Java System Application Server"とかだと見向きする気もなかったのに,まったくもってミーハー丸出しである.:-D


V2になると,さらに速くなるそうだ.
GlassFish v2b26/AS9.1、起動/停止めちゃくちゃ高速化したみたい

YourKit Java Profiler 6.0リリース

出たよ.半年前に5.5にアップグレードしたばかりだから,そのままライセンス使えると思う(一応,問い合わせしとこう).
"J2EE high-level profiling"がうれしい.


びみょうに使いこなせていないメモリプロファイルなんだけど,各インスタンスのサイズが分かるってのは便利だ.Retained Sizeで関連するオブジェクトの総サイズもわかるしの.
「メモリあんまり使うなー」とか言うのは簡単だけど,「じゃあ,このクラス(インスタンス)ってなんぼメモリ喰うの?」という問いに答えるのは難しい.そんな時こそ,メモリプロファイルが役に立つのだ.
#そんな細かい事,いちいち答える必要があるかどうかは置いておく.;-)


そうそう.あと,JVMTI経由だとすこぶる楽ね.JDK6なんかJVMPI載ってないんだし,実行環境だけは,とっととJDK5にスイッチしたいという気持ちは強い.