Spring bootでweb バリデート編

41
バリデート編
  • date post

    16-Jan-2017
  • Category

    Engineering

  • view

    6.806
  • download

    3

Transcript of Spring bootでweb バリデート編

Page 1: Spring bootでweb バリデート編

バリデート編

Page 2: Spring bootでweb バリデート編

アジェンダ はじめに

サンプルページ

サンプルページの準備

バリデート

メッセージを変更

カスタムバリデータ

データベースを参照するバリデート

ローカライズ

まとめ

Page 3: Spring bootでweb バリデート編

はじめに Spring Bootを使って、

Webの入力妥当性チェック(バリデート)の 実装方法を試してみる。

Page 4: Spring bootでweb バリデート編

サンプルページ サンプルページの画面フローは下記の通り

商品入力画面

確認画面

完了画面

【確認】クリック

【登録】クリック

入力エラー時

【戻る】クリック

【戻る】クリック

Page 5: Spring bootでweb バリデート編

サンプルページ サンプルページの画面フローは下記の通り

商品入力画面

確認画面

完了画面

【確認】クリック

【登録】クリック

入力エラー時

【戻る】クリック

【戻る】クリック

ここの話 どのように入力チェックをするか?

Page 6: Spring bootでweb バリデート編

サンプルページ Controller

/** 商品関連のページを制御するController */ @Controller @RequestMapping("product") public class ProductController { } http://someserver/someapp/product/ に

関連付けたController

Page 7: Spring bootでweb バリデート編

サンプルページの準備 まずサンプルページの下記画面フローを作成する

商品入力画面

確認画面

完了画面

【確認】クリック

【登録】クリック

入力エラー時

【戻る】クリック

【戻る】クリック

Page 8: Spring bootでweb バリデート編

サンプルページの準備 Controller

/** 商品関連のページを制御するController */ @Controller @RequestMapping("product") public class ProductController { /** 商品入力の開始 */ @RequestMapping(value = "/input", method= RequestMethod.GET) public String input(Model model) { return "product/input"; } }

http://someserver/someapp/product/input の GETリクエストを処理するメソッド

テンプレート product/input.html が レンダリングされる

Page 9: Spring bootでweb バリデート編

サンプルページの準備 Template (input.html)

<body> <h3>商品入力</h3> <form action="confirm.html" method="post"> Code: <input type="text" name="productCode" size="20"/> <br/> 商品名: <input type="text" name="productName" size="20"/> <br/> 金額: <input type="text" name="price" size="20"/> <br/> <input type="submit" name="naviButton" value="確認"/> </form> </body>

素のHTML

Page 10: Spring bootでweb バリデート編

サンプルページの準備 Template (input.html)

<form action="confirm.html" th:action="@{/product/confirm}" method="post"> Code: <input type="text" name="productCode" size="20" /> <br/> 商品名: <input type="text" name="productName" size="20"/> <br/> 金額: <input type="text" name="price" size="20" /> <br/> <input type="submit" name="naviButton" value="確認"/> </form>

Thymeleaf の記述を追加

Page 11: Spring bootでweb バリデート編

サンプルページの準備 Controller

/** 商品関連のページを制御するController */ @Controller @RequestMapping("product") public class ProductController { /** 商品入力の確認 */ @RequestMapping(value = "/confirm", method=RequestMethod.POST) public String confirm(Model model,) { return "product/confirm"; } }

http://someserver/someapp/product/input からの POSTリクエストを処理するメソッド

テンプレート product/confirm.html が レンダリングされる

Page 12: Spring bootでweb バリデート編

サンプルページの準備 Template (confirm.html)

<body> <h3>商品入力 - 確認</h3> <form action="finish.html" method="post"> Code: <br/> 商品名: <br/> 金額: <br/> <input type="submit" name="naviButton" value="登録"/> </form> </body>

Page 13: Spring bootでweb バリデート編

サンプルページの準備 動作確認

http://someserver/someapp/product/input

http://someserver/someapp/product/confirm

Page 14: Spring bootでweb バリデート編

サンプルページの準備 Template (input.html)

<form action="confirm.html" th:action="@{/product/confirm}" method="post"> Code: <input type="text" name="productCode" size="20" th:field="*{productForm.productCode}"/> <br/> 商品名: <input type="text" name="productName" size="20" th:field="*{productForm.productName}"/> <br/> 金額: <input type="text" name="price" size="20" th:field="*{productForm.price}"/> <br/>

入力画面を実装する

Page 15: Spring bootでweb バリデート編

サンプルページの準備 Form

