Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

SpringでBean ValidationのMethod Validationを使う

公式リファレンスにはこうあった。

Spring-driven Method Validation

The method validation feature supported by Bean Validation 1.1, and as a custom extension also by Hibernate Validator 4.3, can be integrated into a Spring context through a MethodValidationPostProcessor bean definition:

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

In order to be eligible for Spring-driven method validation, all target classes need to be annotated with Spring’s @Validated annotation, optionally declaring the validation groups to use. Check out the MethodValidationPostProcessor javadocs for setup details with Hibernate Validator and Bean Validation 1.1 providers.

Additional Configuration Options

The default LocalValidatorFactoryBean configuration should prove sufficient for most cases. There are a number of configuration options for various Bean Validation constructs, from message interpolation to traversal resolution. See the LocalValidatorFactoryBean javadocs for more information on these options.

ここだけ読むとMethodValidationPostProcessorとLocalValidatorFactoryBeanをBean登録したら動くのかなと勘違いして、ちょっとハマってしまった。実際には、MethodValidationPostProcessorインスタンスにLocalValidatorFactoryBeanインスタンスを設定してやらないと、デフォルトからの変更、たとえばここでのようにMessageSourceとしてmessage.propertiesを追加したい場合などが、反映されない。

Bean ValidationのMethod Validationをコントローラのメソッドで使う。メッセージ本文がプロパティファイルにあり、そのキーを指定するときは"{}"でキーを囲む。NotEmptyはorg.hibernate.validator.NotEmptyというHibernate Validatorが独自に提供するValidation。

@Controller
@Validated
public class HogeController {
  @RequestMapping(path = "hoge", method = RequestMethod.POST)
  public String hoge(@NotEmpty(message = "{valid.required}")) {
  }

Springの設定では、さきほどの説明のようにMethodValidationPostProcessorインスタンスの生成時にLocalValidatorFactoryBeanインスタンスをvalidatorとしてセットする。

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
        ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource();
        reloadableResourceBundleMessageSource.setBasename("classpath:/messages");
        localValidatorFactoryBean.setValidationMessageSource(reloadableResourceBundleMessageSource);
        return localValidatorFactoryBean;
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor(LocalValidatorFactoryBean localValidatorFactoryBean) {
        MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
        methodValidationPostProcessor.setValidator(localValidatorFactoryBean);
        return methodValidationPostProcessor;
    }

Method Validationでは、値が無効な場合javax.validation.ConstraintViolationExceptionオブジェクトがスローされる。これをExceptionHandlerで処理する。たとえばコントローラ共通で扱うときは@ControllerAdviceアノテーションをつけたクラスで、@ExceptionHandler(value = { ConstraintViolationException.class })アノテーションをつけたメソッドに処理を記述する。入力値やメッセージ文字列は、getConstraintViolations()を呼び出して各ConstraintViolationオブジェクトから取得できる。

@ControllerAdvice
public class GlobalDefaultExceptionHandler {

    @ExceptionHandler(value = { ConstraintViolationException.class })
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handleIfInvalid(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        violations.stream().forEach(v -> logger.debug("=======" + v.getMessage()));
        return null;
    }
}