spring

Spring4勉強会 第三回

More than 3 years have passed since last update.

Spring4勉強会 第三回

第一回は環境構築と簡単な値の受け渡し
第二回は色々なFORMとの連携

今回は入力値検証(Validation)について取り扱っていきたいと思います。

INDEXは以下

  • 社員登録機能を作る
  • 解説1
  • 入力値検証機能を追加する
  • 解説2
  • ValidationMessageを日本語にする
  • 独自Validationを作成する
  • 解説3
  • 独自Validationにちょい足しする
  • 解説4

です。早速作って、動かしてみましょう。

社員登録機能を作る

まずは入力値検証のない社員登録機能を作成します。
作成する画面イメージは以下のようなものです。

emp.png

適当に社員を登録すると…

empregi.png

こんな感じで登録した社員が表示されます。
※modelAttributeはsessionに保存されるので、ブラウザを閉じるか、サーバを再起動すると消えます。

ソースは以下です。作っていきましょう。

▼JSP
「WEB-INF/view/employee/list.jsp」を作成

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><c:out value="${title}"></c:out></title>
</head>
<body>
    <h1>
        <c:out value="${title}"></c:out>
    </h1>
    <p>
        <c:out value="${message}"></c:out>
    </p>
    <form:form modelAttribute="employeeListForm">
        <table>
            <tbody>
                <tr>
                    <td><form:label path="name">社員名</form:label></td>
                    <td><form:input path="name" size="20" /></td>
                </tr>
                <tr>
                    <td><form:label path="age">年齢</form:label></td>
                    <td><form:input path="age" size="20" /></td>
                </tr>
                <tr>
                    <td><form:label path="memo">メモ</form:label></td>
                    <td><form:textarea path="memo" cols="20" row="5" /></td>
                </tr>
            </tbody>
        </table>
        <input type="submit" />
    </form:form>
    <c:if test="${not empty employeeList}">
        <table border="1">
            <tbody>
                <tr>
                    <th>社員名</th>
                    <th>年齢</th>
                    <th>メモ</th>
                </tr>
                <c:forEach var="employee" items="${employeeList}">
                    <tr>
                        <td><c:out value="${employee.name}"></c:out></td>
                        <td><c:out value="${employee.age}"></c:out></td>
                        <td><c:out value="${employee.memo}"></c:out></td>
                    </tr>
                </c:forEach>
            </tbody>
        </table>
    </c:if>
</body>
</html>

EmployeeListFormが今回取り扱うFormオブジェクトになります。

登録社員情報は

<c:forEach var="employee" items="${employeeList}">

で回している「employeeList」になります。
これはControllerでDtoをリストに詰めているので、後ほど解説します。

▼Formの作成

「jp.co.kenshu.form.employee」下にEmployeeListForm.javaを作成してください。

package jp.co.kenshu.form.employee;

import java.util.Date;
import lombok.Data;

@Data
public class EmployeeListForm {
    private String name;
    private Integer age;
    private String memo;
}

例によってlombokを使っているので、よしなにsetter/getterを作成してください。
intではなく、Integerを使用している理由は、jspのpath指定したageがデフォルトで「0」と表示されてしまうのを防ぐためです。
Integerならnullが初期値なので、nullはEL式の仕様上表示されません。

▼Dtoの作成

続いて、登録社員表示用のDtoを作成します。

「jp.co.kenshu.dto.employee」にEmployeeDto.javaを作成してください。

package jp.co.kenshu.dto.employee;

import lombok.Data;

@Data
public class EmployeeDto {
    private String name;
    private Integer age;
    private String memo;
}

▼Controllerの作成

「jp.co.kenshu.controller」下にValidationSampleController.javaを作成してください。

package jp.co.kenshu.controller;

