Весенняя проверка параметров 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.