これまでの記事では特に入力を規制していませんでした。しかし、例えば文字数を50文字以下にしたいとか、数値しか入力させたくない項目などもあるでしょう。
もちろんControllerに渡されたFromの情報を一つずつ自分で解析する事もできます。しかし、今回はもっと楽に入力チェック(バリデーション)を行う方法を説明します。
Spring Boot入門:Thymeleafのファイル分割の続きです。
この記事のソースコード
この記事のソースコードはGithubに公開しています
GithubからSpring BootプロジェクトをEclipseにインポートする方法は次の記事を参考にしてください。
Spring Bootプロジェクトを作成
Spring Bootプロジェクトを作成します。
基本的にはこれまでと同じです。適当なプロジェクト名やパッケージを設定しましょう。
依存関係には、Spring Boot DevTools、Lombok、Thymeleaf、Spring Webに追加してValidationも設定します。今回説明する入力チェック用のアノテーションを使うのに必要だからです。
次の画像のようにしてください。選択項目名はValidationですが、選択済みの方はなぜか翻訳されて検証と表示されます。
入力チェック(バリデーション)を行うFormクラス
どの様な入力チェックを行うのかは、Controllerで引数にするFormクラスで指定します。
基本的な入力チェックはアノテーションをつけるだけで行えるので楽です。
package blog.tsuchiya.tutorial.step4.controller.form;
import java.util.Objects;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class MainForm {
@NotEmpty(message="エラーメッセージを独自定義")
@Email
private String email;
@Min(10)
@Max(100)
private Integer integer;
@NotNull
@Size(min=8, max=16)
@Pattern(regexp="^[0-9a-zA-Z]+$")
private String password1;
@NotNull
@Size(min=8, max=16)
@Pattern(regexp="^[0-9a-zA-Z]+$")
private String password2;
@AssertTrue(message="パスワードが一致しません")
public boolean isSamePassword() {
return Objects.equals(this.password1, this.password2);
}
}
クラス指定の前につけた@DataアノテーションはSetterやGetterの記述を省略するためのものです。それ以外のフィールドやメソッドにつけたアノテーションはすべて入力チェックを行うためのものとなります。
Spring Boot v2.5.5ではアノテーションのパッケージがjavax.validationでしたが、v3になる際にjakartaに変更になった模様です。
単項目の入力チェック
フィールドに対する入力をチェックするためには、チェックしたい内容に対応したアノテーションを付けるだけで大丈夫です。
例えば、フィールドemailの場合は@NotEmptyアノテーションで空ではないことを、@Emailアノテーションでメールアドレスであることを宣言しています。
アノテーションで指定した内容に反する入力があった場合は、Thymeleafでそのエラー内容を表示することができます。特に指定しない場合はデフォルトのメッセージが表示されるのですが、 @NotEmpty で行っているようにエラーメッセージを指定することも可能です。(アノテーションにmessageで独自のエラーメッセージを設定できる)
アノテーションの数はたくさんあるのですが、よく使うのは次のアノテーションでしょう。
アノテーション | 内容 |
@NotNull | nullでない。(空文字列やスペース、タブのみは許可する) |
@NotEmpty | 空でない。(nullと空文字列は不許可、スペースやタブのみのみは許可) |
@NotBlank | 空白でない。(null、空文字列、スペースやタブのみだと不許可) |
@Size(min, max) | 要素数の範囲を指定。Stringなら文字数、ListやMapなら要素数。 |
@Max, @Min | 数値の上限と下限を指定。 |
メールアドレスとして妥当かチェックする。 | |
@Pattern(regex) | 指定した正規表現に一致するかチェックする |
@AssertTrue | Trueであることをチェックする。 |
これ以外も色々知りたいのであれば、こちらのページがよくまとまっています。
複数項目の入力チェック
複数の項目に渡って入力チェックを行う方法はいくつかあります。
- チェック内容をメソッドにまとめて単項目のチェックを行う
- 独自のアノテーションを定義してクラスに設定する
- Controllerで入力チェックを実装する
この記事では一番簡単だと思われるチェック内容をメソッドにまとめて、単項目チェックを行う方法を使いました。この部分です。
@AssertTrue(message="パスワードが一致しません")
public boolean isSamePassword() {
return Objects.equals(this.password1, this.password2);
}
password1とpassword2が等しいかどうかをチェックしてその真偽値を返すメソッドを作り、それに@AssertTrueアノテーションを付けています。このメソッドが実行されるのは、各種フィールドに値が設定された後です。
こうすると、メソッドの返り値がFalseだった場合エラーとして扱われます。
今回は2項目に対して簡単なチェックを行うメソッドですが、より多くの項目に対して複雑なチェックを行うことも可能です。
入力チェック(バリデーション)の結果を確認するController
Formクラスにアノテーションを付けただけでは入力チェックは行われません。Controllerでも少し準備が必要です。
package blog.tsuchiya.tutorial.step4.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import blog.tsuchiya.tutorial.step4.controller.form.MainForm;
import jakarta.validation.Valid;
@Controller
public class MainController {
@GetMapping("/")
public String index(Model model) {
model.addAttribute("mainForm", new MainForm());
return "index";
}
@PostMapping("action")
// @Validアノテーションを付けた引数は入力チェックが行われる
// 結果は直後の引数BindingResultに格納される。
// BindingResultは@Validのすぐあとになくてはならない
public String action(Model mode, @ModelAttribute @Valid MainForm mainForm, BindingResult result) {
// 入力チェックに引っかかったらindex.htmlに戻る
if(result.hasErrors()) {
return "index";
}
return "action";
}
}
重要なのは27行目です。入力チェックを行いたいFormクラスに@Validアノテーションを付ける必要があります。(@ModelAttributeアノテーションはあんまり重要ではありません。最初からmodelにmainFormを格納するようにしただけです。)
更に、@Validアノテーションを付けた引数の直後にBindingResultの引数を指定する必要があります。このBindingResultに入力チェックの結果が格納されます。BindingResultは必ず@Validアノテーションを指定した引数の直後でなくてはなりません。
今回の場合はresultに入力チェックの結果が格納されています。hasErrors()メソッドは入力チェックに引っかかったかどうかの確認を行うメソッドです。入力チェックに引っかかったら元のページを表示し、そうでない場合は結果表示を行うようにしています。
エラー表示を行うThymeleafテンプレート
入力チェックを行った結果はユーザに伝えなくてはなりません。Thymeleafにはエラー表示専用の属性があるので、それを利用しています。
index.htmlはtemplatesフォルダの直下です。
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Formに初期値設定</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
<main class="container">
<section class="border p-1 mb-3">
<form method="post" th:action="@{/action}" th:object="${mainForm}">
<div class="mb-3">
<label for="email" class="form-label">メールアドレス(空でない、メールアドレスとして妥当)</label>
<!--/*
th:fieldに指定したフィールドに入力エラーがあったら
th:errorclassの内容をclassに追加する。
*/-->
<input type="text" class="form-control" th:errorclass="is-invalid"
th:field="*{email}">
<!--/* th:errorsに指定したフィールドのエラー内容を表示 */-->
<p class="invalid-feedback" th:errors="*{email}"></p>
</div>
<div class="mb-3">
<label for="integer" class="form-label">整数(整数、10以上100以下、空白OK)</label> <input
type="text" class="form-control" th:errorclass="is-invalid"
th:field="*{integer}">
<p class="invalid-feedback" th:errors="*{integer}"></p>
</div>
<div class="mb-3">
<label for="password1" class="form-label">パスワード1(半角英数、8文字以上16文字以下)</label>
<input type="text" class="form-control" th:errorclass="is-invalid"
th:field="*{password1}">
<p class="invalid-feedback" th:errors="*{password1}"></p>
</div>
<div class="mb-3">
<label for="password2" class="form-label">パスワード2(半角英数、8文字以上16文字以下、パスワード1と等しい)</label>
<input type="text" class="form-control" th:errorclass="is-invalid"
th:field="*{password2}">
<p class="invalid-feedback" th:errors="*{password2}"></p>
<!--/* isSamePasswordの結果はsamePasswordに格納される */-->
<p class="invalid-feedback"
th:errors="*{samePassword}" style="display: block;"></p>
</div>
<div>
<button type="submit" class="btn btn-primary">送信</button>
</div>
</form>
</section>
</main>
</body>
</html>
ちょっと長いので、一部を取り出して説明します。
<input type="text" class="form-control" th:errorclass="is-invalid"
th:field="*{email}">
<!--/* th:errorsに指定したフィールドのエラー内容を表示 */-->
<p class="invalid-feedback" th:errors="*{email}"></p>
inputタグのth:errorclassは、th:fieldに指定したフィールドが入力チェックに引っかかっていると、指定した値をclassに追加する属性です。今回の場合、emailフィールドが入力されていなかったりすると(@NotEmptyに引っかかると)classにis-invalidが追加されます。
CSSとかBootstrapの話になるので詳しくは説明しませんが、今回は入力欄が赤くなります。
その下のpタグにあるth:errorsは、emailフィールドに関するエラーメッセージを表示する属性です。例えば@NotEmpty条件に引っかかった場合は、このpタグのテキストに「エラーメッセージを独自定義」(アノテーションで指定したメッセージ)を設定します。
その他もだいたい同じように属性を設定しています。
samePasswordに関しては少しだけ他と記述が異なります。
<!--/* isSamePasswordの結果はsamePasswordに格納される */-->
<p class="invalid-feedback"
th:errors="*{samePassword}" style="display: block;"></p>
th:errorsにsamePasswordと指定していますが、これはMainFormのisSamePasswordメソッドの入力チェック結果を参照します。メソッドに入力チェックのアノテーションを付けた場合、メソッドの接頭語(getとかisとか)をとって、先頭を小文字にした値を指定してください。ちょっとややこしいですが、こういう仕様なんです。
おまけ:action.html
入力チェックに引っかからずに処理が終了した場合のThymeleafテンプレートです。単純にMainFormの値を表示しているだけなのでこの記事を読んでいる皆さんに説明は不要でしょう。一応コードを貼り付けておきます。
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
rel="stylesheet">
<title>出力</title>
</head>
<body>
<main class="container">
<h1>フォームへの入力を出力します</h1>
<p >メールアドレス:<span th:text="${mainForm.email}">email</span></p>
<p >整数:<span th:text="${mainForm.integer}">integer</span></p>
<p >パスワード1:<span th:text="${mainForm.password1}">password1</span></p>
<p >パスワード2:<span th:text="${mainForm.password2}">password2</span></p>
</main>
</body>
</html>
実際に動かしてみる
Spring Bootアプリケーションを起動して、実際に動きを確認してみましょう。
入力チェックに引っかからない値を入力すると、結果が表示されます。つまらないですね。
色々入力チェックに引っかかる値を入力してみます。
入力チェックに引っかかると入力欄が赤くなり、エラーメッセージも表示されました。
まとめ:入力チェック(バリデーション)を簡単に実装しよう
アノテーションを使って入力チェックを行う方法を説明しました。
基本的なチェック内容はSpring Bootが準備してくれているので、コーディングはFormクラスにアノテーションを付けるだけで済みます。大体の入力チェックは既存のアノテーションで済むでしょう。
複数項目に関連する入力チェックに関しては、チェック内容をメソッドにまとめて、そのメソッドにアノテーションを付けることで実装可能です。ただ、あまりにも複雑になる場合などは対応しきれないかもしれません。その場合は独自のアノテーションを作成するなど他の方法を取るべきでしょう。
この記事は入門として書いているのでここでは説明しません。要望があればいずれ記事にする可能性もあるので、その際は追記するようにします。
サンプルコードはGithubに公開してあります。
質問があったらTwitterやお問い合わせフォームで連絡をください。大歓迎です。
Spring Boot入門:ServiceとDI(依存性の注入)に続きます。
コメント