import java.util.ArrayList;
import java.util.List;
import jp.co.kenshu.dto.employee.EmployeeDto;
import jp.co.kenshu.form.employee.EmployeeListForm;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ValidationSampleController {

    private List<EmployeeDto> employeeList = new ArrayList<>();

    @RequestMapping(value = "/employee/list", method = RequestMethod.GET)
    public String list(Model model) {
        model.addAttribute("title", "社員一覧");
        model.addAttribute("message", "登録社員一覧情報を表示します");
        EmployeeListForm form = new EmployeeListForm();
        model.addAttribute("employeeListForm", form);
        model.addAttribute("employeeList", employeeList);
        return "employee/list";
    }

    @RequestMapping(value = "/employee/list", method = RequestMethod.POST)
    public String list(@ModelAttribute EmployeeListForm form, Model model, BindingResult result) {
        EmployeeDto dto = new EmployeeDto();
        BeanUtils.copyProperties(form, dto);
        employeeList.add(dto);
        model.addAttribute("title", "社員一覧");
        model.addAttribute("message", form.getName() + "を登録しました。");
        model.addAttribute("employeeListForm", new EmployeeListForm());
        model.addAttribute("employeeList", employeeList);
        return "employee/list";
    }
}

少し長くなりましたが、今までと同様、GETで初回画面を表示し、POSTでクライアントからの投稿を受け付ける作りになってます。

解説1

【JSP】

<form:form modelAttribute="employeeListForm">

jspは今までとあまり変化ないので解説は省略します。
ただ今回は上記の通り、「EmployeeListForm」にクライアントが送信した情報がバインドされることになります。

<c:if test="${not empty employeeList}">

Controller側でModelにattributeされるので、それが空じゃければforeachで回して表示するという動きをしています。
ちなみに「not emplty」はEL式の記述なので解説は省きますね。

【EmployeeForm】
クライアントから送られる情報を格納するフォームオブジェクトです。
今回は

private String name;
private Integer age;
private String memo;

例によってlombok使ってます。

【EmployeeDto】
ControllerからViewに情報を渡す用途で使用しています。
たまにForm情報(クライアントから送られる情報)と関係ない情報をFormオブジェクトに詰めて、スーパーオブジェクトみたいに扱う記述も見ますが、今回はそこは分けてます。

  • クライアント入力値
      →Form

  • ビジネスロジック表示情報
      →Dto

今回はJSPの「not empty」でforeachしているListにこのDtoが詰められています。

<c:forEach var="employee" items="${employeeList}">
    <tr>
        <td><c:out value="${employee.name}"></c:out></td>
        <td><c:out value="${employee.age}"></c:out></td>
        <td><c:out value="${employee.memo}"></c:out></td>
    </tr>
</c:forEach>

上の「${employee.name}」がDtoに該当します。

【Controller】

まずはGETから。
GETは単純です。

▼GET

@RequestMapping(value = "/employee/list", method = RequestMethod.GET)
public String list(Model model) {
    model.addAttribute("title", "社員一覧");
    model.addAttribute("message", "登録社員一覧情報を表示します");
    EmployeeListForm form = new EmployeeListForm();
    model.addAttribute("employeeListForm", form);
    model.addAttribute("employeeList", employeeList);
    return "employee/list";
}

JSPで

<form:form modelAttribute="employeeListForm">

としているため、Controllerで設定してあげる必要があります。
なので

model.addAttribute("employeeListForm", form);

とし、key値を「employeeListForm」で合わせてます。

次に

model.addAttribute("employeeList", employeeList);

ですが、これもJSPで

<c:forEach var="employee" items="${employeeList}">

として回しているため、employeeListに要素が存在すれば表示される形になります。
なお、employeeListはControllerのフィールドとして所有しているため、
session範囲内であれば要素が追加されていく挙動をとります。
※POSTで登録済みの情報はGETでアクセスしても表示されます。

private List<EmployeeDto> employeeList = new ArrayList<>();

POST

続いてPOSTです。
POSTで注目すべきは以下の記述のみです。

EmployeeDto dto = new EmployeeDto();
BeanUtils.copyProperties(form, dto);
employeeList.add(dto);

クライアントから送信された情報をdtoに詰め替えるということをしています。
BeanUtils#copyPropertiesは第一引数に指定したオブジェクトのプロパティを、第二引数に指定したオブジェクトのプロパティに代入するという働きをします。
つまり、BeanUtils#copyPropertiesの処理をアホみたいに丁寧に書くと以下になります。

dto.setId(form.getId());
dto.setName(form.getName());
dto.setComment(form.getComment());

