GuiceのSessionScoped

2019年7月29日

Guiceの@SesshonScopedを使うと、「セッション」ごとに同一のデータを保持することができる。

セッションとは?

前提としてこのときの「セッション」というのは、いわゆるHttpSessionであり、つまりサーブレットコンテナがサーブレット仕様の一部としてサポートする「セッション」のことだ。「自前のセッション」ではない(もしそういうものを作るのであれば)。Guiceの@SessionScopedはこのサーブレット仕様のセッションと協調するわけだ。

これについては以下に詳しい。

セッションは自動作成される

前述の参考URLでは、セッションが無い場合に作るか否かは本来選択できるのだが、@SessionScopedを使用した場合には選択できない。@SessionScopedのオブジェクトはセッションにセーブされるからである。

つまり、@SessionScopedのオブジェクトが要求されたとき、Guice内部ではこんな処理をしている。

  • セッションがなければ強制的に作成する。
  • セッションに対してgetAttribute()して、オブジェクトがあるかを調べ、あればそれを返す。
  • なければ新たなオブジェクトを作成して、setAttributeでセッションに保存する。

@SessionScopedのオブジェクトはHttpSessionがなければ話が始まらないため、有無を言わさずセッションは作成されてしまう。したがって、@SessionScopedを使う場合は「必ずセッションがある」と仮定してよい。

@SessionScopedのオブジェクトは小さく、Serializableでなくてはいけない。

サーブレットコンテナ(Tomcat、Jetty)等によってその方法は異なるのだが、HttpSessionの内容は永続化される。つまり、ファイルなり、データベースなりに保持される。そうでないと、サーブレットコンテナを再起動した場合にセッションがすべてなくなってしまうからである。

※もちろん、何の設定もしなければ、TomcatにしてもJettyにしても永続化されない。

このとき、setSttributeでHttpSessionに設定された@SessionScopedのオブジェクトも永続化されるのだが、当然のことながら、Javaオブジェクトが永続化されるにはSerializableでなくてはいけない。

なおかつ、ここに巨大なデータを保持してはいけない。せいぜいログイン情報だろうから、DB上のユーザレコードのIDなり、権限の種類なりだろう。極力小さなデータにしておく必要がある。

実際の例

以下は、GuiceServletContextListener、GuiceFilterをインストールしている例で、これが無いとおそらくは適切にされないと思われる。

ContentWriterのコードは示していないが、単にtext/plainをユーザ側に戻すだけである。


import java.io.*; import java.util.*; import javax.inject.*; import javax.servlet.http.*; import com.google.inject.servlet.*; @Singleton public class TestServlet extends HttpServlet { @Inject private ContentWriter contentWriter; @Inject private Provider<LoginInfo>loginInfoProvider; private Random random = new Random(); @Override public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { LoginInfo loginInfo = loginInfoProvider.get(); String pathInfo = req.getPathInfo(); if (pathInfo.equals("/login")) { if (loginInfo.userId == null) { loginInfo.userId = random.nextLong(); contentWriter.writePlain(res, "ログインしました"); return; } contentWriter.writePlain(res, "既にログインしてます"); return; } if (pathInfo.equals("/logout")) { if (loginInfo.userId == null) { contentWriter.writePlain(res, "ログインしてません、ログアウトできません"); return; } loginInfo.userId = null; contentWriter.writePlain(res, "ログアウトしました"); return; } if (loginInfo.userId == null) { contentWriter.writePlain(res, "ログインしてないので、自由コンテンツです"); } else { contentWriter.writePlain(res, "ログインしてるので、ログイン者コンテンツです"); } } @SessionScoped public static class LoginInfo implements Serializable { private static final long SerialVersionUID = 1L; public Long userId; } }

JettyでのHttpSessionの永続化例

TomcatにしてもJettyにしてもHttpSessionの永続化方法は様々なものがあるようだが、以下はJettyについて、単純にフォルダ以下にセッションを保持する例である。もちろん、これもプログラム的に行う。

  ....

    WebAppContext war = new WebAppContext();
    setupSessionStore(war);

  ....

  static void setupSessionStore(WebAppContext war) throws IOException {
    Path sessionsDir = Paths.get("session");
    Files.createDirectories(sessionsDir);
    DefaultSessionCache ss = new DefaultSessionCache(war.getSessionHandler());
    FileSessionDataStore sds = new FileSessionDataStore();
    ss.setSessionDataStore(sds);
    sds.setStoreDir(sessionsDir.toFile());
    war.getSessionHandler().setSessionCache(ss);
  }