この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

iteratorタグの落とし穴

Struts2のタグは、リストをforeachループでまわしてそれぞれの要素を操作する場合に使用するタグです。
ごく簡単なWebアプリケーションを作成する場合ですら、必須のタグであると言って良いです。たぶん。


しかしながら、にはある落とし穴が存在します。

落とし穴?

先日の日記では、タグの変数スコープが事実上グローバルであるために、いろいろ問題が起こることを説明しました。


実はでもこれと同様の問題が発生します。次のコードを見てください。


main.jsp

<%@ page pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>


<s:set var="hoge" value="1" />

(hoge in main.jsp) = <s:property value="#hoge" />

<%-- <s:component>を使っても結果は同じ --%>
<s:include src="./sub.jsp" />

(hoge in main.jsp) = <s:property value="#hoge" />


sub.jsp

<%@ page pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>

<s:iterator var="hoge" value="{1, 2, 3}">
  (hoge in sub.jsp) = <s:property value="#hoge" />
</s:iterator>


このとき、main.jspを実行した結果は次の通りです。

(hoge in main.jsp) = 1

  (hoge in sub.jsp) = 1
  (hoge in sub.jsp) = 2
  (hoge in sub.jsp) = 3

(hoge in main.jsp) = 3  ← hogeが3になっている


この通り、変数hogeの値が呼び出し先(sub.jsp)で勝手に書き変わってしまいます。
これは、のvarで宣言したループ変数が、を使ってセットした変数と同じ扱いを受けるために発生する現象です。


この問題は、変数名の付け方に配慮すれば回避できる問題ではありますが、それはそれで以下のような問題もあります。

  • 一人でJSPを全部書くのならともかく、実際の案件では全ての変数名を管理するのは不可能だ。
  • かといって、何らかのprefixを付けることで名前の重複を回避する、って戦略は少々カッコ悪い。(例えば次のように)
    • main.jspのhoge → main_hoge
    • sub.jspのhoge → sub_hoge

僕の選んだ解決策

この問題は結局、タグのvarで宣言したループ変数がのタグ内部だけで有効なスコープを持てば解決するのです。

これは、Javaで言うと、

for (int i = 0; i < ary.length; i++) {
    ...
}

このコードのループ変数iのスコープがforの内側だけで有効なことと同一です。
これはモダンなプログラミング言語では当たり前ですね。


というわけで、先日作成したカスタムタグを応用して、と同等の機能を提供しつつ、
ローカル変数のスコープを提供するタグを作りました。
次のように使います。


main.jsp

<%@ page pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<%@ taglib uri="/r42fw-tags" prefix="r"%>


<r:block>
    <r:set-local var="hoge" value="1" />

    (hoge in main.jsp) = <s:property value="#hoge" />

    <s:include src="./sub.jsp" />

    (hoge in main.jsp) = <s:property value="#hoge" />
</r:block>


sub.jsp

<%@ page pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<%@ taglib uri="/r42fw-tags" prefix="r"%>


<r:foreach var="hoge" value="{1, 2, 3}">
  (hoge in sub.jsp) = <s:property value="#hoge" />
</r:foreach>


このとき、main.jspを実行した結果は次の通りです。

(hoge in main.jsp) = 1

  (hoge in sub.jsp) = 1
  (hoge in sub.jsp) = 2
  (hoge in sub.jsp) = 3

(hoge in main.jsp) = 1


この通り、main.jspにおけるhogeの値は、呼び出し先JSP(sub.jsp)の副作用を受けていません。

公開してみる

ところで、今回作成したタグのコードと前回のコードを合わせて、オープンソース( と言っても大したことないですが(-_-) )として公開するつもりです。
Sourceforgeにて配布準備中です。公開までしばらくお待ちください。



(追記)
配布開始しました。


SourceForgeでプロジェクトをホストするのは初めてなので少しおかしな部分もあるかもしれませんが、そのうち直します。


(09/07/02 追記)
ファイルの配布については、こちらも参照してください。
http://d.hatena.ne.jp/takahashikzn/20090702