FrontPage > Spring Bootの覚書

入力チェック

以下のような画面を考える。

サンプル画面
  • 名前は必須で5文字以下とする。
  • 年齢は0以上の整数とする。
  • 入力にエラーがある場合は入力域の背景色を赤にし、横にエラーメッセージを表示する。

1. 画面の作成(Thymeleaf)

  • 画面を作成する。

    (src/main/resources/templates/validSample.html)
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link th:href="@{/resources/css/base.css}" rel="stylesheet" type="text/css" />
    <title>validation Sample</title>
    </head>
    <body>
      <a th:href="@{/}">Topページ</a>
      <hr />
      <form th:action="@{/validSample}" th:object="${form}" method="post">
        <table>
          <tr>
            <td>名前:</td>
            <td>
              <input type="text" th:field="*{name}" th:errorclass="fieldError" />
            </td>
            <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
              style="color: red"></td>
          </tr>
          <tr>
            <td>年齢:</td>
            <td>
              <input type="text" th:field="*{age}" th:errorclass="fieldError" />
            </td>
            <td th:if="${#fields.hasErrors('age')}" th:errors="*{age}"
              style="color: red"></td>
          </tr>
        </table>
        <input type="submit" value="送信" />
      </form>
    </body>
    </html>
    

    (src/main/resources/static/resources/css/base.css抜粋)
    .fieldError {
      background-color: #ff8080;
    }
    

