プルダウンリストのように、複数のコントローラ間でデータを持ち回りたい場合、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を利用すると便利である。