Проверка списка объектов в Spring
У меня есть следующий метод контроллера:
@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid List<CompanyTag> categories,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}
CompanyTag определяется следующим образом:
public class CompanyTag {
@StringUUIDValidation String key;
String value;
String color;
String icon;
Icon iconObj;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
...
}
Проблема в том, что валидация не запускается, список CompanyTag не проверяется, валидатор "StringUUIDValidation" никогда не вызывается.
Если я удалю список и попробую отправить только один CompanyTag, т.е. вместо:
@RequestBody @Valid List<CompanyTag> categories,
использование:
@RequestBody @Valid CompanyTag category,
работает так, как ожидалось, поэтому, по-видимому, Spring не любит проверять списки вещей (вместо этого попытался использовать массив, это тоже не сработало).
Кто-нибудь знает, что не хватает?
Ответы
Ответ 1
Я нашел другой подход, который работает. Основная проблема заключается в том, что вы хотите иметь список как свою полезную нагрузку для своей службы, но javax.validation не будет проверять список, а только JavaBean. Хитрость заключается в том, чтобы использовать собственный класс списка, который функционирует как List и JavaBean:
@RequestBody @Valid List<CompanyTag> categories
Изменить на:
@RequestBody @Valid ValidList<CompanyTag> categories
Подкласс списка будет выглядеть примерно так:
public class ValidList<E> implements List<E> {
@Valid
private List<E> list;
public ValidList() {
this.list = new ArrayList<E>();
}
public ValidList(List<E> list) {
this.list = list;
}
// Bean-like methods, used by javax.validation but ignored by JSON parsing
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
// List-like methods, used by JSON parsing but ignored by javax.validation
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
// Other list methods ...
}
Ответ 2
исходный ответ
Я пытался использовать метод Пола в своем проекте, но некоторые люди говорили, что он слишком сложный. Вскоре после этого я нахожу другой простой способ, который работает, как показано ниже:
@Validated
@RestController
@RequestMapping("/parent")
public class ParentController {
private FatherRepository fatherRepository;
/**
* DI
*/
public ParentController(FatherRepository fatherRepository) {
this.fatherRepository = fatherRepository;
}
@PostMapping("/test")
public void test(@RequestBody @Valid List<Father> fathers) {
}
}
Это работает и прост в использовании. Ключевым моментом является аннотация @Valiated для класса. Кстати, это springBootVersion = '2.0.4.RELEASE', который я использую.
обновление для части обработки исключений
Как сказано в комментариях, здесь мы должны обрабатывать исключения, например, код ниже:
@RestControllerAdvice
@Component
public class ControllerExceptionHandler {
/**
* handle controller methods parameter validation exceptions
*
* @param exception ex
* @return wrapped result
*/
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public DataContainer handle(ConstraintViolationException exception) {
Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
StringBuilder builder = new StringBuilder();
for (ConstraintViolation<?> violation : violations) {
builder.append(violation.getMessage());
break;
}
DataContainer container = new DataContainer(CommonCode.PARAMETER_ERROR_CODE, builder.toString());
return container;
}
}
Принятие кода статуса http в качестве представителя сети - это нормально, и здесь возвращается только первое сообщение о нарушении. Вы можете изменить его, чтобы удовлетворить индивидуальные требования.
Подробнее
С @Validated на уровне класса параметры методов проверяются с помощью так называемой проверки уровня метода при весенней загрузке, которая работает не только для контроллеров, но и для любого компонента, управляемого контейнером IOC
.
Кстати, методы в проверке уровня метода улучшены
- org.springframework.validation.beanvalidation.MethodValidationInterceptor
в то время как типовая проверка методов весеннего загрузочного контроллера обрабатывается в
- org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
Они оба приводят фактическую операцию проверки к org.hibernate.validator.internal.engine.ValidatorImpl
по умолчанию, но методы, которые они вызывают, отличаются, что приводит к различиям в логике проверки.
MethodValidationInterceptor
вызов метода validateParameters
в ValidatorImpl
RequestResponseBodyMethodProcessor
вызов метода validate
в ValidatorImpl
Вы можете углубиться в это для более подробной информации.
Ответ 3
Я бы предложил обернуть категории списка в DTO bean и проверить его. Помимо проверки работоспособности, вы получите более гибкий API.
@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid TagRequest tagRequest,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}
public static class TagRequest {
@Valid
List<CompanyTag> categories;
// Gettes setters
}
Ответ 4
Я думаю, что наиболее элегантным решением является создание пользовательского Validator for Collection и @ControllerAdvice, который регистрирует этот валидатор в WebDataBinders, посмотрите на Spring проверку параметров RequestBody связанные с наборами в методах контроллера
Ответ 5
@Paul Strack отличное решение, смешанное с магией Ломбок:
@Data
public class ValidList<E> implements List<E> {
@Valid
@Delegate
private List<E> list = new ArrayList<>();
}
Использование (список подкачки для ValidList):
public ResponseEntityWrapper updateMapTheme(
@RequestBody @Valid ValidList<CompanyTag> categories, ...)
(нужен Lombok, но если вы его уже не используете, вы действительно хотите попробовать)
Ответ 6
Проверка коллекции не работает напрямую.
Например: что он должен делать, если несколько элементов не прошли проверку? Остановить после первой проверки? Проверить все (если да, что делать с коллекцией сообщений)?
Если в вашей конфигурации Spring делегирует поставщик Bean Validator, такой как Hibernate Validator, вы должны искать способы реализации там, где есть валидатор коллекции.
В Hibernate рассматривается аналогичная проблема здесь
Ответ 7
Я использую Spring-Boot 1.5.19.RELEASE
Я аннотирую свой сервис с помощью @validated
и затем применяю @Valid
к параметру List
в методе, и элементы в моем списке проверяются.
модель
@Data
@ApiModel
@Validated
public class SubscriptionRequest {
@NotBlank()
private String soldToBpn;
@NotNull
@Size(min = 1)
@Valid
private ArrayList<DataProducts> dataProducts;
private String country;
@NotNull
@Size(min = 1)
@Valid
private ArrayList<Contact> contacts;
}
Сервисный интерфейс (или использовать на конкретном типе, если нет интерфейса)
@Validated
public interface SubscriptionService {
List<SubscriptionCreateResult> addSubscriptions(@NonNull @Size(min = 1) @Valid List<SubscriptionRequest> subscriptionRequestList)
throws IOException;
}
Метод Global Exception Handler (ApiError Type не мой дизайн)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
public ApiError[] handleConstraintViolationException(ConstraintViolationException exception) {
List<InvalidField> invalidFields = exception.getConstraintViolations().stream()
.map(constraintViolation -> new InvalidField(constraintViolation.getPropertyPath().toString(),
constraintViolation.getMessage(),
constraintViolation.getInvalidValue()))
.collect(Collectors.toList());
return new ApiError[] {new ApiError(ErrorCodes.INVALID_PARAMETER, "Validation Error", invalidFields)};
}
пример плохого вызова метода из контроллера
LinkedList<SubscriptionRequest> list = new LinkedList<>();
list.add(new SubscriptionRequest());
return subscriptionService.addSubscriptions(list);
Тело ответа (обратите внимание на индекс [0])
[
{
"errorCode": "invalid.parameter",
"errorMessage": "Validation Error",
"invalidFields": [
{
"name": "addSubscriptions.arg0[0].soldToBpn",
"message": "may not be empty",
"value": null
},
{
"name": "addSubscriptions.arg0[0].dataProducts",
"message": "may not be null",
"value": null
},
{
"name": "addSubscriptions.arg0[0].contacts",
"message": "may not be null",
"value": null
}
]
}
]
Ответ 8
использовать @Validated аннотировать контроллер
используйте @Valid annotate @RequestBody
Ответ 9
создать класс сущности:
import javax.validation.Valid;
import java.util.List;
public class ValidList<E> {
@Valid
private List<E> list;
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
}
использовать контроллер
@RequestMapping(value = "/sku", method = RequestMethod.POST)
public JsonResult createSKU(@Valid @RequestBody ValidList<Entity> entityList, BindingResult bindingResult) {
if (bindingResult.hasErrors())
return ErrorTools.build().handlerError(bindingResult);
return new JsonResult(200, "result");
}
Ответ 10
@Valid
аннотация может использоваться внутри оператора Diamond:
private List<@Valid MyType> types;
или
@Valid
private List<MyType> types;
Теперь каждый элемент списка будет проверен.