Весенняя проверка параметров RequestBody, связанных с коллекциями в методах Controller

Я имею

Сущность:

package org.ibp.soq;

public class MyEntity {

    private String field1;
    private String field2;

    //..getters and setters

}

Валидатор для сущности:

package org.ibp.soq;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Component
public class MyEntityValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return MyEntity.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        MyEntity myEntity = (MyEntity) target;
        // Logic to validate my entity
        System.out.print(myEntity);
    }

}

а также

Контроллер REST с методом объемного PUT:

package org.ibp.soq;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/myEntity")
public class MyEntityRestResource {

    @Autowired
    private MyEntityValidator myEntityValidator;

    @InitBinder
    protected void initBinder(final WebDataBinder binder) {
        binder.addValidators(this.myEntityValidator);
    }

    @RequestMapping(method = RequestMethod.PUT)
    public void bulkCreate(@RequestBody @Valid List<MyEntity> myEntities) {
        // Logic to bulk create entities here.
        System.out.print(myEntities);
    }
}

Когда я делаю запрос PUT к этому ресурсу со следующим телом запроса:

[
  {
    "field1": "AA",
    "field2": "11"
  },

  {
    "field1": "BB",
    "field2": "22"
  }
]

Я получаю ошибку:

"Invalid target for Validator [org.ibp.soq.MyEntityValidator@4eab617e]: [org.ibp.soq.MyEntity@21cebf1c, org.ibp.soq.MyEntity@c64d89b]"

Я могу понять, что это потому, что MyEntityValidator "поддерживает" сингл MyEntity проверка, а не проверка для ArrayList<MyEntity>,

MyEntityValidator работает отлично, если у меня есть один MyEntity объект в теле запроса и соответствующий метод контроллера с @RequestBody @Valid MyEntity myEntity параметр.

Как можно расширить настройки валидатора, которые я использовал, для поддержки валидации коллекции MyEntityх?

3 ответа

Решение заключается в создании пользовательских Validator за Collection и @ControllerAdvice что это регистрирует Validator в WebDataBinders,

Оценщик:

import java.util.Collection;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

/**
 * Spring {@link Validator} that iterates over the elements of a 
 * {@link Collection} and run the validation process for each of them
 * individually.
 *   
 * @author DISID CORPORATION S.L. (www.disid.com)
 */
public class CollectionValidator implements Validator {

  private final Validator validator;

  public CollectionValidator(LocalValidatorFactoryBean validatorFactory) {
    this.validator = validatorFactory;
  }

  @Override
  public boolean supports(Class<?> clazz) {
    return Collection.class.isAssignableFrom(clazz);
  }

  /**
   * Validate each element inside the supplied {@link Collection}.
   * 
   * The supplied errors instance is used to report the validation errors.
   * 
   * @param target the collection that is to be validated
   * @param errors contextual state about the validation process
   */
  @Override
  @SuppressWarnings("rawtypes")
  public void validate(Object target, Errors errors) {
    Collection collection = (Collection) target;
    for (Object object : collection) {
      ValidationUtils.invokeValidator(validator, object, errors);
    }
  }
}

ControllerAdvice:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;

/**
 * Controller advice that adds the {@link CollectionValidator} to the 
 * {@link WebDataBinder}.
 * 
 * @author DISID CORPORATION S.L. (www.disid.com)
 */
@ControllerAdvice
public class ValidatorAdvice {

  @Autowired
  protected LocalValidatorFactoryBean validator;


  /**
   * Adds the {@link CollectionValidator} to the supplied 
   * {@link WebDataBinder}
   * 
   * @param binder web data binder.
   */
  @InitBinder
  public void initBinder(WebDataBinder binder) {
    binder.addValidators(new CollectionValidator(validator));
  }
}

Как вы уже догадались, этого нельзя достичь с помощью Spring Validation. Spring Validation реализует Bean Validation(JSR 303/349) в отличие от проверки объекта. К сожалению, коллекция не является Java Bean. У вас есть два варианта

  • Оберните ваш список внутри Java Bean
  • Вызовите валидатор вручную в вашем методе массового создания. myEntityValidator. validate(targetObject, errors),

На самом деле, это может быть достигнуто с помощью Spring Validation и JSR303.

  • Предоставьте bean-компонент MethodValidationPostProcessor.
  • Аннотируйте свой класс контроллера с помощью @Validated (org.springframework.validation.annotation.Validated)
  • Используйте аннотации проверки JSR303 в ваших полях / методах MyEntity.
  • Аннотируйте свой аргумент RequestBody с помощью @Valid (вы уже сделали это в своем примере).
  • Добавьте метод @ExceptionHandler для обработки MethodArgumentNotValidException. Это можно сделать в контроллере или в классе @ControllerAdvice.
Другие вопросы по тегам