三つくらいのプロパティなら問題ないかもしれませんが、これが10個も20個もプロパティをコピーする必要があったら辛いですね。
ちなみに、copyPropertiesは以下の規則の下、異なるオブジェクト間のプロパティをコピーしてくれます。

  1. 型が同じ
  2. 変数名が同じ

そして該当しないプロパティは無視されます。

入力値検証機能を追加する

以下のような入力値検証を作成します。

  • 名前は必須入力
  • 年齢は18歳以上から受け付ける

▼名前も年齢も空の場合

null.png

▼年齢が18以下の場合

18.png

まずは動かしてみて、解説します。

 1. ライブラリの追加

今回は「javax.validationAPI」と「hibernate」を新たに利用していきます。
ライブラリを追加する際はMavenで管理するため、pom.xmlに以下を追記します。

<!-- validation-api(入力値検証で使用) -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<!-- Hibernate(入力値検証で使用) -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.0.1.Final</version>
</dependency>

追記したら「PJを右クリック」→「実行」→「Maven install」を実行してください。

 2. Formに入力値検証アノテーションを追記

@Data
public class EmployeeListForm {
    @NotEmpty
    private String name;
    @NotNull
    @Min(18)
    private Integer age;
    private String memo;
}

今回はnameとageにのみ、入力値検証アノテーションを追加します。

 3. JSPにform:errorsタグの追加

<form:form modelAttribute="employeeListForm">
<div><form:errors path="*"  /></div>

入力値検証にかかった際に上記のerrorsタグにメッセージが展開されます。

 4. Controllerの修正

@RequestMapping(value = "/employee/list", method = RequestMethod.POST)
public String list(@Valid @ModelAttribute EmployeeListForm form, BindingResult result, Model model) {
    if (result.hasErrors()) {
        model.addAttribute("title", "エラー");
        model.addAttribute("message", "以下のエラーを解消してください");
    } else {
        EmployeeDto dto = new EmployeeDto();
        BeanUtils.copyProperties(form, dto);
        employeeList.add(dto);
        model.addAttribute("title", "社員一覧");
        model.addAttribute("message", form.getName() + "を登録しました。");
        model.addAttribute("employeeListForm", new EmployeeListForm());
    }
    model.addAttribute("employeeList", employeeList);
    return "employee/list";
}

