対応したことの3行まとめ
-
spring-session-data-redis
を依存関係へ追加する -
@EnableRedisIndexedHttpSession
を設定に追加する - セッションへ格納するクラスに
@JsonIgnoreProperties(ignoreUnknown = true)
を追加する
セッション情報とは
Javaで作成するWebアプリケーションには、アプリケーション利用者ごとにデータを保持する「セッション情報1」があります。セッション情報を扱うことで、例えばログインした利用者ごとに入力情報を複数の画面にわたって保持する2のが簡単に実現できます。
Spring Frameworkのセッション拡張:SpringSession
SpringSessionはアプリケーションとセッション管理の間に抽象化レイヤーを提供します。SpringSessionではセッション情報の保存先にリレーショナルデータベース、NoSQLデータベースなどさまざまな永続ストアに保存できます。これにより分散アプリケーション環境でのセッション管理が実現できます。
また使用する永続ストアに関係なく同じAPIを通してセッションを管理できるため、用いる永続ストアに依ってアプリケーションのコードを変える必要がありません。
他にも、セッションの有効期限の設定や、異なるWebアプリケーション間のクロスコンテキスト通信などの機能も提供します。
まとめると、SpringSessionはWebアプリケーションでのユーザーセッションの管理を簡素化し、アプリケーションの構築に集中しやすくします。
SpringSessionが利用する永続ストア
SpringSessionが標準で対応している永続ストアには以下があります。
ストア | 利用方法 |
---|---|
Redis | spring-session-data-redis の適用 |
リレーショナルデータベース | リレーショナルデータベースごとのJDBC3ドライバー |
MongoDB | spring-session-data-mongodbの適用 |
選定については、すでに存在する永続ストアがあるかなど、コスト面や負荷状況から選ぶことになるでしょうか。接続する永続ストアを簡単に切り替えできるのもSpringSessionのいいところです。
SpringSessionの利用先にRedisを適用し、Redisへ保管する方法にJSONを選択する
かんたんに方法をまとめると
- プロジェクトに
org.springframework.session:spring-session-data-redis
を適用する - Redis用のSpring設定を追加する
です。
spring-session-data-redis を適用する
gradle であれば、dependencies
へ spring-session-data-redis
を追加します。
dependencies {
....
implementation 'org.springframework.session:'
....
}
Redis用のSpring設定を追加する
複数の方法が用意されていますが、本記事ではコードで記述します。
以下のConfigurationクラスを追加します。他の設定用クラスに混ぜても構いません。
@EnableRedisIndexedHttpSession
を追加し、RedisSerializer<Object>
を返す springSessionDefaultRedisSerializer(ObjectMapper)
メソッドを定義します。
@Configuration
@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:spring-sample-session-serialize")
public class SessionConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer("");
}
}
これでセッション情報をJSONに変換した内容でRedisへ保存します。
セッション格納方法にJSONを選択すると何がうれしいか
JavaのWebアプリケーションは、セッションへ格納していたクラスとアプリケーションの実装に違いがあると、アプリケーションがセッションから情報を参照した際には「型が違う」ためセッションが復元できずにアプリケーションエラーが発生します。
こうなると、セッション情報をすべて破棄するか、有効期限が切れるまではアプリケーションエラーが発生し続けることになります。
具体的な例)
セッションに格納していたクラス:
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
public class UserInfo implements Serializable {
private String userId;
private String userName;
}
アプリケーション変更後のセッションに格納するクラス:
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
public class UserInfo implements Serializable {
private String userId;
private String userName;
private Integer age; // フィールドの追加
}
今回のようにJacksonを使ってセッション情報を扱うと、Jackson
の ObjectMapper
でシリアライズ 4 し、セッションから取り出すときも Jackson
の ObjectMapper
がデシリアライズ5 します。
これにより、復元しようとした型が違うないしは存在しない場合の制御が Jackson
の設定で行え、セッションエラーが回避されますので、アプリケーションエラーを起こすかどうかも実装できます。
セッションに格納するオブジェクトは
また、SpringSession+Redisのライブラリは、以下の形式でJSONに変換したオブジェクトと、セッションの有効期限に関する情報もあわせて格納します。
Redisに格納する内容
セッション情報そのものと、セッションの寿命に関する専用のフィールド名を自動的に使います。
フィールド | 値 | 説明 |
---|---|---|
sessionAttr:userProfile | { "@class": "com.github.apz.sample.model.UserProfile", "userId": { "@class": "com.github.apz.sample.model.UserId", "value": "" }, "userName": { "@class": "com.github.apz.sample.model.UserName", "value": "" } } |
JacksonによりJSONへ変換したセッション格納オブジェクト |
creationTime | 1756363221869 | セッション作成時刻 |
lastAccessedTime | 1756363221869 | セッションオブジェクトの最終アクセス時間 |
maxInactiveInterval | 1800 | セッションオブジェクトの有効期限 |
セッション情報を操作するアプリケーションはこのセッション情報を取り出すときは、このJSONで変換したオブジェクトとセッションの有効期限に関するフィールドを元に、有効期限内の値なのか、どのクラスにデシリアライズして値を格納するのかを決定します。
セッションの属性が増えるとき
Redisに保存されていたセッション情報に対してアプリケーション側で新しい属性が増えた場合、新しい属性はnullになります6。アプリケーションエラー等は発生しません。
セッションの要素が減ったとき
Redisに保存されていたセッション情報に対して、アプリケーションのセッション情報から属性が足りなかった場合は、jackson が 「JSONからオブジェクトを復元したときにフィールドが不足している」旨の例外をスローします。
org.springframework.data.redis.serializer.SerializationException:
Could not read JSON:Unrecognized field "userAge"
(class com.github.apz.sample.model.UserProfile),
not marked as ignorable (2 known properties: "userId", "userName"])
上記のメッセージから、アプリケーションのUserProfileクラスは userId と userName の2つだが、JSONで格納されている userAge プロパティは認識できないので読めない です。まさしくその通りですね。
回避方法ですが、セッション情報に格納しているクラスに、デシリアライズ5する際に存在しないフィールドがあった場合には無視するようJacksonの設定を記述します。
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserProfile implements Serializable {
......
}
以上です。
参考実装
参考文献
url | 内容 |
---|---|
https://spring.pleiades.io/spring-session/reference/ | Spring Session 公式 |
https://www.baeldung.com/jackson-exception | jacksonのエラーと対応のまとめ |
-
セッション情報はJavaのWebアプリケーションの仕様である サーブレット仕様 で扱います。
サーブレット仕様:https://docs.oracle.com/cd/E18355_01/web.1013/B31859-01/overview.htm
サーブレットセッション:https://docs.oracle.com/cd/E18355_01/web.1013/B31859-01/overview.htm#810230 ↩ -
WebアプリケーションはHTTPで実現しています。HTTPはステートレス、つまりリクエストに対して簡単な処理を行った結果を出力し、すぐに消滅するものです。つまり情報を保持しません。
これに対してセッションは、ログイン情報やショッピングサイトの買い物かごのように、複数の画面を遷移しても状態を維持する仕組みをHTTPで実現できるようにする仕様で、これはJavaのWebアプリケーションはサーブレット仕様に則って実装します。 ↩ -
JDBC:https://docs.oracle.com/cd/G11854_01/jjdbc/introducing-JDBC.html リレーショナルデータベースへ接続し、データの送受信を行うモジュール。リレーショナルデータベースごとに用意される。 ↩
-
オブジェクトを文字列に変換すること。日本語では「直列化」と表現します。 ↩
-
この動きはJacksonの設定で変わります。 ↩