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

Spring MVC - @Valid в списке beans в службе REST

В службе Spring MVC REST (json) у меня есть метод контроллера, подобный этому:

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody List<MyBean> request, BindingResult bindingResult) {

Где класс MyBean имеет bean аннотации проверки.

В этом случае валидация не происходит, хотя она хорошо подходит для других контроллеров.

Я не хочу инкапсулировать список в dto, который изменил бы вход json.

Почему нет проверок для списка beans? Каковы альтернативы?


4b9b3361

Ответ 1

@Valid - это аннотация JSR-303, а JSR-303 применяется для проверки на JavaBeans. java.util.List не является JavaBean (согласно официальному описанию JavaBean), поэтому его нельзя проверить напрямую с помощью совместимого с JSR-303 валидатора. Это подтверждается двумя наблюдениями.

Раздел 3.1.3 Спецификации JSR-303 гласит, что:

В дополнение к поддержке проверки экземпляров, также поддерживается проверка графиков объекта. Результат проверки графа возвращается в виде унифицированного набора нарушений ограничений. Рассмотрим ситуацию, когда бин X содержит поле типа Y. Аннотируя поле Y аннотацией @Valid, Валидатор проверяет Y (и его свойства), когда проверяется X. Точный тип Z значения, содержащегося в объявленном поле типа Y (подкласс, реализация), определяется во время выполнения. Используются определения ограничений Z. Это обеспечивает правильное полиморфное поведение для ассоциаций, помеченных @Valid.

Значения коллекций, массивы и вообще итерируемые поля и свойства также могут быть украшены аннотацией @Valid. Это приводит к проверке содержимого итератора. Любой объект, реализующий java.lang.Iterable, поддерживается.

Я выделил важные части информации жирным шрифтом. Этот раздел подразумевает, что для того, чтобы тип коллекции был проверен, он должен быть инкапсулирован внутри bean-компонента (подразумевается Consider the situation where bean X contains a field of type Y); и далее, что коллекции не могут быть проверены напрямую (что подразумевается под Collection-valued, array-valued and generally Iterable fields and properties may also be decorated с акцентом на полях и свойствах).

Актуальные реализации JSR-303

У меня есть пример приложения, которое проверяет валидацию коллекций с помощью Hibernate Validator и Apache Beans Validator. Если вы запускаете тесты для этого образца как mvn clean test -Phibernate (с Hibernate Validator) и mvn clean test -Papache (для Beans Validator), оба отказываются проверять коллекции напрямую, что, по-видимому, соответствует спецификации. Поскольку Hibernate Validator является эталонной реализацией для JSR-303, этот пример является еще одним доказательством того, что коллекции должны быть инкапсулированы в bean-компоненте для проверки.


После этого я бы сказал, что при попытке передать коллекцию методу контроллера непосредственно способом, показанным в вопросе, также существует проблема проектирования. Даже если проверки должны были работать непосредственно с коллекциями, метод контроллера не сможет работать с альтернативными представлениями данных, такими как пользовательские XML, SOAP, ATOM, EDI, буферы протокола Google и т.д., Которые не отображаются непосредственно в коллекции. Для поддержки этих представлений контроллер должен принимать и возвращать экземпляры объекта. Это потребует инкапсуляции коллекции внутри экземпляра объекта любым способом. Поэтому было бы очень желательно заключить List в другой объект, как предлагали другие ответы.

Ответ 2

Единственный способ, которым я смог найти это, - это обернуть список, это также означает, что вход JSON должен измениться.

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody List<MyBean> request, BindingResult bindingResult) {

становится:

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody MyBeanList request, BindingResult bindingResult) {

и нам также необходимо:

import javax.validation.Valid;
import java.util.List;

public class MyBeanList {

    @Valid
    List<MyBean> list;

    //getters and setters....
}

Похоже, что это также может быть возможно с помощью специального validatior для списков, но я еще не получил этого.

Аннотации @Valid являются частью стандартного API-интерфейсов JSR-303 Bean и не являются конструкцией Spring. Spring MVC будет проверять объект @Valid после привязки так долго, как был настроен соответствующий валидатор.

Ссылка: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html

Ответ 3

Попробуйте прямое подтверждение. Что-то вроде этого:

@Autowired
Validator validator;

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public Object myMethod(@RequestBody List<Object> request, BindingResult bindingResult) {
    for (int i = 0; i < request.size(); i++) {
        Object o = request.get(i);
        BeanPropertyBindingResult errors = new BeanPropertyBindingResult(o, String.format("o[%d]", i));
        validator.validate(o, errors);
        if (errors.hasErrors())
            bindingResult.addAllErrors(errors);
    }
    if (bindingResult.hasErrors())
        ...

Ответ 4

Использование com.google.common.collect.ForwardingList

public class ValidList<T> extends ForwardingList<T> {

  private List<@Valid T> list;

  public ValidList() {
    this(new ArrayList<>());
  }

  public ValidList(List<@Valid T> list) {
    this.list = list;
  }

  @Override
  protected List<T> delegate() {
    return list;
  }

  /** Exposed for the {@link javax.validation.Validator} to access the list path */
  public List<T> getList() {
    return list;
  }
}

Так что нет необходимости в обертке

вы можете использовать

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody ValidList<MyBean> request, BindingResult bindingResult) {

Используя обертку, ваш JSON должен быть изменен на

{
  "list": []
}

с этой реализацией вы можете использовать оригинальный JSON

[]

Ответ 5

Внедрите свой собственный валидатор с помощью org.springframework.validation.beanvalidation.LocalValidatorFactoryBean как член и вызовите этот валидатор для каждого элемента.

public class CheckOutValidator implements Validator {


    private Validator validator;

   @Override
    public void validate(Object target, Errors errors) { 
    List request = (List) target;
    Iterator it = request.iterator()   
    while(it.hasNext()) {
    MyBean b = it.next();
    validator.validate(b, errors);

     }

     }

//setters and getters

}

Ответ 6

Существует элегантный способ обернуть ваш запрос в пользовательский java.util.List, который действует как List и JavaBean. см. здесь

Ответ 7

Если вы не хотите писать оболочку для каждого списка, у вас есть, вы можете использовать общую оболочку:

public class ListWrapper<E> {

    private List<E> list;

    public ListWrapper() {
        list = new ArrayList<>();
    }

    public ListWrapper(List<E> list) {
        this.list = list;
    }

    @Valid
    public List<E> getList() {
        return list;
    }

    public void setList(List<E> list) {
        this.list = list;
    }

    public boolean add(E e) {
        return list.add(e);
    }

    public void clear() {
        list.clear();
    }

}

Ответ 9

@Valid @RequestBody List<MyBean> request

работает для меня, пока вы отправляете действительный JSON: -

[
    {
        "property1": "value1",
        "property2": "value2"
      },
    {
        "property1": "value3",
        "property2": "value4"
        }
]