プルダウンリストのように、複数のコントローラ間でデータを持ち回りたい場合、Spring FrameworkのセッションスコープのBeanを利用すると便利である。
今回は、Spring BootのWebアプリケーションでセッションスコープを利用してみたので、そのサンプルプログラムを共有する。
なお、セッションスコープについては、以下のサイトを参照のこと。
https://terasolunaorg.github.io/guideline/current/ja/ArchitectureInDetail/WebApplicationDetail/SessionManagement.html#spring-frameworksessionbean
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
プルダウンリストを格納するクラスは以下の通りで、セッションスコープのBeanクラスを利用している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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クラスの内容は以下の通りで、前提条件のプログラムから、プルダウンリストを削除する変更をしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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の初期化とクリアを行っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | 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)を参照している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | <!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> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <!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を利用すると便利である。