【Java & Spring】オリジナルのバリデーターを作成しよう

## 目標
ユーザーの入力した値が、電話番号かどうかを判断するバリデータ（@PhoneNumber）を作成する。
ここでは入力値が「0~9と-のみ」の場合、電話番号と判断する。


## 実装手順
<ol>
<li>アノテーションクラスを作成する。<li>バリデータークラスを作成する。<li>バリデーションを使用する。</ol>

## 手順 1: アノテーションクラスの作成
まず、アノテーションを作成します。このアノテーションを利用して、入力値が電話番号かどうかを判定します。
```java:
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
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;

// 使用箇所のJavadocにアノテーションの情報が表示されるようにする
@Documented
// バリデータークラスを指定
@Constraint(validatedBy = PhoneNumberValidator.class)
// アノテーションがフィールドやメソッドに適用できるようにする
@Target({ ElementType.FIELD, ElementType.METHOD })
// アノテーションの有効範囲を実行時までに設定
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {

    // エラーメッセージのデフォルト設定
    String message() default "Invalid phone number";

    // グループ化に使う属性（必須）
    Class<?>[] groups() default {};

    // カスタムペイロード（必須）
    Class<? extends Payload>[] payload() default {};
}
```
【解説】
多数のアノテーションが記載されていますが、すべて「必ず書いておかないといけないもの」とご理解ください。<br>またクラス内の``message``、``groups``、``payload``のメソッドについても同様必ず記載しなければいけないメソッドです。``message``メソッドにはエラー時に出力したいメッセージを設定します。しかし使用箇所にて、引数でメッセージ文字列が渡された場合は、そちらの文字列が優先されます。


## 手順 2: バリデーションクラスの作成
次に、バリデーションロジックを実装するクラスを作成します。これは、上記のアノテーションによって適用されます
```java:
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {

    // 電話番号の正規表現 (0-9とハイフンのみを許可)
    private static final String PHONE_NUMBER_PATTERN = "^[0-9\\-]+$";

    // 初期化（カスタムバリデーションでは通常は何もしない）
    @Override
    public void initialize(PhoneNumber constraintAnnotation) {
    }

    // 実際のバリデーションロジック
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // nullの場合は許容される（必須かどうかのチェックは別途行う）
        if (value == null || value.isEmpty()) {
            return true;
        }
        // 正規表現にマッチするかをチェック
        return value.matches(PHONE_NUMBER_PATTERN);
    }
}
```

## 手順 3: 使用するクラスに適用する

次に、このカスタムアノテーションを使用するクラスに適用します。
```java:
import jakarta.validation.constraints.NotNull;

public class UserForm {

    @NotNull(message = "電話番号を入力してください")
    @PhoneNumber(message = "電話番号は、半角数値とハイフン(-)のみで入力してください")
    private String phoneNumber;

    // ゲッターとセッター
    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}

```
## 手順 4: コントローラーでバリデーションを使用する
次に、Springのコントローラーで、フォームオブジェクトに対してバリデーションを実行します。
```java:
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.validation.Valid;

@Controller
@RequestMapping("/user")
public class UserController {

    @PostMapping("/submit")
    public String submitForm(@Valid @ModelAttribute UserForm userForm, BindingResult result) {
        if (result.hasErrors()) {
            // バリデーションエラーがある場合は、エラーページやフォームに戻す
            return "form";
        }
        // 成功した場合の処理
        return "success";
    }
}

```