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. フォームクラスの作成 †
フォームクラスが入れ子になっている場合 †
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. エラーメッセージの変更 †
- 表示されるフィールド名を変更する
メッセージソースに指定されているメッセージファイルに
"フィールド名=表示名称"を記述する。
(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}:整数を入力してください。
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);
}
- 実行結果
|