職業訓練でJavaを学ぶ!49日目(1/1)「Javaプログラミング実習(フレームワーク実習編)」
職業訓練で、Javaを使ったWebシステム開発・Androidアプリ開発を学ぶことになりました。復習かねて情報発信をします!
これから職業訓練に通うことを検討している方の参考になれば嬉しいです。↓↓職業訓練に通うことになった経緯はこちら
https://note.mu/yukoro/n/ned8d43b0110d
本日の授業内容
・Springのバリデーション機能
Hibernate Validator
(Springのバリデーション機能)
Springのバリデーション機能を提供するHibernate Validatorは、Hibernateというオープンソースコミュニティによって作成されたアノテーションによるバリデーションのライブラリです。
Hibernateの公式サイトはこちら。
Hibernateのバリデーション機能を使うと、下記のようなアノテーションでバリデーションを実行することができます。if文を書いて自分でバリデーションを実装しなくても簡単な記述で実装できるようになるのです。
Javaのアノテーションによるバリデーション仕様に「JSR 303: Bean Validation」というものがあり、これをHibernateが実装しています。SpringのなかでHibernateを使うことで、Javaとの連携が可能になります。
■Hibernate Validatorの使い方
まずプロジェクトに必要なライブラリを追加します。
javax.validation の validation-api
org.hibernate の hibernate-validator
※Mavenを使う場合は、hibernate-validatorのみ追加すれば、自動でvalidation-apiの追加を行なってくれます。
あとは下記の手順で実装します。
・ドメインオブジェクトにアノテーションでバリデーションの内容を指定
・Controller内でバリデーションの結果を確認し、適切な対応処理を行う
・Springのformタグライブラリでエラーメッセージを表示
■バリデーション用のアノテーション
Bean Validationで定義されているアノテーションとHibernate Validatorで独自に定義されているアノテーションがあります。
<Bean Validationで定義>
---------
@NotNull
- nullではないこと
- 例)@NotNull String name
@Max
- 数値の最大値
- 例)@Max(100) int rate
@Min
- 数値の最小値
- 例)@Min(0) int age
@Size
- 文字数やCollectionの要素数
- 例)@Size(min=1, max=20)
String name
@Future
- 未来であること
- 例)@Future Date event
@Past
- 過去であること
- 例)@Past Date birthdate
@AssertTrue
- trueであること
- 例)@AssertTrue boolean isRegistered
@AssertFalse
- falseであること
- 例)@AssertFalse boolean isError
@Pattern
- 正規表現にマッチすること
- 例)@Pattern(regexp="¥¥d{3}-¥¥d{4}")
String zipCode
---------
<Hibernate Validatorで独自に定義>
---------
@NotEmpty
- 文字列やCollectionがnullまたは空ではないこと(文字列の場合、空白文字があればEmptyとみなされない)
- 例)@NotEmpty
String name
@NotBlank
- 文字列がnullまたは空ではないこと(空白文字だけの場合はBlankとみなされる)
- 例)@NotBlank
String name
@Length
- 文字数の範囲
- 例)@Length(min=0, max=100)
String message
@Range
- 数値の範囲
- 例)@Range(min=0, max=100)
int score
@Email
- 文字列がEメール形式であること
- 例)@Email String primaryMail
@CreditCardNumber
- 文字列がクレジットカード番号形式であること
- 例)@CreditCardNumber
String primaryCardNumber
@URL
- 文字列がURL形式であること
- 例)@URL String website
---------
■Hibernate Validatorを使ったプロジェクト例
実際にアノテーションを使ってバリデーションを実行してみます。
下記の条件で、商品追加を行うフォームをHibernate Validatorを使って実装します。
-------
商品名:name 必須/10文字以内
値段:price 必須/0〜10000
説明:descroption 30文字以内
------
作成ファイルのフォルダ構成
プロジェクト
L Javaリソース
L src/main/java
L com.example.mvc.controller
L ItemController.java
L com.example.mvc.domain
L Item.java
L src
L main
L webapp
L WEB-INF
L views
L addItem.jsp ←フォーム表示用
L done.jsp ←完了ページ表示用
domain(Item.java)
package com.example.mvc.domain;
//インポートは、javax.validation.constraintsパッケージからおこなう
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Range;
public class Item {
//フィールド
@NotBlank
@Size(min = 1, max = 10)
private String name;
@NotNull
@Range(min = 0, max = 100000)
private Integer price;
@Size(max = 30)
private String description;
//アクセッサ
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
説明:
ドメインオブジェクトのフィールドにアノテーションによるバリデーション指示を記述します。
//フィールド
@NotBlank
@Size(min = 1, max = 10)
private String name;
商品名が入るnameフィールドでは
@NotBlank で必須項目で
@Size(min = 1, max = 10) で文字数が10文字以内
というバリデーションを実装しています。
@NotNull
@Range(min = 0, max = 100000)
private Integer price;
値段が入るpriceフィールドでは
@NotNull で必須項目で
@Range(min = 0, max = 100000)で数値が0〜10000の間
というバリデーションを実装しています。
@Size(max = 30)
private String description;
商品説明が入るdescroptionフィールドでは
@Size(max = 30) で文字数が30文字以内
というバリデーションを実装しています。
Controller(ItemController.java)
package com.example.mvc.controller;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.mvc.domain.Item;
@Controller
public class ItemController {
@GetMapping("/addItem")
public String addGet(Model model) {
model.addAttribute("item", new Item());
return "addItem";
}
@PostMapping("/addItem")
public String addPost(
@Valid @ModelAttribute("item") Item item,
Errors errors) {
if(errors.hasErrors()) {
return "addItem";
} else {
return "done";
}
}
}
説明:
/addItem にGet や Post でアクセスがあった場合の処理を実装しています。
@GetMapping("/addItem")
public String addGet(Model model) {
model.addAttribute("item", new Item());
return "addItem";
}
/addItem にGetでアクセスがあった場合のaddGetメソッドを定義しています。addGetメソッド内でModelオブジェクトを使いたいので、引数に持たせています。
model.addAttribute("item", new Item());
でdomainクラスItemのドメインオブジェクトを生成しています。
その後 addItem.jsp を実行し、フォームを表示させます。
@PostMapping("/addItem")
public String addPost(
@Valid @ModelAttribute("item") Item item,
Errors errors) {
if(errors.hasErrors()) {
return "addItem";
} else {
return "done";
}
}
/addItem にPostでアクセスがあった場合に実行するaddPost()メソッドを定義します。
addPost()メソッドの
第一引数の @Valid @ModelAttribute("item") Item item
はセッションに格納するフォームオブジェクト(item)です。@Valid をつけることでバリデーションを有効にします。
第二引数の Errors errors はエラー情報をErrorsオブジェクトとして受け取るための引数です。
@Valid のついた引数の後にErrorsの引数をセットで指定します。
if(errors.hasErrors())
でエラーかどうかを判断することができます。
エラーであれば再びフォームを表示し、エラーでなければ done.jsp を起動して登録完了ページに遷移させます。
補足:
---------
<エラー情報を扱うErrorsクラスの主なメソッド>
boolean hasErrors()
- バリデーションでエラーがあればtrue
int getErrorCount()
- エラーの総数を取得する
List<FieldError> getFieldErrors(String field)
- 引数で指定したフィールドに関するエラーのリストを取得するList<ObjectError> getGlobalErrors()
- グローバルエラー(オブジェクト全体としてのエラー)のリストを取得するvoid reject(String errorCode)
- グローバルエラーを追加する
void rejectValue(String field, String errorCode)
- フィールドエラー(オブジェクトの特定のフィールドに対するエラー)を追加する
---------
JSP(addItem.jsp)
<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品の登録</title>
</head>
<body>
<h1>商品の登録</h1>
<form:form modelAttribute="item">
<p>
商品名(必須):
<form:input path="name" />
</p>
<p>
値段(必須):
<form:input path="price" />
</p>
<p>
説明:
<form:input path="description" />
</p>
<input type="submit" />
</form:form>
</body>
</html>
説明:
商品の登録を行うためのフォーム画面です。formタグライブラリを使用しています。入力項目は3つです。
<form:form modelAttribute="item">
の modelAttribute属性の値は、Controller側で指定したドメインオブジェクトを格納する変数名item と一致させます。
JSP(done.jsp)
<%@ page pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登録完了</title>
</head>
<body>
<h1>登録完了</h1>
</body>
</html>
説明:
フォームからの送信が正常に完了した場合に表示させる画面です。
プロジェクト実行
プロジェクトを実行したら、URL末尾にControllerで設定したURLになるように addItem を追加してアクセスします。
アノテーションで指定した必須項目に入力がない場合や、文字数や数値を超えた値を入力した場合は、送信ボタンを押しても何も起こりません。
必須項目に正しく入力があれば、完了ページに遷移します。
■エラーメッセージの表示
それではバリデーションに引っかかった場合にエラーメッセージを表示してみます。
domain(Item.java)
package com.example.mvc.domain;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Range;
public class Item {
//フィールド
@NotBlank(message="商品名は必須項目です。")
@Size(min = 1, max = 10, message="商品名は{max}文字以内で入力してください。")
private String name;
@NotNull(message="値段は必須項目です。")
@Range(min = 0, max = 100000, message="{max}以下で入力してください。")
private Integer price;
@Size(max = 30, message="{max}文字以内で入力してください。")
private String description;
//アクセッサ
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
説明:
アノテーションをつけたドメインオブジェクトのフィールドに、message属性を使を使うことで、エラーメッセージの内容を指定することができます。
//フィールド
@NotBlank(message="商品名は必須項目です。")
@Size(min = 1, max = 10, message="商品名は{max}文字以内で入力してください。")
private String name;
@NotNull(message="値段は必須項目です。")
@Range(min = 0, max = 100000, message="{max}以下で入力してください。")
private Integer price;
@Size(max = 30, message="{max}文字以内で入力してください。")
private String description;
アノテーション横の()内に、message="エラーメッセージの文字列"のようにエラー内容を記述します。message属性ないで {max} や {min} とすることで、アノテーションに指定した max属性やmin属性の値をそのまま表示することができます。
JSP(addItem.jsp)
<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品の登録</title>
<style>
.error {
color : red;
}
</style>
</head>
<body>
<h1>商品の登録</h1>
<form:form modelAttribute="item">
<form:errors path="name" element="p" cssClass="error" />
<form:errors path="price" element="p" cssClass="error" />
<form:errors path="description" element="p" cssClass="error" />
<p>
商品名(必須):
<form:input path="name" />
</p>
<p>
値段(必須):
<form:input path="price" />
</p>
<p>
説明:
<form:input path="description" />
</p>
<input type="submit" />
</form:form>
</body>
</html>
説明:
フォーム側にエラーメッセージを出力させる際は<form:errors>タグを使います。path属性には対応させたいドメインクラスのフィールド名を指定します。
このエラーメッセージがHTMLとして出力された時、デフォルトでは<span>タグで囲まれた状態になっています。element属性を指定することで、囲みたいタグを指定することができます。
element="p" とした場合は、そのエラーメッセージはpタグで囲われた状態で出力されます。
cssClass属性で class属性を指定知ることができます。
cssClass="error" とした場合には、そのエラーメッセージがHTMLとして出力された際に class="error" がつくことになります。これによりエラーメッセージにCSSで見た目を整えたりすることができるようになります。
<form:form modelAttribute="item">
<form:errors path="name" element="p" cssClass="error" />
<form:errors path="price" element="p" cssClass="error" />
<form:errors path="description" element="p" cssClass="error" />
.....
もしフォームタグの外にエラーメッセージを表示させたい場合は、path属性に、modelAttribute属性の値(ドメインオブジェクト名)を「 .(ドット)」で繋げて記述します。
<form:errors path="item.name" element="p" cssClass="error" />
<form:errors path="item.price" element="p" cssClass="error" />
<form:errors path="item.description" element="p" cssClass="error" />
<form:form modelAttribute="item">
.....
プロジェクト実行
バリデーションに引っかかった場合は、エラーメッセージが表示されます。
■プロパティファイルによるエラーメッセージの指定
ここまではバリデーション用のアノテーションと一緒にエラーメッセージ内容を指定する方法をみてきましたが、プロパティファイルというものを使ってメッセージを指定する方法もあります。
プロパティファイルのメリットは、重複するメッセージをまとめて指定できたり、domainクラス側の記述が煩雑になるのを防ぐことができる点です。
例えば以下のようなメッセージの場合、「は必須項目です。」が重複しています。これらをまとめて「必須項目です」として一箇所で管理することができます。
@NotBlank(message="商品名は必須項目です。")
@Size(min = 1, max = 10, message="商品名は{max}文字以内で入力してください。")
private String name;
@NotNull(message="値段は必須項目です。")
@Range(min = 0, max = 100000, message="{max}以下で入力してください。")
private Integer price;
プロパティファイルを使ったエラーメッセージを行う手順を説明します。
まずbean定義ファイルに記述を追加します。
例)
<mvc:annotation-driven validator="validator" />
<mvc:annotation-driven>タグに validator="validator" を追加します。
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource" />
</bean>
LocalValidatorFactoryBean を生成するための記述です。id属性は前述の<mvc:annotation-driven>タグのvalidator属性値と紐づきます。
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages" />
</bean>
ResourceBundleMessageSource を生成するための記述です。
id属性は前述の LocalValidatorFactoryBean を生成するBeanタグのref属性値と紐づきます。
<property>タグのvalue属性値は、プロパティファイル名と紐づきます。これによりプロジェクトで使っていくエラーメッセージとこれから作成するプロパティファイルを紐づけることができます。
それでは設定が完了したのでプロパティファイルにエラーメッセージを記載していきます。この記事で例として書いてきたエラーメッセージの表示方法を、プロパティファイルでの指定に書き換えてみます。
作成ファイルのフォルダ構成
プロジェクト
L Javaリソース
L src/main/java
L com.example.mvc.controller
L ItemController.java
L com.example.mvc.domain
L Item.java
L src/main/resources
L messages.properties ← プロパティファイル
L デプロイ済みリソース
L webapp
L WEB-INF
L spring
L appServlet
L servlet-context.xml ← Bean定義ファイル
L src
L main
L webapp
L WEB-INF
L views
L addItem.jsp
L done.jsp
◇プロパティファイルによるエラーメッセージの表示
domain(Item.java)
package com.example.mvc.domain;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Range;
public class Item {
//フィールド
@NotBlank
@Size(min = 1, max = 10)
private String name;
@NotNull
@Range(min = 0, max = 100000)
private Integer price;
@Size(max = 30)
private String description;
//アクセッサ
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
説明:
エラーメッセージの内容を記述していないドメインクラスを準備しました。
propertieファイル(messages.properties)
プロパティファイルを通常ファイルの形式で作成したら、
ファイルを一度閉じて
ファイル上で右クリック >> 次で開く >> Limy プロパティ・エディタ
を選択し、このような記述を追加して保存します。
javax.validation.constraints.NotBlank.message={0}が未入力です。
javax.validation.constraints.Min.message={1}以上にしてください。
javax.validation.constraints.Max.message={1}以下にしてください。
1行目は、 @NotBlank に対応したエラーメッセージ
2行目は、 @Size や @Range の Min値 と Max値 に対応したエラーメッセージです。
プロジェクト実行
フォームにバリデーションに引っかかる情報を入力して実行してみると、英語のメッセージが表示されます。
◇エラーメッセージの文言調整
氏名の入力に関するエラーメッセージで、{0}の部分が name というフィールド名でそのまま出力されてしまっています。この部分を「氏名」に変更してみます。
プロパティファイルにnameの表示名を追加します。
javax.validation.constraints.NotBlank.message={0}が未入力です。
javax.validation.constraints.Min.message={1}以上にしてください。
javax.validation.constraints.Max.message={1}以下にしてください。
name=氏名
プロジェクト実行
「nameが未入力です。」となっていた部分が「氏名が未入力です。」に変わっています。
次に、Min値 と Max値 に対応したエラーメッセージを日本語で表示させます。方法はいくつかありますが、バリデーションのアノテーションを変更する方法で実現してみます。
domain(Item.java)
ドメインフィールドのアノテーションをこのように書き換えます。
変更前:
//フィールド
@NotBlank
@Size(min = 1, max = 10)
private String name;
@NotNull
@Range(min = 0, max = 100000)
private Integer price;
@Size(max = 30)
private String description;
変更後:
@NotBlank
@Size
@Min(1)
@Max(10)
private String name;
@NotNull
@Range
@Min(0)
@Max(100000)
private Integer price;
@Size
@Max(30)
private String description;
これでプロパティファイルの {1} の中に @Max() や @Min() の()内の値が入ってくるようになります。
javax.validation.constraints.NotBlank.message={0}が未入力です。
javax.validation.constraints.Min.message={1}以上にしてください。
javax.validation.constraints.Max.message={1}以下にしてください。
name=氏名
プロジェクト実行
エラーメッセージが日本語に変わりました。
次にmust not be null の部分も任意の文字列にしたいので、プロパティファイルに javax.validation.constraints.NotNull.message に対応するメッセージを追記します。
javax.validation.constraints.NotBlank.message={0}が未入力です。
javax.validation.constraints.NotNull.message=必須項目です。
javax.validation.constraints.Min.message={1}以上にしてください。
javax.validation.constraints.Max.message={1}以下にしてください。
name=氏名
補足:
------------
javax.validation.constraints.NotNull.message の部分の調べ方としては、出てきたエラーメッセージを、インターネット場で検索するとまとめサイトなどが対応するメッセージを掲載してくれています。
もしも検索しても出てこない場合は、Controller側のErrorオブジェクトを受け取れるPostのメソッドに、エラー内容をコンソールに表示させる処理を追記します。その内容をプロパティファイルに貼って、メッセージの内容を設定します。
Controller(ItemController.java)
...省略
@Controller
public class ItemController {
...省略
@PostMapping("/addItem")
public String addPost(
@Valid @ModelAttribute("item") Item item,
Errors errors) {
//エラー内容をコンソールに表示させる
List<ObjectError> objList = errors.getAllErrors();
for(ObjectError obj : objList) {
System.out.println(obj.toString());
}
if(errors.hasErrors()) {
return "addItem";
} else {
return "done";
}
}
...省略
}
------------
プロジェクト実行
無事に全ての項目が任意のメッセージで表示され利用になりました。
■カスタムエラーの追加
次にカスタムエラーの追加方法について説明します。Errorsオブジェクトには独自のエラー情報を追加することができます。
フォームオブジェクト全体に関するエラーの場合はこのように記述します。
書式:
----------
errors.reject(エラーコード);
----------
特定のフィールドに関するエラーの場合はこのように記述します。
書式:
----------
errors.rejectValue(フィールド名, エラーコード);
----------
<form:form>タグ外に、フォームに関するエラーメッセージを表示させる場合にはこのように記述することは前述しました。
<form:errors path="item.name" element="p" cssClass="error" />
<form:errors path="item.price" element="p" cssClass="error" />
<form:errors path="item.description" element="p" cssClass="error" />
<form:form modelAttribute="item">
.....
この場合のフォームオブジェクト全体に関するエラーは、下記のように<form:errors path="フォームオブジェクト名"> で出力できます。
<form:errors path="item" element="p" cssClass="error" />
特定のフィールドに関するエラーの場合は、フィールド名にバリデーション対象オブジェクトのフィールド名を文字列として指定します。
<form:errors path="item.name" element="p" cssClass="error" />
これらのエラーに対応するエラーコードは任意の文字列で設定することができます。Controller側でカスタムエラーを追加したら、追加したエラーコードに対応するメッセ―ジをプロパティファイルに設定します。
...
@PostMapping("/addItem")
public String addPost(
@Valid @ModelAttribute("item") Item item,
Errors errors,
Model model) {
if(条件) {
errors.rejectValue("name", "error.conf.null");
}
...
Controller側でカスタムエラーを追加したら、追加したエラーコードに対応するメッセ―ジをプロパティファイルに設定します。
error.conf.null=必須項目です。
■バリデーショングループ
バリデーションの内容をグループ分けする機能があります。
例えば、あるオブジェクトのバリデーションについて、特定の場面でだけ特定のバリデーションを行いたいという時に便利です。
例) Userオブジェクトは、ユーザ登録時はログインID、パスワード、氏名についてバリデーションを行いたいが、ログイン時にはログインIDとパスワードのみバリデーションを行いたい、など
◇バリデーショングループの作り方
Interface
グループを表すインターフェースを作成しますが、メソッドの定義は不要です。(=マーカーインターフェース)
例:ユーザ登録時のグループとログイン時のグループ
AddGrouop.java
public interface AddGroup {
}
LoginGroup.java
public interface LoginGroup {
}
◇バリデーショングループの使い方
domain
ドメインクラスでは、バリデーション用アノテーションのgroups属性でグループを指定します。
例)User.java
public class User {
@NotBlank(groups={AddGroup.class,LoginGroup.class})
private String loginId;
@NotBlank(groups={AddGroup.class,LoginGroup.class})
private String loginPass;
@NotBlank(groups={AddGroup.class})
private String name;
...
Controller
コントローラでは、@Validatedというアノテーションを使ってグループを指定します。
例)addUserメソッドでは、AddGroupを指定
public String addUser(@Validated(AddGroup.class) User user,
Errors errors) {
...
例)loginメソッドでは、LoginGroupを指定
public String login(@Validated(LoginGroup.class) User user,
Errors errors) {
...
「Springのバリデーション機能」については以上です。最後までお読みいただきありがとうございました!
この記事が気に入ったらサポートをしてみませんか?