2. フォームクラスの作成

  • 入力値を保持するBeanクラスを作成し、各フィードに検証用のアノテーションを記述する。

    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
    public class ValidSampleForm {
        @NotBlank
        @Size(max=5)
        private String name;
    
        @Pattern(regexp="[0-9]*")
        private String age;
    

フォームクラスが入れ子になっている場合

  • 以下の例のようにリスト内の各Beanについても入力チェックする場合は
    @Validを付与する。

    (入れ子の例)
        @valid
        private List<Child> children;
    

3. コントローラクラスの作成

  • 入力の検証を行う引数に対して@Validを付与する。検証結果は引数のBindingResultで受け取る。
    dispValidSampleメソッドは初期表示用で、入力チェックは不要のため、@Validは付与しない。
    以下の例では発生したエラーコードをログに出力している。

    (src/main/java/com/ziqoo/sbSample/web/ValidSampleContrller.java)
    package com.ziqoo.sbSample.web;
    
    import javax.validation.Valid;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import com.ziqoo.sbSample.web.form.ValidSampleForm;
    
    @Controller
    public class ValidSampleContrller {
        private static final Logger log = LoggerFactory
    .getLogger(ValidSampleContrller.class);
    
        @RequestMapping(value="/validSample", method = RequestMethod.GET)
        public String dispValidSample(@ModelAttribute("form") ValidSampleForm form, Model model) {
            return "validSample";
        }
    
        @RequestMapping(value="/validSample", method = RequestMethod.POST)
        public String postValidSample(@ModelAttribute("form") @Valid ValidSampleForm form, BindingResult result, Model model) {
    
            if (result.hasErrors()) {
                for(FieldError err: result.getFieldErrors()) {
                    log.debug("error code = [" + err.getCode() + "]");
                }
            }
            return "validSample";
        }
    }
    
  • 実行すると以下のようになる。

    結果

4. エラーメッセージの一覧表示

エラーメッセージをページ先頭にまとめて表示してみる。

  • htmlを修正する。

    (src/main/resources/templates/validSample.html抜粋)
    <body>
      <a th:href="@{/}">Topページ</a>
      <hr />
      <form th:action="@{/validSample}" th:object="${form}" method="post">
        <ul>
          <li th:each="e : ${#fields.detailedErrors()}"
            th:class="${e.global}? globalerrMsg : fielderrMsg" th:text="${e.message}" />
        </ul>
        <table>
          <tr>
            <td>名前:</td>
            <td>
            <input type="text" th:field="*{name}"
              th:errorclass="fieldError" />
            </td>
          </tr>
    

    (src/main/resources/static/resources/css/base.css抜粋)
    .fielderrMsg {
      color: red;
    }
    .globalerrMsg {
      color: blue;
    }
    
  • 実行結果

    結果

5. エラーメッセージの変更

  • ライブラリーにある、hibernate-validator-xxx.jar内のorg.hibernate.validator.ValidationMessages.propertiesをsrc/main/resourcesにコピーし、 メッセージを編集する。
    メッセージにフィールド名も表示させるため、メッセージ内に{0}を記述する。

    (src/main/resources/ValidationMessages.properties抜粋)
    javax.validation.constraints.Pattern.message     = {0}:入力は "{regexp}"にマッチする文字列を入力してください。
    javax.validation.constraints.Size.message        = {0}:{min}文字以上 {max}文字以下で入力してください。
    ・・・
    org.hibernate.validator.constraints.NotBlank.message                = {0}:入力は必須です。
    
  • 実行結果

    結果
  • 表示されるフィールド名を変更する
    メッセージソースに指定されているメッセージファイルに
    "フィールド名=表示名称"を記述する。

    (src/main/resources/messages.properties抜粋)
    name=名前
    age=年齢
    
  • 実行結果

    結果

6. 型変換時エラーのメッセージ指定

  • フォームクラスのageフィールドをInteger型に変更し、ValidationMessages.propertiesを編集する。

    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
        @Min(0)
        private Integer age;
    

    (src/main/resources/ValidationMessages.properties抜粋)
    javax.validation.constraints.Min.message         = {0}:{value}以上の数値を入力してください。
    

  • ここで、年齢に数値以外を入力すると以下のようになる。

    結果

  • この場合のエラーメッセージを指定するために、メッセージファイルへ
    "エラーコード=メッセージ"を記述する。
    (エラーコードはコントローラから表示されるログを参照)

    (src/main/resources/messages.properties抜粋)
    typeMismatch={0}:入力された文字はタイプが違います。
    
  • 実行結果

    結果

  • 変換先のタイプによりメッセージを変える場合

    (src/main/resources/messages.properties抜粋)
    typeMismatch={0}:入力された文字はタイプが違います。
    typeMismatch.java.lang.Integer={0}:整数を入力してください。
    
  • フィールド固有のメッセージを指定する場合

    (src/main/resources/messages.properties抜粋)
    typeMismatch={0}:入力された文字はタイプが違います。
    typeMismatch.java.lang.Integer={0}:整数を入力してください。
    typeMismatch.age=年齢として有効な数値を入力してください。
    
  • 実行結果

    結果

  • メッセージコードについては
    org.springframework.validation.DefaultMessageCodesResolverを参照

7. 日付のフォーマット指定

  • application.propertiesへ日付のフォーマットを記述する。

    (src/main/resources/application.properties抜粋)
    spring.mvc.date-format=yyyy/MM/dd
    
  • 画面、フォームクラスに日付フィールドを追加し、メッセージファイルにエラーメッセージを追加する。

    (src/main/resources/templates/validSample.html抜粋)
           <tr>
            <td>誕生日:</td>
            <td>
            <input type="text" th:field="*{birthday}"
              th:errorclass="fieldError" />
            </td>
          </tr>
        </table>
    

    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
        private Date birthday;
    

    (src/main/resources/messages.properties抜粋)
    typeMismatch={0}:入力された文字はタイプが違います。
    typeMismatch.java.lang.Integer={0}:整数を入力してください。
    typeMismatch.java.util.Date={0}:yyyy/mm/ddで入力してください。
    typeMismatch.age=年齢として有効な数値を入力してください。
    

  • 実行結果

    (エラー時)
    結果

    (正常時)
    結果

8. 入力チェックのグループ化

チェック処理をグループ化し、指定したグループのチェックのみ実行することができる。

  • フォームクラスを修正し、グループを指定する。
    グループはインターフェースで定義する。

    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
    public class ValidSampleForm {
    
        public static interface Group1{};
        public static interface Group2{};
    
        @NotBlank(groups=Group1.class)
        @Size(max=5, groups=Group1.class)
        private String name;
    
        @Min(value=0, groups={Group1.class, Group2.class})
        private Integer age;
    
        @NotBlank(groups=Group2.class)
        private Date birthday;
    
  • コントローラのメソッドでチェックを行うグループを"@Validated"で指定する。
    以下の例ではGroup1のチェックのみ行われる。

    (src/main/java/com/ziqoo/sbSample/web/ValidSampleContrller.java抜粋)
        @RequestMapping(value="/validSample", method = RequestMethod.POST)
        public String postValidSample(@ModelAttribute("form") @Validated(Group1.class) ValidSampleForm form, BindingResult result, Model model) {
    
            if (result.hasErrors()) {
                for(FieldError err: result.getFieldErrors()) {
                    log.debug("error code = [" + err.getCode() + "]");
                }
            }
            return "validSample";
        }
    
  • 実行するグループを複数指定する場合は
    "@Validated({Group1.class,Group2.class})"などと記述する。

9. 独自の入力チェック

9.1 既存のアノテーションを利用する場合

  • 既存のアノテーションを利用して新たなアノテーションを作成する。
    以下では@NotBlankと@Sizeを利用して@Nameアノテーションを作成してみる。

    (src/main/java/com/ziqoo/sbSample/validation/constraints/Name.java)
    package com.ziqoo.sbSample.validation.constraints;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import javax.validation.ReportAsSingleViolation;
    import javax.validation.constraints.Size;
    
    import org.hibernate.validator.constraints.NotBlank;
    
    @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Constraint(validatedBy={})
    @ReportAsSingleViolation
    @NotBlank
    @Size(min=1, max=5)
    public @interface Name {
          String message() default "{com.ziqoo.validator.constraints.Name.message}";
    
          Class<?>[] groups() default {};
    
          Class<? extends Payload>[] payload() default {};
    
          @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.PARAMETER})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public static @interface List
          {
              Name[] value();
          }
    
    }
    

    (src/main/resources/ValidationMessages.properties抜粋)
    com.ziqoo.validator.constraints.Name.message=名前は5文字以内で入力してください。(必須)
    
    
  • 利用例
    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
        @Name
        private String name;
    
    

