spring bootでweb バリデート編

Post on 16-Jan-2017

6.806 Views

Category:

Engineering

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

バリデート編

アジェンダ はじめに

サンプルページ

サンプルページの準備

バリデート

メッセージを変更

カスタムバリデータ

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

ローカライズ

まとめ

はじめに Spring Bootを使って、

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

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

商品入力画面

確認画面

完了画面

【確認】クリック

【登録】クリック

入力エラー時

【戻る】クリック

【戻る】クリック

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

商品入力画面

確認画面

完了画面

【確認】クリック

【登録】クリック

入力エラー時

【戻る】クリック

【戻る】クリック

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

サンプルページ Controller

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

関連付けたController

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

商品入力画面

確認画面

完了画面

【確認】クリック

【登録】クリック

入力エラー時

【戻る】クリック

【戻る】クリック

サンプルページの準備 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 が レンダリングされる

サンプルページの準備 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

サンプルページの準備 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 の記述を追加

サンプルページの準備 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 が レンダリングされる

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

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

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

http://someserver/someapp/product/input

http://someserver/someapp/product/confirm

サンプルページの準備 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/>

入力画面を実装する

サンプルページの準備 Form

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

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

サンプルページの準備 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クラスを初期化する

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

サンプルページの準備 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/>

確認画面を実装する

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

http://someserver/someapp/product/input

http://someserver/someapp/product/confirm

バリデート 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までの入力

バリデート 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に対して バリデートをすることを明記

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

バリデート 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/>

エラーがあるか?

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

バリデート 動作確認

http://someserver/someapp/product/input

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

エラーメッセージを表示

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

これを日本語にする

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

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

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

http://someserver/someapp/product/input

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

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

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

http://someserver/someapp/product/input

ValidationMessages.properties

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

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

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

http://someserver/someapp/product/input

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

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

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

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

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

@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 {}; }

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

デフォルトメッセージ

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

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; } }

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

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

初期処理

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

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つあること

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

@NotBlank @TelNumber private String telNumber;

http://someserver/someapp/product/input

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

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

Controller

Service

ブラウザ

Bean Validation

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

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

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

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

異常時は例外を投げる

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

public class DuplicateProductException extends Exception { }

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

データベースを参照するバリデート 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でフィールド ごとのエラーを詰める

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

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

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

messages.properties

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

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

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

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

spring: messages: basename: messages

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

application.yml

http://someserver/someapp/product/input

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

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を削除

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

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

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

英語

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

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

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

まとめ 参考

■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

top related