[英]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
,它同時充當List
和JavaBean
。 看這里
使用 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.