ある日の出来事
SpringのBeanのスコープ、便利ですよね。ライフサイクル管理を任せられるのはDIコンテナを利用するメリットの大きなところだと思います。
いつも私は、何も考えずに以下のようにコーディングして、Springコンテナにスコープ管理を任せていました。
@Component @SessionScope public class User implements Serializable {}
そしてある日、ふと疑問が…このBeanって結局どこに格納されてるんだろう…@SessionScope
っていうくらいだからセッションに格納される?
結論
実行環境がServletの場合、
@SessionScope
はHttpSessionに格納される@RequestScope
はHttpServletRequestに格納される
Spring WebFluxの場合、@SessionScope
や@RequestScope
はサポートされていない。いずれもスレッドローカルに値を管理する仕組みであり、実行スレッドが固定されない環境では実現が難しいため。
参考 stack overflow
我々は真相に迫った
検証環境
pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> .. <\dependencies>
まずはデバッグ
アノテーションはただのマーカーなので、実際にアノテーションを処理するクラスを探したい。しかし、アノテーション自体にはブレークポイントが打てないため、探すのがけっこう難しい。そのため、まずはHttpSession#setAttribute(String name, Object value)
にあたりを付けて、ブレークポイントを打ってみた。
そうすると、以下のスタックトレースが取得できた。
うーん、SessionScopeクラスがあやしそう。
Javadocを読んでみる
SessionScope(@SessionScope
とは違うパッケージの)はScopeインタフェース(@Scope
とは違う)を実装したクラスのひとつ。
またScopeインタフェースは、データのCRUD操作を定義している。
参考 SessionScope Javadoc
参考 Scope Javadoc
ソースコードを読んでみる
Scopeは4つのメソッドを持っている。
public interface Scope { Object get(String name, ObjectFactory<?> objectFactory); Object remove(String name); Object resolveContextualObject(String key); String getConversationId(); }
Scope#get
の実装はAbstractRequestAttributesScope#get
で以下のように定義されている。
参考 AbstractRequestAttributesScope#get
永続先への具体的な操作はRequestAttributesクラスで行われる。デバッグしてみたら実装クラスにはServletRequestAttributesが利用されていた。
ServletRequestAttributesを確認すると、HttpSession#setAttributeを呼んでいた。
参考 ServletRequestAttributes
@RequestScope
も似た処理で、書き込み先はHttpServletRequestだった。
終わりに
Springの@RequestScope
や@SessionScope
はどこから来てどこへ行くのか?我々は真相に迫ったが、あまり驚きはなかった。また、調べたことがそのままspringのリファレンスに書いてあった。リファレンスしっかり読もう…(反省)
参考 1.5.5. Custom scopes