9.2 新規にValidatoを作成する場合(単項目チェック)

例として年齢チェック用のValidatorを作成してみる。

  • アノテーションを作成する。
    名称は@Ageとし、引数としてmin、maxを受け取り、デフォルト値はそれぞれ0、150とする。
    新たに作成するValidatorクラスは"AgeValidator"とする。

    (src/main/java/com/ziqoo/sbSample/validation/constraints/Age.java)
    package com.ziqoo.sbSample.validation.constraints;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    import com.ziqoo.sbSample.validation.AgeValidator;
    
    @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Constraint(validatedBy={AgeValidator.class})
    public @interface Age {
          String message() default "{com.ziqoo.validator.constraints.Age.message}";
    
          Class<?>[] groups() default {};
    
          Class<? extends Payload>[] payload() default {};
    
          int min() default 0;
          int max() default 150;
    
          @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.PARAMETER})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public static @interface List
          {
            Age[] value();
          }
    }
    

    (src/main/resources/ValidationMessages.properties抜粋)
    com.ziqoo.validator.constraints.Age.message=指定できる年齢は{min}才以上、{max}才以下です。
    

  • Validatorを作成する。

    (src/main/java/com/ziqoo/sbSample/validation/AgeValidator.java)
    package com.ziqoo.sbSample.validation;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import com.ziqoo.sbSample.validation.constraints.Age;
    
    public class AgeValidator implements ConstraintValidator<Age, Integer> {
        int min;
        int max;
    
        @Override
        public void initialize(Age annotation) {
            min = annotation.min();
            max = annotation.max();
        }
    
        @Override
        public boolean isValid(Integer value,
                ConstraintValidatorContext paramConstraintValidatorContext) {
            if (value == null) {
                return true;
            }
            if (value < min || value > max) {
                return false;
            }
            return true;
        }
    
    }
    
  • 利用例
    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
        @Age(min=18)
        private Integer age;
    
    

9.3 新規にValidatorを作成する場合(相関チェック)

