【SpringBoot】バリデーション（@Validated / @Valid）を任意のタイミングで動かす【BeanValidation】

検証はSpringBoot2.0.9で行いました。

# TL;DR
単純なバリデーションは、`javax.validation.Validation`の`Validation.buildDefaultValidatorFactory().getValidator()`で取得した`Validator`を用いることで、任意のタイミングでバリデーションできます。

`@Autowired`するバリデーターを用いるアノテーションが絡むような内容は、`Validator`を`@Autowire`でインジェクションして取得し用いることで、任意のタイミングでバリデーションができます。

# 従来の動かし方
バリデーションしたいフォームをコントローラーで受け取る場合には、以下のようにすることで、`BindingResult`にエラーを受け取ることができます。

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/* 省略 */

@PostMapping("/api/test-post-api")
public void editRelayStructs(
        @RequestBody @Validated Form form,
        BindingResult errorResult
) {
    /* 省略 */
}
```

## 従来手法の問題点
単体テストやパラメーターとの相関チェックなど、実際には任意のタイミングでバリデーションを行いたいという場合があります。
その場合、この上に挙げた例の形でバリデーションしてエラーを受け取ることはできません。

# `Validation.buildDefaultValidatorFactory().getValidator()`で`Validator`を取得する方法
`@Autowire`したいなどの理由が無ければ、`javax.validation.Validation`の`Validation.buildDefaultValidatorFactory().getValidator()`で取得した`Validator`を用いることで、任意のタイミングでバリデーションができます。

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

/* 省略 */

@PostMapping("/api/test-post-api")
public void editRelayStructs(
        @RequestBody Form form
) {
    // バリデーション結果を取得
    Set<ConstraintViolation<Form>> errorResult =
            Validation.buildDefaultValidatorFactory().getValidator().validate(form);

    /* 省略 */
}
```

この方法は、単純なバリデーションについてはカバーできます。
一方、このやり方はSpringを介さずにバリデーションを実行するため、`@Autowired`する自作アノテーションはインジェクションが機能しないという問題があります。

# `Validator`を`@Autowire`でインジェクションして取得する方法
前述の通り、`Validation.buildDefaultValidatorFactory().getValidator()`で取得した`Validator`では、`@Autowired`して検証を行うようなバリデータークラスが機能しません。

```java:@Autowiredするバリデータークラスの例
import org.springframework.beans.factory.annotation.Autowired;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class ExistingIdValidator implements ConstraintValidator<ExistingId,Integer> {
    @Autowired
    public Dao dao;

    @Override
    public boolean isValid(Integer id, ConstraintValidatorContext context) {
        if (id == null) {
            return true;
        }
        return /* Daoを用いたIDに対する存在チェック結果 */;
    }
}
```

この場合、Spring側に定義されている`Validator`を`@Autowire`でインジェクションして取得することで、バリデータークラスでの`@Autowired`が機能します。

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

/* 省略 */

// （本来はコンストラクタパターンでインジェクションすべき）
@Autowired
public Validator validator; // Springに定義されているjavax.validation.Validator

@PostMapping("/api/test-post-api")
public void editRelayStructs(
        @RequestBody Form form
) {
    // バリデーション結果を取得
    Set<ConstraintViolation<Form>> errorResult = validator.validate(form);

    /* 省略 */
}
```