プルダウンリストのように、複数のコントローラ間でデータを持ち回りたい場合、Spring FrameworkのセッションスコープのBeanを利用すると便利である。
今回は、Spring BootのWebアプリケーションでセッションスコープを利用してみたので、そのサンプルプログラムを共有する。
なお、セッションスコープについては、以下のサイトを参照のこと。
https://terasolunaorg.github.io/guideline/current/ja/ArchitectureInDetail/WebApplicationDetail/SessionManagement.html#spring-frameworksessionbean
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は以下の通り。

なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
プルダウンリストを格納するクラスは以下の通りで、セッションスコープのBeanクラスを利用している。
package com.example.demo;
import lombok.Data;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* プルダウンリストのクラス
* 「@Scope」アノテーションにより、プルダウンリストを
* セッション(sessionスコープのBean)としてもたせている
*/
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Data
public class DemoPulldown {
/** 生年月日_月のMapオブジェクト */
private Map<String, String> monthMap;
/** 生年月日_日のMapオブジェクト */
private Map<String, String> dayMap;
/** 性別のMapオブジェクト */
private Map<String, String> sexMap;
}また、Formクラスの内容は以下の通りで、前提条件のプログラムから、プルダウンリストを削除する変更をしている。
package com.example.demo;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
/**
* Formオブジェクトのクラス
*/
@Data
public class DemoForm {
/** 名前 */
@NotEmpty
private String name;
/** 生年月日_年 */
private String birthYear;
/** 生年月日_月 */
private String birthMonth;
/** 生年月日_日 */
private String birthDay;
/** 性別 */
@NotEmpty
private String sex;
/** 確認チェック */
@NotEmpty
private String checked;
}さらに、コントローラクラスの内容は以下の通りで、プルダウンリストを格納するセッションスコープのBeanの初期化とクリアを行っている。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* コントローラクラス
* 「@SessionAttributes(types = DemoForm.class)」により、
* 生成したFormオブジェクトをセッションとしてもたせている
*/
@Controller
@SessionAttributes(types = DemoForm.class)
public class DemoController {
// プルダウンリスト(sessionスコープ)
@Autowired
private DemoPulldown demoPulldown;
/**
* Formオブジェクトを初期化して返却する
* @return Formオブジェクト
*/
@ModelAttribute("demoForm")
public DemoForm createDemoForm(){
DemoForm demoForm = new DemoForm();
return demoForm;
}
/**
* 入力画面に遷移する
* @return 入力画面へのパス
*/
@GetMapping("/")
public String index(){
// プルダウンリスト(sessionスコープ)を生成後、入力画面に遷移
initDemoPulldown();
return "input";
}
/**
* プルダウンリスト(sessionスコープ)を初期化する
*/
private void initDemoPulldown(){
// 生年月日_月のMapオブジェクトを生成しプルダウンリストに設定
Map<String, String> monthMap = new LinkedHashMap<String, String>();
for(int i = 1; i <= 12; i++){
monthMap.put(String.valueOf(i), String.valueOf(i));
}
demoPulldown.setMonthMap(monthMap);
// 生年月日_日のMapオブジェクトを生成しプルダウンリストに設定
Map<String, String> dayMap = new LinkedHashMap<String, String>();
for(int i = 1; i <= 31; i++){
dayMap.put(String.valueOf(i), String.valueOf(i));
}
demoPulldown.setDayMap(dayMap);
// 性別のMapオブジェクトを生成しプルダウンリストに設定
Map<String, String> sexMap = new LinkedHashMap<String, String>();
sexMap.put("1", "男");
sexMap.put("2", "女");
demoPulldown.setSexMap(sexMap);
}
/**
* エラーチェックを行い、エラーが無ければ確認画面に遷移し、
* エラーがあれば入力画面のままとする
* @param demoForm Formオブジェクト
* @param result バインド結果
* @return 確認画面または入力画面へのパス
*/
@PostMapping("/confirm")
public String confirm(@Validated DemoForm demoForm, BindingResult result){
// formオブジェクトのチェック処理を行う
if(result.hasErrors()){
// エラーがある場合は、入力画面のままとする
return "input";
}
// アノテーション以外のチェック処理を行い、画面遷移する
return checkOthers(demoForm, result, "confirm");
}
/**
* エラーチェックを行い、エラーが無ければ完了画面へのリダイレクトパスに遷移し、
* エラーがあれば入力画面に戻す
* @param demoForm Formオブジェクト
* @param result バインド結果
* @return 完了画面へのリダイレクトパスまたは入力画面へのパス
*/
@PostMapping(value = "/send", params = "next")
public String send(@Validated DemoForm demoForm, BindingResult result){
// formオブジェクトのチェック処理を行う
if(result.hasErrors()){
// エラーがある場合は、入力画面に戻す
return "input";
}
// アノテーション以外のチェック処理を行い、画面遷移する
return checkOthers(demoForm, result, "redirect:/complete");
}
/**
* アノテーション以外のチェック処理を行い、画面遷移先を返却
* @param demoForm Formオブジェクト
* @param result バインド結果
* @param normalPath 正常時の画面遷移先
* @return 正常時の画面遷移先または入力画面へのパス
*/
private String checkOthers(DemoForm demoForm
, BindingResult result, String normalPath){
//** アノテーション以外のチェック処理を行う
//** エラーがある場合は、エラーメッセージ・(エラー時に赤反転するための)
//** エラーフィールドの設定を行い、入力画面のままとする
//生年月日のチェック処理
int checkDate = DateCheckUtil.checkDate(demoForm.getBirthYear()
, demoForm.getBirthMonth(), demoForm.getBirthDay());
switch(checkDate){
case 1:
//生年月日_年が空文字の場合のエラー処理
result.rejectValue("birthYear", "validation.date-empty"
, new String[]{"生年月日_年"}, "");
return "input";
case 2:
//生年月日_月が空文字の場合のエラー処理
result.rejectValue("birthMonth", "validation.date-empty"
, new String[]{"生年月日_月"}, "");
return "input";
case 3:
//生年月日_日が空文字の場合のエラー処理
result.rejectValue("birthDay", "validation.date-empty"
, new String[]{"生年月日_日"}, "");
return "input";
case 4:
//生年月日の日付が不正な場合のエラー処理
result.rejectValue("birthYear", "validation.date-invalidate");
//生年月日_月・生年月日_日は、エラーフィールドの設定を行い、
//メッセージを空文字に設定している
result.rejectValue("birthMonth", "validation.empty-msg");
result.rejectValue("birthDay", "validation.empty-msg");
return "input";
case 5:
//生年月日の日付が未来日の場合のエラー処理
result.rejectValue("birthYear", "validation.date-future");
//生年月日_月・生年月日_日は、エラーフィールドの設定を行い、
//メッセージを空文字に設定している
result.rejectValue("birthMonth", "validation.empty-msg");
result.rejectValue("birthDay", "validation.empty-msg");
return "input";
default:
//性別が不正に書き換えられていないかチェックする
if(!demoPulldown.getSexMap().keySet().contains(demoForm.getSex())){
result.rejectValue("sex", "validation.sex-invalidate");
return "input";
}
//エラーチェックに問題が無いので、正常時の画面遷移先に遷移
return normalPath;
}
}
/**
* 完了画面に遷移する
* @param sessionStatus セッションステータス
* @return 完了画面
*/
@GetMapping("/complete")
public String complete(SessionStatus sessionStatus){
// @SessionAttributeアノテーションで設定したセッションオブジェクトを破棄
sessionStatus.setComplete();
// プルダウンリスト(sessionスコープ)をクリア
clearDemoPulldown();
return "complete";
}
/**
* プルダウンリスト(sessionスコープ)をクリアする
*/
private void clearDemoPulldown(){
demoPulldown.setMonthMap(null);
demoPulldown.setDayMap(null);
demoPulldown.setSexMap(null);
}
/**
* 入力画面に戻る
* @return 入力画面
*/
@PostMapping(value = "/send", params = "back")
public String back(){
return "input";
}
}
また、入力画面・確認画面のHTMLファイルは以下の通りで、プルダウンリストを格納するセッションスコープのBean(demoPulldown)を参照している。
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link th:href="@{/demo.css}" rel="stylesheet" type="text/css" />
<title>入力画面</title>
</head>
<body>
<p>下記必要事項を記載の上、「確認」ボタンを押下してください。</p><br/>
<form method="post" th:action="@{/confirm}" th:object="${demoForm}">
<!-- セッションで保持しているプルダウンリストを取得し、生年月日(月・日)及び性別で利用 -->
<table th:with="demoPulldown=${@demoPulldown}">
名前: <input type="text" th:value="*{name}"
th:field="*{name}" th:errorclass="fieldError"/>
<span th:if="*{#fields.hasErrors('name')}"
th:errors="*{name}" class="errorMessage"></span>
<br/>
生年月日:
<input type="text" th:value="*{birthYear}" size="4"
maxlength="4" th:field="*{birthYear}" th:errorclass="fieldError"/>年
<select th:field="*{birthMonth}" th:errorclass="fieldError">
<option value="">---</option>
<option th:each="item : ${demoPulldown.getMonthMap()}"
th:value="${item.key}" th:text="${item.value}"/>
</select>月
<select th:field="*{birthDay}" th:errorclass="fieldError">
<option value="">---</option>
<option th:each="item : ${demoPulldown.getDayMap()}"
th:value="${item.key}" th:text="${item.value}"/>
</select>日
<span th:if="*{#fields.hasErrors('birthYear')}"
th:errors="*{birthYear}" class="errorMessage"></span>
<span th:if="*{#fields.hasErrors('birthMonth')}"
th:errors="*{birthMonth}" class="errorMessage"></span>
<span th:if="*{#fields.hasErrors('birthDay')}"
th:errors="*{birthDay}" class="errorMessage"></span>
<br/>
性別:
<for th:each="item : ${demoPulldown.getSexMap()}">
<input type="radio" name="sex" th:value="${item.key}"
th:text="${item.value}" th:field="*{sex}" th:errorclass="fieldError"/>
</for>
<span th:if="*{#fields.hasErrors('sex')}"
th:errors="*{sex}" class="errorMessage"></span>
<br/>
入力確認:
<input type="checkbox" name="checked" th:value="確認済"
th:field="*{checked}" th:errorclass="fieldError"/>
<span th:if="*{#fields.hasErrors('checked')}"
th:errors="*{checked}" class="errorMessage"></span>
<br/><br/>
<input type="submit" value="確認"/>
</table>
</form>
</body>
</html><!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>確認画面</title>
</head>
<body>
<p>入力内容を確認し、問題なければ「送信」ボタンを押下してください。</p>
<form method="post" th:action="@{/send}" th:object="${demoForm}">
<!-- セッションで保持しているプルダウンリストを取得し、生年月日(月・日)及び性別で利用 -->
<table th:with="demoPulldown=${@demoPulldown}">
<p th:text="'名前: ' + *{name}">ここに名前が表示されます</p>
<p th:text="'生年月日: ' + *{birthYear} + '年'
+ ${demoPulldown.getMonthMap().get('__*{birthMonth}__')} + '月'
+ ${demoPulldown.getDayMap().get('__*{birthDay}__')} + '日'">
ここに生年月日が表示されます
</p>
<p th:text="'性別: ' + ${demoPulldown.getSexMap().get('__*{sex}__')}">
ここに性別が表示されます
</p>
<p th:text="'確認チェック: ' + *{checked}">
ここに確認チェック内容が表示されます
</p>
<input type="submit" name="next" value="送信"/>
<input type="submit" name="back" value="戻る"/>
</table>
</form>
</body>
</html>その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-web-session-scope/demo
サンプルプログラムの実行結果
実行結果は、下記記事の「完成した画面イメージの共有」と同等の実行結果となる。
要点まとめ
- プルダウンリストのように、複数のコントローラ間でデータを持ち回りたい場合、Spring FrameworkのセッションスコープのBeanを利用すると便利である。





