Spring Boot 基本

Spring BootのWebアプリケーションでセッションスコープを利用してみた

プルダウンリストのように、複数のコントローラ間でデータを持ち回りたい場合、Spring FrameworkのセッションスコープのBeanを利用すると便利である。

今回は、Spring BootのWebアプリケーションでセッションスコープを利用してみたので、そのサンプルプログラムを共有する。

なお、セッションスコープについては、以下のサイトを参照のこと。
https://terasolunaorg.github.io/guideline/current/ja/ArchitectureInDetail/WebApplicationDetail/SessionManagement.html#spring-frameworksessionbean

前提条件

下記記事の実装が完了していること。

Spring BootのWEB画面上でチェック処理を実装してみた(ソースコード編)今回も、引き続きチェック処理を含むそのサンプルプログラムについて共有する。前回は、サンプルプログラムの完成イメージについて記載していたが...

サンプルプログラムの作成

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。

プルダウンリストを格納するクラスは以下の通りで、セッションスコープの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="送信"/>&nbsp;&nbsp;
        <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 BootのWEB画面上でチェック処理を実装してみた(完成イメージ編)今回は、以前作成した、入力画面・確認画面・完了画面の3画面を含み、HTMLオブジェクトとしてテキストボックス・セレクトボックス・ラジオボ...

要点まとめ

  • プルダウンリストのように、複数のコントローラ間でデータを持ち回りたい場合、Spring FrameworkのセッションスコープのBeanを利用すると便利である。