はじめに
SessionスコープのBeanを利用したかったので、@SessionScope
を使った実装をしたところ、デッドループが発生してしまったので、その原因と解決方法を調べた際の記録です。
SessionスコープのBeanを利用したかったが、デッドループが発生
SessionスコープのBeanを利用して、そのSession中で有効なBean(例えば、ECサイトのショッピングカート)を作るため、@Component
と@SessionScope
を付与したクラス(仮に「Cart」クラスとします。)を作成しました。
そして、セッション開始時にCartクラスが持つカートの中の商品を表すリストitemList
を初期化するため、HttpSessionListener
においてArrayList
を生成して初期化をしようとしました。
ところが、その初期化をする処理が何度も呼び出され(HttpSessionListener
のsessionCreated
が何度も呼び出され)、デッドループが発生してしまいました。
原因はプロキシ?
ここでかなりハマって色々と調べてみると、原因はSessionスコープのBeanをラップするプロキシが関係しているのでは、ということが分かりました。
SessionスコープのBeanをインジェクションするときに、実際のCartクラスのインスタンスが注入されるのではなく、それをラップするプロキシが注入されます。
そして、プロキシを介してCartクラスが呼び出されます。
その際、セッション内でまだ完全に初期化されていないCartクラスを呼び出すと、プロキシはCartの生成を行います。
その状態でCartクラスを参照する処理が行われると、まだCartクラスが完全に初期化されていないため、プロキシが再度Cartを生成し、ということを繰り返してしまい、デッドループに陥る、という仕組みのようです。
まだ理解が浅いですが、上記がデッドループの原因と考えられます。
解決策
セッションスコープを使用せず、HttpSessionに直接Cartインスタンスを格納します。
具体的には、ユーザが最初にアクセスするページのコントローラにおいて、Cartがセッションに格納されていなければ(初回のアクセスであれば)、新しくCartをnewし、それをHttpSessionのsetAttribute()
メソッドを用いてセッションに格納します。
これで、Springのプロキシを使用しなくて良いため、デッドループが解消されます。
おわりに
Springのスコープ管理がなかなか掴めません…セッションスコープのBeanなので、セッション開始時に初期化した方がきれいかなと思い、最初の方法で進めたかったのですが、最終的にコントローラにベタ書きすることになりました。
もし他に良い方法があれば、ご教示いただきたいです。