/** 画面の値を保持するForm */ public class ProductForm { private Integer productCode; private String productName; private Integer price; // getter/setteを省略

フォームクラスを実装する これが入力値の格納先となる

Page 16: Spring bootでweb バリデート編

サンプルページの準備 Controller

/** 商品関連のページを制御するController */ @Controller @RequestMapping("product") public class ProductController { @ModelAttribute public ProductForm setupForm() { return new ProductForm(); } /** 商品入力の確認 */ @RequestMapping(value = "/confirm", method=RequestMethod.POST) public String confirm(Model model, ProductForm productForm) { return "product/confirm"; } }

画面用のFormクラスを初期化する

引数に入れた項目が 画面上の同名の項目にマップされる

Page 17: Spring bootでweb バリデート編

サンプルページの準備 Template (confirm.html)

<form action="finish.html" id="confirm" th:object="${productForm}" method="post"> Code: <span th:text="${productForm.productCode}"/> <br/> 商品名: <span th:text="${productForm.productName}"/> <br/> 金額: <span th:text="${productForm.price}"/> <br/>

確認画面を実装する

Page 18: Spring bootでweb バリデート編

サンプルページの準備 動作確認

http://someserver/someapp/product/input

http://someserver/someapp/product/confirm

Page 19: Spring bootでweb バリデート編

バリデート Formの修正

/** 画面の値を保持するForm */ public class ProductForm { @NotNull private Integer productCode; @NotNull @Length(max=10) private String productName; @NotNull @Max(1000) private Integer price; // getter/setteを省略

Bean Validationの仕様に従って アノテーションを記述する

@NotNull → 必須入力 @Length → 長さ制限(10文字まで)

@Max → 1,000までの入力

Page 20: Spring bootでweb バリデート編

バリデート Controllerの修正

/** 商品関連のページを制御するController */ @Controller @RequestMapping("product") public class ProductController { @RequestMapping(value = "/confirm", method=RequestMethod.POST) public String confirm(Model model, @Valid ProductForm productForm, Errors errors) { if (errors.hasErrors()) { return "product/input"; } return "product/confirm"; } }

@Validを付けて、Formに対して バリデートをすることを明記

受け取ったバリデート結果を参照し、 エラーがあれば入力画面に遷移する

Page 21: Spring bootでweb バリデート編

バリデート Templateの修正(input.html)

Code: <input type="text" name="productCode" size="20" th:field="*{productForm.productCode}"/> <span th:if="${#fields.hasErrors('*{productForm.productCode}')}" th:errors="*{productForm.productCode}" style="color: red"/> <br/> 商品名: <input type="text" name="productName" size="20" th:field="*{productForm.productName}"/> <span th:if="${#fields.hasErrors('*{productForm.productName}')}" th:errors="*{productForm.productName}" style="color: red"/> <br/> 金額: <input type="text" name="price" size="20" th:field="*{productForm.price}"/> <span th:if="${#fields.hasErrors('*{productForm.price}')}" th:errors="*{productForm.price}" style="color: red"/><br/>

エラーがあるか?

エラーがあれば、内容を出力

Page 22: Spring bootでweb バリデート編

バリデート 動作確認

http://someserver/someapp/product/input

未入力、 10文字超え、 1000超え

エラーメッセージを表示

Page 23: Spring bootでweb バリデート編

メッセージを変更 初期状態ではメッセージが全て英語のため、

これを日本語にする

Page 24: Spring bootでweb バリデート編

メッセージを変更 方法1:個別に指定する

public class ProductForm { @NotNull(message="入力してください(埋め込み)") private Integer productCode;

各バリデート用のアノテーションにある messageプロパティで指定する

http://someserver/someapp/product/input

Page 25: Spring bootでweb バリデート編

メッセージを変更 方法2:プロパティファイルで指定する

javax.validation.constraints.NotNull.message=入力してください(プロパティ)

プロパティファイルに対応するアノテーションの メッセージをプロパティファイルで指定する

http://someserver/someapp/product/input

ValidationMessages.properties

Page 26: Spring bootでweb バリデート編

メッセージを変更 可変項目をメッセージに埋め込む

org.hibernate.validator.constraints.Length.message=入力は{max}文字までです。

アノテーションのパラメータ名を プロパティファイルで指定する

http://someserver/someapp/product/input

@NotBlank @Length(min=1, max=10) private String productName;

{max}に 10が埋め込まれる

Page 27: Spring bootでweb バリデート編

カスタムバリデータ 自前のバリデート機能を実装する方法は次の通り

(サンプルとして電話番号のバリデータを実装する)

Page 28: Spring bootでweb バリデート編

カスタムバリデータ アノテーションを宣言する (お約束の書き方)

@Constraint(validatedBy = TelNumberValidator.class) @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface TelNumber { String message() default “TEL number is invalid"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }

実際にチェックを行う クラスを指定(後述)

デフォルトメッセージ

Page 29: Spring bootでweb バリデート編

カスタムバリデータ チェック処理を実装する

public class TelNumberValidator implements ConstraintValidator<TelNumber, String> { @Override public void initialize(TelNumber constraintAnnotation) { } @Override public boolean isValid(final String value, final ConstraintValidatorContext context) { return true; } }

チェック対象のアノテーションと チェック対象のデータ型

ここにチェック処理を 実装する

初期処理

Page 30: Spring bootでweb バリデート編

カスタムバリデータ チェック処理を実装する

public class TelNumberValidator implements ConstraintValidator<TelNumber, String> { private Pattern pattern; @Override public void initialize(TelNumber constraintAnnotation) { pattern = Pattern.compile("^0¥¥d*-¥¥d*-¥¥d*"); } @Override public boolean isValid(final String value, final ConstraintValidatorContext context) { Matcher matcher = pattern.matcher(value); return matcher.find(); } }

正規表現チェック用の フィールド

パターンに合ってたら true 違ったら false を返す

初期処理 0から始まって、数値の間に「-」が2つあること

Page 31: Spring bootでweb バリデート編

カスタムバリデータ カスタムバリデータを利用する

@NotBlank @TelNumber private String telNumber;

http://someserver/someapp/product/input

Page 32: Spring bootでweb バリデート編

データベースを参照するバリデート 今までのBean Validationは

Controllerの手前での出来事だったが、 データベースを参照するバリデートはControllerの先の動作となる

Controller

Service

ブラウザ

Bean Validation

データベースを参照する バリデート

Page 33: Spring bootでweb バリデート編

データベースを参照するバリデート Serviceにバリデート用メソッドを用意する

/** Productをチェックする */ public void validate(ProductForm productForm) throws DuplicateProductException { if ( productForm.getProductCode() == 1 ) { throw new DuplicateProductException(); } }

サンプルとして、商品コードに 「1」が入れられたら例外を投げる

異常時は例外を投げる

Page 34: Spring bootでweb バリデート編

データベースを参照するバリデート サービス固有の例外を作成する

public class DuplicateProductException extends Exception { }

Exceptionを拡張して 必ずキャッチさせる

Page 35: Spring bootでweb バリデート編

データベースを参照するバリデート ControllerでServiceに追加したチェックを呼び出す

/** 商品入力の確認 */ @RequestMapping(value = "/confirm", method=RequestMethod.POST) public String confirm(Model model, @Valid ProductForm productForm, Errors errors) { if (errors.hasErrors()) { return "product/input"; } try { productService.validate(productForm); } catch(DuplicateProductException e) { errors.rejectValue("productCode", "duplicate", new String[]{"商品コード"}, "default message."); return "product/input"; } return "product/confirm"; }

チェックを呼び出す

エラーがあったら rejectValueでフィールド ごとのエラーを詰める

Page 36: Spring bootでweb バリデート編

データベースを参照するバリデート メッセージ用のプロパティファイルを用意する

duplicate={0}が重複しています。

「キー = メッセージ」の形式で記述

messages.properties

errors.rejectValue("productCode", "duplicate", new String[]{"商品コード"}, "default message.");

rejectValueしたときのerrorCodeを プロパティと同じキーにする

置換箇所「{0}」に 「商品コード」を当てる

Page 37: Spring bootでweb バリデート編

データベースを参照するバリデート アプリケーション設定を変更する

spring: messages: basename: messages

先ほどのmessages.propertiesを 参照するよう設定する

application.yml

http://someserver/someapp/product/input

Page 38: Spring bootでweb バリデート編

データベースを参照するバリデート その他の方法

if (errors.hasErrors()) { return "product/input"; } productService.validate(productForm); return "product/confirm"; } @ExceptionHandler( DuplicateProductException.class ) public ModelAndView handleException(RuntimeException e ) { return new ModelAndView(" product/input ") .addObject("error", e.getMessage()); }

public class DuplicateProductException extends RuntimeException { }

RuntimeExceptionに変更

try ~ catchを削除

コントローラの例外を 一手に引き受ける

Page 39: Spring bootでweb バリデート編

ローカライズ 言語ごとのプロパティファイルを配置し、多言語対応をする

javax.validation.constraints.NotNull.message=Please input.

ValidationMessages.properties

デフォルト

javax.validation.constraints.NotNull.message=入力してください。

ValidationMessages_ja.properties

日本語

javax.validation.constraints.NotNull.message=Please input.

ValidationMessages_en.properties

英語

Page 40: Spring bootでweb バリデート編

まとめ SpringというよりBean Validation(JSR-303,JSR-349)の仕様を

知る方が、学習の近道かもしれない・・・?

また、Springの採用している実装のHibernate Validatorも確認を

Page 41: Spring bootでweb バリデート編

まとめ 参考

■JSR 303 Bean Validationで遊んでみるよ! - Yamkazu's Blog http://yamkazu.hatenablog.com/entry/20110206/1296985545 ■私のBeanValidationの使い方(Java EE Advent Calendar 2013) — 裏紙 http://backpaper0.github.io/2013/12/03/javaee_advent_calendar_2013.html ■Spring Boot Security Application - Bartosz Kielczewski http://kielczewski.eu/2014/12/spring-boot-security-application/ ■81.参考: 妥当性チェックのエラーメッセージ出力方法 - soracane https://sites.google.com/site/soracane/home/springnitsuite/spring-batch/81-can-kao-tuo-dang-xingchekkunoeramesseji-chu-li-fang-fa