Spring Boot 自作アノテーションを作成して複数項目にvalidationをかける


## やりたいこと

リクエストクラスでは以下`@NotNull`や`@Size`のような
`@`のついたアノテーションで入力チェックをすることができる。
用意されているアノテーションは一つの項目のみしかチェックを行うことができない。
複数の項目を検証するアノテーションが欲しいため、独自に作成したアノテーションで入力チェックを行いたい。


#### リクエストクラス

```.Java 
public class SampleRequest {

  // nullは不可
  @NotNull
  private Integer id;

  // 1〜５０文字
  @Size(min = 1, max = 50)
  private String name;

  private String name1;

  private String name2;

  private String name3;

  public Sample convert() {
    Sample sample = new Sample();
    sample.setId(id);
    sample.setName(name);
    sample.setName(name1);
    sample.setName(name2);
    sample.setName(name3);
    return sample;
  }

}
```

## 今回やること

上記のリクエストクラスで、「name1, name2, name3のうち２つ以上の入力が必要」
という入力チェックを行うアノテーションを作成する。


### 1. 入力チェック内容を記載したクラスを作る

どんな入力チェックを行いたいかを記載する。

```.Java

package test.pj.web.validation;

import java.util.ArrayList;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

// ConstraintValidator の実装クラスとして作成する
// <>内の引数1つ目は下記2のアノテーションクラス名
public class SampleAnnotationValidation
    implements ConstraintValidator<SampleAnnotation, Object> {

  // バリデーション対象の変数の値を入れる
  private String[] fields;
  // アノテーションクラス（下記2）で設定しているエラーメッセージが入る
  private String message;

  /**
   * 初期化処理 
   * ()内のクラスは下記2のアノテーションクラス
   */
  public void initialize(SampleAnnotation annotation) {
    this.fields = annotation.fields();
    this.message = annotation.message();
  }

  /**
   *  自分でバリデーション内容を設定する
   */
  @Override
  public boolean isValid(Object value, ConstraintValidatorContext context) {

    BeanWrapper beanWrapper = new BeanWrapperImpl(value);

    // 入力されている回数をカウントする変数を作成
    Integer idCount = 0;

    for (String string : fields) {
      // 以下で、fieldValueに、validation対象の各値（今回の場合、name1, name2, name3）が入る
      Object value = beanWrapper.getPropertyValue(string);

      // 入力がある場合、入力カウントに1を足しておく
      if (value != "") {
        idCount += 1;
      }
    }

    // 入力数が2以上のときはtrueを返す
    if (idCount >= 2)) {
      return true;
    }

    // それ以外はfalseを返し、messageを出す
    context.disableDefaultConstraintViolation();
    context.buildConstraintViolationWithTemplate(message).addPropertyNode(fields[0])
        .addConstraintViolation();
    return false;
  }

}

```


### 2. アノテーションクラスを作る

`@hogehoge`の名前の部分を作る

```.Java

package test.pj.web.validation;

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;

@Documented
// 1で記載した入力チェック内容を記載したクラスをかく
@Constraint(validatedBy = {SampleAnnotationValidation.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
// クラス名には、アノテーション名として使いたい名前をつける
public @interface SampleAnnotation {

  // 入力チェック不可だった場合に、表示するエラーメッセージ
  String message() default "name1, name2, name3のいずれか2つ以上を入力してください。";

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

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

  @Target({ElementType.TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  public @interface List {
    SampleAnnotation[] value();
  }

  // チェックする値を格納
  String[] fields();

}
```

### 3.リクエストクラスに自作したアノテーションをつける


```.Java 
package test.pj.web.validation;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import test.pj.web.validation.SampleAnnotation;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
// 2で作成したアノテーション名と、入力チェックを行いたい項目を入れる
@SampleAnnotation(fields = {"name1", "name2", "name3"})
@AllArgsConstructor
@NoArgsConstructor
public class SampleRequest {

  // nullは不可
  @NotNull
  private Integer id;

  // blankは不可、1〜５０文字
  @NotBlank
  @Size(min = 1, max = 50)
  private String name;

  private String name1;

  private String name2;

  private String name3;

  public Sample convert() {
    Sample sample = new Sample();
    sample.setId(id);
    sample.setName(name);
    sample.setName(name1);
    sample.setName(name2);
    sample.setName(name3);
    return sample;
  }

}
```