WicketとSessionについてのまとめ
Wicket 1.4.13にアップグレードしてから、StackOverflowError()が頻発する現象が発生。
いろいろ試してみたけれどもどうにも原因がわからず、途方にくれていたところ、id:mdgw 先輩と id:t_yano さんに助けていただきました。
お二方とも、重ね重ねありがとうございました。いつもご助言をいただきまして恐縮です。
ご助言の内容も含めて、簡単にまとめておきたいと思います。
何をしていたか
全てのWebPageクラスの親となるクラス(Page0)を作り、これを継承したWebPageを各画面用に作成していました。
例として、
//Page0.java public class Page0 extends WebPage() { protected MySession session; public Page0 { session = (MySession)Session.get(); IModel ldm = new LoadableDetachableModel() { protected String load() { String name = session.getName(); //(略) } } } }
//Page1.java public class Page1 extends Page0 { public Page1 { add(new Label("name", new PropertyModel(session, "name")); } }
このようにしておけば、session変数を子クラスが使えるよね、と思っていました。
しかし、Wicket 1.4.13 にアップデートした後、突然、特にAjaxLinkのonSubmit実行時などで、
11/04 23:19:39.130 TP-Processor42 ERROR org.apache.wicket.Session - Exception when detaching/serializing page java.lang.StackOverflowError: null at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:266) ~[na:1.6.0_22] at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1114) ~[na:1.6.0_22] at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1518) ~[na:1.6.0_22] at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1483) ~[na:1.6.0_22] at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1400) ~[na:1.6.0_22] (略)
といったStackOverflowErrorが頻発し、サーバが停止する事態に、
スタックサイズなどを調整するも効果が無く、本家のUsers Forumなども漁ってみて、PropertyModelの対象オブジェクトにSessionを用いるとStackOverflowが起こる といった記事にも行きあったったのですが、具体例として出ていたPropertyModelにだけ注視してしまい、いまいち理解が足りていませんでした。
どうすれば良いか
id:t_yanoさんにポイントを数点教えて頂きました。
1. 匿名クラスとして利用するModelでは、フィールドにSessionを格納しない
//駄目な例 IModel<String> ldm1 = new LoadableDetachableModel<String>() { MySession session; @Override protected String load() { session = (MySession)Session.get(); //(略) } };
//良い例 IModel<String> ldm1 = new LoadableDetachableModel<String>() { @Override protected String load() { //本当に使いたいメソッドやタイミングでSessionを生成する MySession session = (MySession)Session.get(); //(略) } };
2. PropertyModelの対象にSessionを使いたいときは、プロパティの指定に注意する
//駄目な例 MySession session = session.get(); IModel<String> pm1 = new PropertyModel(session, "name");
//良い例 IModel<String> pm2 = new PropertyModel(this, "session.property");
3. コンストラクタ変数でSessionの結果を渡したり、Session.get()の結果の入ったローカル変数を匿名クラスから使ってはいけない。
//駄目な例1 MySession session = (MySession)session.get(); setResponsePage(new Page1(session));
//駄目な例2 final MySession session = (MySession)session.get(); IModel<String> ldm1 = new LoadableDetachableModel<String>() { @Override protected String load() { String name = session.getName(); //(略) } };
最初のPage0, Page1の例も、
//Page0.java public class Page0 extends WebPage() { public Page0 { IModel ldm = new LoadableDetachableModel() { protected String load() { String name = ((PortalSession)Session.get()).getName(); //(略) } } }
//Page1.java public class Page1 extends Page0 { public Page1 { add(new Label("name", new PropertyModel(this, "session.name")); } }
とすることで、上手く動作するようになりました。
なお、私の環境では、Wicket 1.4.12で間違った書き方をしていてもエラーは出ないのですが、本家のUsers Forumを見ると、1.4.10など、1.4.13以前でも発生している例もあるようです。