ある日の出来事
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