読者です 読者をやめる 読者になる 読者になる

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以前でも発生している例もあるようです。