@Validationの追加と、if (result.hasErrors()) {の分岐が追加されました。

これで準備はOKです。再起動して動かしてみましょう。

解説2

【ライブラリの追加】

今回追加したライブラリは二つ。

  • javax.validation

 →java Beans のためのバリデーション仕様

  • org.hibernate

 →ORM。今回はアノテーションのみを利用。@NotEmptyとか

【Form】

@NotEmpty
private String name;
@NotNull
@Min(18)
private Integer age;

 →指定したプロパティがnull or 空の場合にvalidationに引っかかる。今回はnameにのみ指定。

 →読んで字のごとく。nullかどうかをチェック。nullならvalidationにひっかかる。
 ※Integerに@NotEmptyを指定すると

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: java.lang.Integer.

というエラーが発生する。
Integerには@NotEmptyは使えず、@NotNullを使えとのことみたい。

 →数値の最小値を指定。今回は18を指定したため、18以上の値のみが受け付けられる。それ以外はvalidationの対象となる。

他にも色々な入力値検証アノテーションが用意されているので、以下、公式のJavaDocを参考にしてみてください。
http://docs.oracle.com/javaee/7/api/javax/validation/constraints/package-summary.html

また、validationAPIだけでなく、hibernateのアノテーションも利用しているため、こちらも参考にしてください。
https://access.redhat.com/documentation/ja-JP/JBoss_Enterprise_Application_Platform/5/html-single/Hibernate_Validator_Reference_Guide/index.html

【Controller】

今回注目するのは以下だけでよい。

public String list(@Valid @ModelAttribute EmployeeListForm form, BindingResult result, Model model) {
if (result.hasErrors()) {

気づいた人もいるかと思うが、仮引数の順番が最初の例と変わっている。
私も嵌ったのですが、

public String list(@Valid @ModelAttribute EmployeeListForm form, Model model, BindingResult result)

これだとForm送信時に400エラーが返却されました。(リクエストにエラーがありますってステータスです。)
Springのドキュメントを見ると、

The Errors or BindingResult parameters have to follow the model object that is being bound immediately as the method signature might have more that one model object and Spring will create a separate BindingResult instance for each of them so the following sample won't work:

と記述されてます。
つまり、入力値を検証するオブジェクトのすぐ後ろにBindingResultは宣言しないとダメだよってことみたいです。
…。
なので@Validに指定したオブジェクトの次に、BindingResultを定義することが求められます。お気をつけください。。。

話が少しずれましたが、BindingResultには、Formの入力値検証で引っかかったvalidation結果が格納されます。
エラーであった場合にのみ、erroesに情報が格納されます。
そのため、

if (result.hasErrors()) {

でエラー情報があったとき、無かったときで処理を分岐させています。

【JSP】

<form:errors path="*"  />

上記はBindingResultがhasErrorsの時にのみ、値が出力されます。
「path="*"」とすることで、EmployeeListForm.javaの全てのプロパティを参照しにいってます。
これは局所的に指定することも可能であり、その場合は、「path = "name"」とかでも指定できます。

【その他】

入力値にエラーがあった場合、画面には
may not be empty」や「must be greater than or equal to 18」と表示されていました。
アノテーションを指定するだけで、プログラマはエラーチェックロジックや、メッセージの設定等の面倒な作業が不要
で便利そうです。
しかし、このメッセージはどこを参照して表示されているのでしょうか??
また、日本語に変えたいときとかはどうしたら良いでしょうか??

まず、今表示されている英語のメッセージは以下のpropertiesファイルを内部では参照しています。

prop.png

このファイルを開くと

javax.validation.constraints.AssertFalse.message = must be false
javax.validation.constraints.AssertTrue.message  = must be true
javax.validation.constraints.DecimalMax.message  = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message  = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message      = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Future.message      = must be in the future
javax.validation.constraints.Max.message         = must be less than or equal to {value}
javax.validation.constraints.Min.message         = must be greater than or equal to {value}
javax.validation.constraints.NotNull.message     = may not be null
javax.validation.constraints.Null.message        = must be null
javax.validation.constraints.Past.message        = must be in the past
javax.validation.constraints.Pattern.message     = must match "{regexp}"
javax.validation.constraints.Size.message        = size must be between {min} and {max}

org.hibernate.validator.constraints.CreditCardNumber.message = invalid credit card number
org.hibernate.validator.constraints.Email.message            = not a well-formed email address
org.hibernate.validator.constraints.Length.message           = length must be between {min} and {max}
org.hibernate.validator.constraints.NotBlank.message         = may not be empty
org.hibernate.validator.constraints.NotEmpty.message         = may not be empty
org.hibernate.validator.constraints.Range.message            = must be between {min} and {max}
org.hibernate.validator.constraints.SafeHtml.message         = may have unsafe html content
org.hibernate.validator.constraints.ScriptAssert.message     = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.URL.message              = must be a valid URL
org.hibernate.validator.constraints.br.CNPJ.message          = invalid Brazilian corporate taxpayer registry number (CNPJ)
org.hibernate.validator.constraints.br.CPF.message           = invalid Brazilian individual taxpayer registry number (CPF)
org.hibernate.validator.constraints.br.TituloEleitor.message = invalid Brazilian Voter ID card number

こんな文字列が定義されいます。

今回使った「NotEmpty」はあるかな??

org.hibernate.validator.constraints.NotEmpty.message         = may not be empty

ありました!!ここのメッセージを使用しているんですね。

propファイルは他にも多数ありますが、各国語用にそれぞれpropが用意されているんですね。
標準は「ValidationMessages.properties」ファイルが読まれます。特別なことはしてないです。

じゃー日本語にするには??それは次の項でやります。

ValidationMessageを日本語にする

Validationメッセージを日本語化するには、二通りの方法があります。

  1. アノテーションにメッセージを指定する
  2. Japaneseなpropertiesファイルを用意する

1のアノテーションで設定する方法は以下です。

【アノテーションで指定】

▼修正前

@NotEmpty
private String name;

▼修正後

@NotEmpty(message = "名前は必須です")
private String name;

こうすると、以下のように表示されます。

名前.png

また、こんな使い方も出来ます。

▼修正前

@Min(18)
private Integer age;

▼修正後

@Min(value = 18, message = "{value}以上の値を設定してください")
private Integer age;

「{value}」で指定した値を参照することもできます。
以下みたいに表示されます。

18以上.png

【propertiesで指定】

日本語プロパティを作成していきましょう。

 1. 「src/main/resources」下に「ValidatorMessages_ja.properties」を作成

中身は以下。日本語はUnicodeエスケープして記述する必要がありますが、
リソースを開く際に、「PropertiesEdotorで開く」を選択すれば日本語で記述できます。
PropertiesEdotorで開き、作成してください。

NotEmpty={0}は必須です
NotNull={0}は必須です
Min={0}は、{1}以上の値を指定してください

念のため、言語指定されていないケースを踏まえ、デフォルトも用意しておきます。
 2. 「src/main/resources」下に「ValidatorMessages.properties」を作成

中身は以下

NotEmpty=must not empty.
NotNull=must not null.
Min=must be greater than or equal to {1}.

 3. mvc-config.xmlへの追記

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
 <property name="basename" value="classpath:ValidatorMessages" />
</bean>

上記を

<mvc:annotation-driven />

下に追加する。

これで準備OKです。

画面を確認してみましょう。

prop日本.png

解説です。

mvc-config.xmlに記述した

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="classpath:ValidatorMessages" />
</bean>

により、「src/main/resources」下のpropertiesファイルを参照しにいくようになります。
ReloadableResourceBundleMessageSourceクラスに渡される「basename」プロパティがそれを実現させています。

独自Validationを作成する

ここまでで、一通りのValidationを使ってみました。
しかし、デフォルトで用意されているValidatorアノテーションだけだと、要件を適えにくいケースも出てくるかと思います。
そんな時は独自にValidatorを作成することもできます。

早速作っていきましょう。

 1. MemoValidator.javaの作成

「jp.co.kenshu.validator」下に「MemoValidator.java」を以下のように作成する。

package jp.co.kenshu.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import jp.co.kenshu.validator.annotation.Memo;

public class MemoValidator implements ConstraintValidator<Memo, String> {

    @Override
    public void initialize(Memo memo) {
    }

    @Override
    public boolean isValid(String input, ConstraintValidatorContext con) {
        if (input == null) {
            return false;
        }
        if (input.matches("^[0-9]*$")) {
            return false;
        }
        return true;
    }
}

 2. Memoアノテーションを作成

「jp.co.kenshu.validator.annotation」下に「Memo.java」を以下のように作成する

package jp.co.kenshu.validator.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import jp.co.kenshu.validator.MemoValidator;

@Documented
@Constraint(validatedBy = MemoValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Memo {
    String message() default "Please input a memo.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

 3. 日本語エラーメッセージ定義を指定する

「/src/main/resources/ValidatorMessages_ja.properties」に以下を追記

Memo={0}は文字列のみを指定してください

 4. EmployeeListFormに「@Memo」を追加

以下のように、String memoにアノテーションを付与する。

@Memo
private String memo;

これで以下のような画面がでれば成功です。
※Memoの項目を空でForm送信
memo日本.png

完成です。

解説3

@interface」は独自アノテーションを作成するときに指定します。Spring関係ないです。Javaです。

@Documented

JavaDocによってドキュメント化されることを示しています。

@Constraint(validatedBy = MemoValidator.class)

Validatorを行うクラスを指定しています。
今回新規で作成したMemoValidatorを指定しています。

@Target({ ElementType.METHOD, ElementType.FIELD })

アノテーションが何に対して有効になるかを指定しています。
メソッドとフィールドに対して付与できるアノテーションを表しています。

@Retention(RetentionPolicy.RUNTIME)

アノテーションの有効範囲を指定しています。
RetentionPolicy.RUNTIMEは、そのアノテーションが実行される際に、JVMに情報が読み込まれる挙動をとります。
そのため、リフレクションを用いてアノテーションの情報を取得することが出来ます。
それ以外にもRetentionPolicyクラスに定数があるので確認してみてください。

  • MemoValidatorの解説

実際に入力値を検証するクラスです。

implements ConstraintValidator<Memo, String>

この記述は「String型の文字列をMemo型を用いてValidatorします」という意味になります。

public boolean isValid(String input, ConstraintValidatorContext con)

実際に検証を行っているメソッドです。
第一引数にクライアントが入力した値が渡ってきます。
return値がfalseであった際に、validatorにひっかかったこととなり、propertiesに記述されたメッセージが呼び出されることになります。

独自Validationにちょい足しする

「Memo」バリデーションに以下の要件が追加要望として上がったとします。

memoは50文字以内の文字列を指定
※ただし、この指定文字数は頻繁に変わる可能性があります。

これを叶えるために、MemoValidatorを修正していきましょう。

↓まずは@interface Memoの修正です。

@Documented
@Constraint(validatedBy = MemoValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Memo {
    String message() default "Please input a memo.";

+   int value();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

「int value」が追加されました。

続いてMemoValidator.javaの修正

public class MemoValidator implements ConstraintValidator<Memo, String> {

+   private int maxValue;

    @Override
    public void initialize(Memo memo) {
+       this.maxValue = memo.value();
    }

    @Override
    public boolean isValid(String input, ConstraintValidatorContext con) {
        if (input == null) {
            return false;
        }
        if (input.matches("^[0-9]*$")) {
            return false;
        }
+       if (maxValue < input.length()) {
+           return false;
+       }
        return true;
    }

}

フィールド値とチェックロジックが追加されました。

最後にメッセージの修正です。
ValidatorMessages_ja.propertiesを以下に修正してください。

NotEmpty={0}は必須です
NotNull={0}は必須です
Min={0}は、{1}以上の値を指定してください
Memo={0}は{1}字以内の文字列を指定してください

これで完了です。画面から正常に検証が走るか、試してみてください。

memokai.png


解説4

今回はメモの文字数制限を設けました。

そんな難しいことやってないので、一つづつ解説を書いていきます。

まずはAnnotation

@Documented
@Constraint(validatedBy = MemoValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Memo {
    String message() default "Please input a memo.";

+   int value();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

int value()が追加されただけです。
これだけでアノテーションに指定できるオプションが追加されます。

結果、Formでは

@Memo(50)
private String memo;

と記述することができました。
ちなみに、「@Memo(50)」は「@memo(value = 50)」と同義です。
単一指定プロパティのみ、省略して値だけ記述することができます。

ただ、これだけだと「50」はなんの意味も持ちませんので、
実際に検証を行っているMemoValidator.javaに検証ロジックを追加する必要があります。

public class MemoValidator implements ConstraintValidator<Memo, String> {

+   private int maxValue;

    @Override
    public void initialize(Memo memo) {
+       this.maxValue = memo.value();
    }

    @Override
    public boolean isValid(String input, ConstraintValidatorContext con) {
        if (input == null) {
            return false;
        }
        if (input.matches("^[0-9]*$")) {
            return false;
        }
+       if (maxValue < input.length()) {
+           return false;
+       }
        return true;
    }

}

これです。
最初に

private int maxValue;

このクラスのフィールドとして「maxValue」を宣言してます。
そして、アノテーションに指定された値を受け取るために、

this.maxValue = memo.value();

として代入してます。
こう書けるのは、

implements ConstraintValidator<Memo, String>

のMemo型を約束されており、型の安全性が保たれているために

@Override
public void initialize(Memo memo) {

と、初期化処理にMemo型が渡せるようになっているためです。

結果として、

@Memo(50)
private String memo;

の「50」がMemoValidator.javaのフィールド値に代入される挙動をとってます。

今回は以上です。
次回はMyBatis連携したDB接続サンプルになります。

validationの{0}にフィールド名ではなく、日本語名を当て込む方法

@NotEmpty
private String name;

とし、プロパティに

NotEmpty={0}は必須です

とした場合、画面に表示されるエラーメッセージはnameは必須ですとなります。

これでは日本人の私たちには少し不便ですよね。
可能であれば、名前は必須ですと表示したいですね。
プロパティに以下のように記載すればそれが可能です。

NotEmpty={0}は必須です
name=名前

nameというプロパティが渡ってきた際に、「名前」と変換してくれます
便利ですね。