[英]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.