在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列表进行验证?有哪些替代方案?
答案 0 :(得分:41)
@Valid
是JSR-303注释,JSR-303适用于JavaBeans上的验证。 java.util.List
不是JavaBean(根据JavaBean的official description),因此无法使用符合JSR-303的验证器直接验证它。这得到了两个观察结果的支持。
JSR-303 Specification的 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
隐含,重点是字段和属性)。
实际的JSR-303实施
我有a sample application用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
包装在另一个对象中,如其他答案所示。
答案 1 :(得分:12)
我能找到的唯一方法就是包装列表,这也意味着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的构造。 只要配置了适当的Validator,Spring MVC就会在绑定后验证@Valid对象。
参考:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html
答案 2 :(得分:5)
尝试直接验证。像这样:
@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())
...
答案 3 :(得分:2)
使用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
}
答案 4 :(得分:1)
有一种优雅的方式可以将您的请求包装在自定义java.util.List
中,该List
同时充当JavaBean
和abs(r1) > 15
。 see here
答案 5 :(得分:1)
如果您不想为每个List编写包装器,可以使用通用包装器:
var tagsMeta = [];
//alert(tagsMeta);
var tagsString = document.getElementById('metaTags').value;
//alert(tagsString);
var tagsArray = tagsString.split(',');
for(i=0; i < tagsArray.length; i++) {
tagsMeta.push({tag: tagsArray[i]});
}
$('.chips-initial').material_chip({
data: tagsMeta
});
答案 6 :(得分:1)
给Spring-Boot + Jackson进行JSON序列化+ 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
。因此,请确保您也为此使用ControllerAdvice
!
使用包装器
列表包装器很好(也就是说,一个类具有单个List<E>
类型的字段),但是从上面的响应中,您还必须更改JSON({"list": []}
与{{ 1}}),这不是很好...
解决方案:
[]
批注@JsonValue
@JsonCreator
这有效,优雅,并且不需要任何其他功能。而且,它将在无效的bean上抛出通常的@Valid @RequestBody ListWrapper<Pojo> tokenBodies
。
包装器示例(java):
(有关科特琳的完整示例,请参见https://stackoverflow.com/a/64060909)
org.springframework.web.bind.MethodArgumentNotValidException
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": ""}]'
答案 7 :(得分:0)
我认为你最好的选择是包装清单 - How to validate request parameter if it is not a bean in spring MVC?
没有办法可以说 @Valid 适用于集合的元素。
答案 8 :(得分:0)
使用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
[]
答案 9 :(得分:0)
@Valid @RequestBody List<MyBean> request
只要您提交有效的json,就可以为我工作:-
[
{
"property1": "value1",
"property2": "value2"
},
{
"property1": "value3",
"property2": "value4"
}
]