例として"開始日" <= "終了日"をチェックするValidatorを作成する。

  • 画面とフォームに"期間(form, to)"を追加する。

    (src/main/resources/templates/validSample.html抜粋)
          <tr>
            <td>期間:</td>
            <td>
              <input type="text" th:field="*{from}" th:errorclass="fieldError" />
              〜 
              <input type="text" th:field="*{to}" th:errorclass="fieldError" />
            </td>
          </tr>
    

    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
        private Date from;
        private Date to;
    

    (画面イメージ)
    結果
  • アノテーションを作成する。
    名称は@Periodとし、引数としてフィールド名from、toを受け取り、デフォルト値はそれぞれ"from"、"to"とする。
    新たに作成するValidatorクラスは"PeriodValidator"とする。

    (src/main/java/com/ziqoo/sbSample/validation/constraints/Period.java)
    package com.ziqoo.sbSample.validation.constraints;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    import com.ziqoo.sbSample.validation.PeriodValidator;
    
    @Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Constraint(validatedBy={PeriodValidator.class})
    public @interface Period {
          String message() default "{com.ziqoo.validator.constraints.Period.message}";
    
          Class<?>[] groups() default {};
    
          Class<? extends Payload>[] payload() default {};
    
          String fieldFrom() default "from";
          String fieldTo() default "to";
    
          @Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public static @interface List
          {
              Period[] value();
          }
    
    }
    

    (src/main/resources/ValidationMessages.properties抜粋)
    com.ziqoo.validator.constraints.Period.message={0}:開始日は終了日以前でなくてはいけません。
    

  • Validatorを作成する。

    (src/main/java/com/ziqoo/sbSample/validation/PeriodValidator.java)
    package com.ziqoo.sbSample.validation;
    
    import java.util.Date;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import org.springframework.beans.BeanWrapper;
    import org.springframework.beans.BeanWrapperImpl;
    
    import com.ziqoo.sbSample.validation.constraints.Period;
    
    public class PeriodValidator implements ConstraintValidator<Period, Object> {
        private String fieldFrom;
        private String fieldTo;
        private String message;
    
        @Override
        public void initialize(Period annotation) {
            this.fieldFrom = annotation.fieldFrom();
            this.fieldTo = annotation.fieldTo();
            this.message = annotation.message();
        }
    
        @Override
        public boolean isValid(Object value,
                ConstraintValidatorContext context) {
    
            BeanWrapper beanWrapper = new BeanWrapperImpl(value);
    
            Date from = (Date)beanWrapper.getPropertyValue(fieldFrom);
            Date to = (Date)beanWrapper.getPropertyValue(fieldTo);
    
            if (from != null && to != null && from.compareTo(to) > 0) {
                context.disableDefaultConstraintViolation();
                context.buildConstraintViolationWithTemplate(message)
                .addNode(fieldFrom)
                .addConstraintViolation();
                return false;
            }
            return true;
        }
    
    }
    
  • 利用例
    アノテーションはクラスに対して付与する。

    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
    @Period(fieldFrom="from", fieldTo="to")
    public class ValidSampleForm implements PeriodForm {
    
        @Name
        private String name;
    
        @Age(min=18)
        private Integer age;
    
        @NotBlank(groups=Group2.class)
        private Date birthday;
    
        private Date from;
        private Date to;
    
  • 実行結果

    結果

9.4 特定フォーム用のValidatorを作成する場合

例として、ValidSampleForm用のValidatorを作成し、9.3で作成した期間のチェックと同じことを実現してみる。

  • 先にValidSampleFormクラスに付与した@Periodアノテーションを削除しておく。

    (src/main/java/com/ziqoo/sbSample/web/form/ValidSampleForm.java抜粋)
    //@Period(fieldFrom="from", fieldTo="to")
    public class ValidSampleForm implements PeriodForm {
    

  • Validatorを作成する。
    implementsするValidatorに注意。

    (src/main/java/com/ziqoo/sbSample/web/form/VdSampleValidator.java)
    package com.ziqoo.sbSample.web.form;
    
    import java.util.Date;
    
    import org.springframework.stereotype.Component;
    import org.springframework.validation.Errors;
    import org.springframework.validation.Validator;
    @Component
    public class VdSampleValidator implements Validator {
    
        @Override
        public boolean supports(Class<?> paramClass) {
            return ValidSampleForm.class.isAssignableFrom(paramClass);
        }
    
        @Override
        public void validate(Object paramObject, Errors paramErrors) {
            if (paramErrors.hasFieldErrors("from") || paramErrors.hasFieldErrors("to")) {
                return;
            }
    
            ValidSampleForm form = (ValidSampleForm)paramObject;
            Date from = form.getFrom();
            Date to = form.getTo();
            if (from != null && to != null && from.compareTo(to) > 0) {
                paramErrors.rejectValue("from", "validator.Period");
            }
        }
    
    }
    

    (src/main/resources/messages.properties)
    validator.Period=正しい期間を入力してください。
    

  • コントローラクラスへValidatorを登録する。

    (src/main/java/com/ziqoo/sbSample/web/ValidSampleContrller.java抜粋)
        @Autowired
        VdSampleValidator vdSampleValidator;
    
        @InitBinder
        public void initBinder(WebDataBinder binder) {
            binder.addValidators(vdSampleValidator);
        }
    

  • 実行結果

    結果






添付ファイル: filesbvd-page13.png 308件 [詳細] filesbvd-page12.png 310件 [詳細] filesbvd-page11.png 305件 [詳細] filesbvd-page10.png 316件 [詳細] filesbvd-page9.png 313件 [詳細] filesbvd-page8.png 338件 [詳細] filesbvd-page7.png 296件 [詳細] filesbvd-page6.png 307件 [詳細] filesbvd-page5.png 296件 [詳細] filesbvd-page4.png 303件 [詳細] filesbvd-page3.png 151件 [詳細] filesbvd-page2.png 303件 [詳細] filesbvd-page1.png 315件 [詳細]

トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2015-03-27 (金) 19:35:35 (993d)