SIerだけど技術やりたいブログ

5年目のSIerのブログです

Springの@RequestScopeや@SessionScopeは結局どこに保存されるのか?我々は真相に迫った

ある日の出来事

SpringのBeanのスコープ、便利ですよね。ライフサイクル管理を任せられるのはDIコンテナを利用するメリットの大きなところだと思います。

いつもは何も考えずに以下のようにコーディングして、Springコンテナにスコープ管理を任せていました。

@Component
@SessionScope
public class User {}

が、ある日、ある疑問が…。このBeanって結局どこに格納されてるんだろう…@SessionScopeっていうくらいだからセッションに格納される?

我々は真相に迫った

まずはデバッグ

HttpSession#setAttribute(String name, Object value)にブレークを打ってみた。
そうすると、name="scopedTarget.user"のメソッド呼び出しで以下のスタックトレースが取得できた。equalsで比較したら同じインスタンスだった(厳密にいうと、今回は@Autowiredで取得できるbeanでcglibがかかるので、interfaceベースのproxyに変更してproxyを外したuserインスタンスとhttpSessionから取得したインスタンスを比較した)。

これはSessionScopeクラスがあやしそう。

"http-nio-8080-exec-1"@4,828 in group "main": RUNNING
setAttribute():137, StandardSessionFacade {org.apache.catalina.session}
setAttribute():172, ServletRequestAttributes {org.springframework.web.context.request}
get():45, AbstractRequestAttributesScope {org.springframework.web.context.request}
get():93, SessionScope {org.springframework.web.context.request}
doGetBean():340, AbstractBeanFactory {org.springframework.beans.factory.support}
getBean():197, AbstractBeanFactory {org.springframework.beans.factory.support}
getTarget():35, SimpleBeanTargetSource {org.springframework.aop.target}
getTargetObject():73, PrintRestController {com.example}
printOnlyScopeBean():62, PrintRestController {com.example}
print():54, PrintRestController {com.example}
print():30, PrintRestController {com.example}

Javadocを読んでみる

SessionScope(@SessionScopeとは違う)はScopeインタフェース(@Scopeとは違う)を実装したクラスのひとつ。そのほかにもRequestScopeなど、スコープに応じたクラスが存在するらしい。

そしてScopeインタフェースは、スコープごとのCRUD操作(SessionScopeの場合はbeanをセッションに永続化したりセッションから削除したりする)を定義する、と。

public interface Scope {
  /**
   * Return the object with the given name from the underlying scope,
   * {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
   * if not found in the underlying storage mechanism.
   * <p>This is the central operation of a Scope, and the only operation
   * that is absolutely required.
   * @param name the name of the object to retrieve
   * @param objectFactory the {@link ObjectFactory} to use to create the scoped
   * object if it is not present in the underlying storage mechanism
   * @return the desired object (never {@code null})
   * @throws IllegalStateException if the underlying scope is not currently active
   */
  Object get(String name, ObjectFactory<?> objectFactory);

  Object remove(String name);

Scope#getの具体的な処理はAbstractRequestAttributesScope#getで定義されている。永続化されてないbeanがあれば永続化する。

public abstract class AbstractRequestAttributesScope implements Scope {

  @Override
  public Object get(String name, ObjectFactory<?> objectFactory) {
    RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
    Object scopedObject = attributes.getAttribute(name, getScope());
    if (scopedObject == null) {
      scopedObject = objectFactory.getObject();
      attributes.setAttribute(name, scopedObject, getScope());
    }
    return scopedObject;
  }

永続化先への具体的な操作はRequestAttributesクラスで行われる。
デバッグしてみたら実装クラスにはServletRequestAttributesが利用されてた。

 /**
  * ..
  * <p>Relies on a thread-bound {@link RequestAttributes} instance, which
  * can be exported through {@link RequestContextListener},
  * {@link org.springframework.web.filter.RequestContextFilter} or
  * {@link org.springframework.web.servlet.DispatcherServlet}.
  * ..
  */
 public class SessionScope extends AbstractRequestAttributesScope {

確かにServletRequestAttributesでHttpSession#setAttributeが呼ばれていた。

public class ServletRequestAttributes extends AbstractRequestAttributes {
  @Override
  public void setAttribute(String name, Object value, int scope) {
    if (scope == SCOPE_REQUEST) {
      if (!isRequestActive()) {
        throw new IllegalStateException(
         "Cannot set request attribute - request is not active anymore!");
      }
      this.request.setAttribute(name, value);
    }
    else {
      HttpSession session = getSession(true);
      this.sessionAttributesToUpdate.remove(name);
      session.setAttribute(name, value);
    }
  }

@RequestScopeも似たような処理で、書き込み先はHttpServletRequestだった。

結論

Springの@RequestScopeや@SessionScopeはどこから来てどこへ行くのか?我々は真相に迫ったが、あまり驚きはなかった。

追加

調べたことがそのままspringのリファレンスに書いてた。ちゃんと読まないとダメですね。
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-scopes-custom