簡體   English   中英

Spring MVC - @Valid 在 REST 服務中的 bean 列表中

[英]Spring MVC - @Valid on list of beans in REST service

在 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 輸入。

為什么沒有對 bean 列表的驗證? 有哪些選擇?


@Valid是 JSR-303 注釋,JSR-303 適用於 JavaBeans 上的驗證。 java.util.List不是 JavaBean(根據 JavaBean 的官方描述),因此它不能直接使用符合 JSR-303 的驗證器進行驗證。 這得到了兩個觀察結果的支持。

JSR-303 規范的3.1.3節說:

除了支持實例驗證,還支持對象圖的驗證。 圖驗證的結果作為一組統一的約束違規返回。 考慮bean X 包含類型 Y 的字段的情況 通過使用 @Valid 注釋字段 Y 進行注釋,驗證器將在 X 被驗證時驗證 Y(及其屬性) 包含在聲明為類型 Y(子類、實現)的字段中的值的確切類型 Z 是在運行時確定的。 使用 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暗示, 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 (with Hibernate Validator) 和mvn clean test -Papache (for Beans Validator),兩者都拒絕直接驗證集合,這似乎符合規范。 由於 Hibernate Validator 是 JSR-303 的參考實現,此示例進一步證明了集合需要封裝在 bean 中才能進行驗證。


清除后,我會說嘗試以問題中所示的方式將集合直接傳遞給控制器​​方法也存在設計問題。 即使驗證直接在集合上工作,控制器方法也無法使用替代數據表示,例如自定義 XML、SOAP、ATOM、EDI、Google Protocol Buffers 等,它們不直接映射到集合。 為了支持這些表示,控制器必須接受並返回對象實例。 這需要以任何方式將集合封裝在對象實例中。 因此,如其他答案所建議的那樣,將List包裝在另一個對象中是非常可取的。

我能找到的唯一方法是包裝列表,這也意味着 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....
}

這看起來也可以使用列表的自定義驗證器,但我還沒有那么遠。

@Valid 注釋是標准 JSR-303 Bean Validation API 的一部分,不是特定於 Spring 的構造。 只要配置了適當的驗證器,Spring MVC 就會在綁定后驗證 @Valid 對象。

參考: http : //docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html

嘗試直接驗證。 像這樣的東西:

@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())
        ...

有一種優雅的方法可以將您的請求包裝在一個自定義java.util.List ,它同時充當ListJavaBean 看這里

使用 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

[]

鑒於用於 JSON 序列化的 Spring-Boot + Jackson + org.springframework.boot:spring-boot-starter-validation (必須手動包含 Spring Boot >= 2.3.0)

使用內置插件

  • @Validated添加到您的控制器
  • 在控制器方法簽名中使用@Valid @NotNull @RequestBody List<@Valid Pojo> pojoList

這將在無效 bean 上拋出javax.validation.ConstraintViolationException 500 Internal Error ,默認情況下映射到500 Internal Error 因此,請確保您也有一個ControllerAdvice

使用包裝器

列表包裝器很好(即具有List<E>類型的單個字段的類),但是從上面的響應中,您還必須更改 JSON( {"list": []} vs [] ) ,這不是很好...

解決方案:

  • 在包裝器中,在包裝的列表字段上使用@JsonValue注釋
  • 添加一個以列表為參數的構造函數,並使用@JsonCreator對其進行@JsonCreator
  • 在您的控制器方法中,使用@Valid @RequestBody ListWrapper<Pojo> tokenBodies

這有效,很優雅,不需要更多。 此外,它會在無效 bean 上拋出通常的org.springframework.web.bind.MethodArgumentNotValidException


包裝示例(java)

(有關Kotlin 中的完整示例,請參閱https://stackoverflow.com/a/64060909

public class ValidList<E> {
    @JsonValue
    @Valid
    @NotNull
    @Size(min = 1, message = "array body must contain at least one item.")
    private List<E> values;

    @JsonCreator
    public ValidList(E... items) {
        this.values = Arrays.asList(items);
    }

    public List<E> getValues() {
        return values;
    }

    public void setValues(List<E> values) {
        this.values = values;
    }
}
public class SomePojo {
    @Min(value = 1)
    int id;

    @Size(min = 2, max = 32)
    String token;

    // getters and setters
}
@RestController
public class SomeController {

    @PostMapping("/pojos")
    public ValidList<SomePojo> test(@Valid @RequestBody ValidList<SomePojo> pojos) {
        return pojos;
    }
}

提交確定:

curl -H "Content-Type: application/json" -X POST http://localhost:8080/pojos -d '[{"id": 11, "token": "something"}]'
[{"token" : "something", "id" : 11}]

提交空體:

curl -H "Content-Type: application/json" -X POST http://localhost:8080/ns -d '[]'
{
   "timestamp" : "2020-09-25T09:55:05.462+00:00",
   "error" : "Bad Request",
   "message" : "Validation failed for object='validList'. Error count: 1",
   "exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
   "path" : "/pojos",
   "status" : 400,
   "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.ValidList<com.example.demo.SomePojo> com.example.demo.SomeController.test(com.example.demo.ValidList<com.example.demo.SomePojo>): [Field error in object 'validList' on field 'values': rejected value [[]]; codes [Size.validList.values,Size.values,Size. [...]"
}

提交無效項目:

curl -H "Content-Type: application/json" -X POST http://localhost:8080/ns -d '[{"id": -11, "token": ""}]'
{
   "timestamp" : "2020-09-25T09:53:56.226+00:00",
   "error" : "Bad Request",
   "message" : "Validation failed for object='validList'. Error count: 2",
   "exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
   "path" : "/pojos",
   "status" : 400,
   "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.ValidList<com.example.demo.SomePojo> com.example.demo.SomeController.test(com.example.demo.ValidList<com.example.demo.SomePojo>) with 2 errors: [Field error in object 'validList' on field 'values[0].id': rejected value [-11]; co [...]"
}

使用 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

}

如果您不想為您擁有的每個 List 編寫包裝器,則可以使用通用包裝器:

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();
    }

}

我認為你最好的選擇是包裝列表 - 如果它不是 Spring MVC 中的 bean,如何驗證請求參數?

沒有辦法 atm 說@Valid適用於集合的元素。

@Valid @RequestBody List<MyBean> request

只要您提交有效的 json,就對我有用:-

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM