Edited at

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

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


TL;DR

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

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


従来の動かし方

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

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.ValidationValidation.buildDefaultValidatorFactory().getValidator()で取得したValidatorを用いることで、任意のタイミングでバリデーションができます。

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<AdCreateForm>> errorResult =
Validation.buildDefaultValidatorFactory().getValidator().validate(form);

/* 省略 */
}

この方法は、単純なバリデーションについてはカバーできます。

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


Validator@Autowireでインジェクションして取得する方法

前述の通り、Validation.buildDefaultValidatorFactory().getValidator()で取得したValidatorでは、@Autowiredして検証を行うようなバリデータークラスが機能しません。


@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が機能します。

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<AdCreateForm>> errorResult = validator.validate(adCreateForm);

/* 省略 */
}