Подтвердить что ты не робот

Проверка списка объектов в 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 не любит проверять списки вещей (вместо этого попытался использовать массив, это тоже не сработало).

Кто-нибудь знает, что не хватает?

4b9b3361

Ответ 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
}

Ответ 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;

Теперь каждый элемент списка будет проверен.