我有以下控制器方法:
@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid List<CompanyTag> categories,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}
CompanyTag以这种方式定义:
public class CompanyTag {
@StringUUIDValidation String key;
String value;
String color;
String icon;
Icon iconObj;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
...
}
问题是没有触发验证,没有验证CompanyTag列表,&#34; StringUUIDValidation&#34;永远不会调用验证器。
如果我删除了List并且只尝试发送一个CompanyTag,而不是:
@RequestBody @Valid List<CompanyTag> categories,
使用:
@RequestBody @Valid CompanyTag category,
它按预期工作,所以显然Spring不喜欢验证事物列表(尝试使用数组,但也没有用)。
任何人都知道缺少什么?
答案 0 :(得分:38)
我找到了另一种方法。基本问题是您希望将列表作为服务的输入有效负载,但javax.validation不会验证列表,只能验证JavaBean。诀窍是使用一个自定义列表类,它既作为List 又作为JavaBean:
@RequestBody @Valid List<CompanyTag> categories
更改为:
@RequestBody @Valid ValidList<CompanyTag> categories
您的列表子类看起来像这样:
public class ValidList<E> implements List<E> {
@Valid
private List<E> list;
public ValidList() {
this.list = new ArrayList<E>();
}
public ValidList(List<E> list) {
this.list = list;
}
// Bean-like methods, used by javax.validation but ignored by JSON parsing
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
// List-like methods, used by JSON parsing but ignored by javax.validation
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
// Other list methods ...
}
答案 1 :(得分:8)
我建议将List类别包装到一些DTO bean中并验证它。除了工作验证之外,您还将受益于更灵活的API。
@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid TagRequest tagRequest,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}
public static class TagRequest {
@Valid
List<CompanyTag> categories;
// Gettes setters
}
答案 2 :(得分:5)
我认为最优雅的解决方案是创建一个自定义Validator for Collection和一个@ControllerAdvice,在WebDataBinders中注册Validator,看一看Spring validation for RequestBody parameters bound to collections in Controller methods
答案 3 :(得分:5)
我试图在项目中使用Paul的方法,但是有人说这太复杂了。不久之后,我发现了另一种简单的方法,其工作方式类似于以下代码:
@Validated
@RestController
@RequestMapping("/parent")
public class ParentController {
private FatherRepository fatherRepository;
/**
* DI
*/
public ParentController(FatherRepository fatherRepository) {
this.fatherRepository = fatherRepository;
}
@PostMapping("/test")
public void test(@RequestBody @Valid List<Father> fathers) {
}
}
它有效且易于使用。关键点是类上的@Valiated批注。 顺便说一句,我使用的是springBootVersion ='2.0.4.RELEASE'。
答案 4 :(得分:3)
验证集合不会直接起作用。
例如:如果多个元素未通过验证,该怎么办?首次验证后停止?验证所有(如果是这样,对消息集合要做什么)?
如果在你的配置中Spring委托给像Hibernate Validator这样的Bean Validator提供者,你应该在那里寻找实现集合验证器的方法。
对于Hibernate,讨论了类似的问题here
答案 5 :(得分:2)
使用 Spring Boot 2.4.1
:
在类中添加 @Validated
注释
在菱形运算符内移动 @Valid
注释:
@RestController
@Validated // <-- This activates the Spring Validation AOP interceptor
public class MyController {
...
@RequestBody List<@Valid CompanyTag> categories
// ^^^ - NOTE: the @Valid annotation is inside <> brackets
答案 6 :(得分:1)
@Paul Strack的great solution与龙目岛魔法混在一起:
@Data
public class ValidList<E> implements List<E> {
@Valid
@Delegate
private List<E> list = new ArrayList<>();
}
用法(有效列表的交换列表):
public ResponseEntityWrapper updateMapTheme(
@RequestBody @Valid ValidList<CompanyTag> categories, ...)
(需要Lombok,但是如果您还没有使用它,那么您真的想尝试一下)
答案 7 :(得分:1)
这是我调和许多不同答案的尝试。
Lebecca's answer不需要Paul's answer便需要包装器,因为放置在类上的@Validated
启用了Bean Validation API的method validation feature。
Hibernate Validator documentation专门说明:
[...] @Valid批注可用于标记可执行参数并返回值以进行级联验证。
[...]
级联验证不仅可以应用于简单对象 引用,但也引用集合类型的参数和返回值。 这意味着将@Valid批注放入参数或返回时 值
是一个数组
实现java.lang.Iterable
或实现java.util.Map
每个包含的元素都经过验证。
如果您需要验证 Beans 的集合,这是最方便的方法(请确保还根据需要实现@ExceptionHandler
)。
如果您需要验证 Non-Beans 的集合,例如List<String>
,其中每个元素必须匹配一个模式,您可以像这样使用container element constraints:
controllerMethod(List<@Pattern(regexp="pattern") String> strings)
也有可能仅在控制器方法参数上使用@Valid
(必须为Bean类型)不还要放置{{1} }。在这种情况下,您可以“免费”获得适当,详细的HTTP 400响应,即无需自定义@Validated
。但这不应用级联验证,因此您无法验证类似@ExceptionHandler
之类的东西,也不支持容器元素约束。
最后,您可以将后一种方法与类型为@Valid List<SomeBean> beans
的方法中添加的额外参数结合使用。在验证错误的情况下,这不会触发自动错误响应,但是您必须自己在方法主体中检查注入的BindingResult
并采取相应的措施(这样可以提供更大的灵活性)。 this综合答案中对此进行了描述。
答案 8 :(得分:0)
我正在使用spring-boot 1.5.19.RELEASE
我用@validated
注释我的服务,然后将@Valid
应用于方法中的List
参数,并且列表中的项目得到验证。
型号
@Data
@ApiModel
@Validated
public class SubscriptionRequest {
@NotBlank()
private String soldToBpn;
@NotNull
@Size(min = 1)
@Valid
private ArrayList<DataProducts> dataProducts;
private String country;
@NotNull
@Size(min = 1)
@Valid
private ArrayList<Contact> contacts;
}
服务接口(如果没有接口,请使用具体类型)
@Validated
public interface SubscriptionService {
List<SubscriptionCreateResult> addSubscriptions(@NonNull @Size(min = 1) @Valid List<SubscriptionRequest> subscriptionRequestList)
throws IOException;
}
全局异常处理程序方法(ApiError类型不是我的设计)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
public ApiError[] handleConstraintViolationException(ConstraintViolationException exception) {
List<InvalidField> invalidFields = exception.getConstraintViolations().stream()
.map(constraintViolation -> new InvalidField(constraintViolation.getPropertyPath().toString(),
constraintViolation.getMessage(),
constraintViolation.getInvalidValue()))
.collect(Collectors.toList());
return new ApiError[] {new ApiError(ErrorCodes.INVALID_PARAMETER, "Validation Error", invalidFields)};
}
从控制器调用错误方法的示例
LinkedList<SubscriptionRequest> list = new LinkedList<>();
list.add(new SubscriptionRequest());
return subscriptionService.addSubscriptions(list);
响应正文(请注意索引[0])
[
{
"errorCode": "invalid.parameter",
"errorMessage": "Validation Error",
"invalidFields": [
{
"name": "addSubscriptions.arg0[0].soldToBpn",
"message": "may not be empty",
"value": null
},
{
"name": "addSubscriptions.arg0[0].dataProducts",
"message": "may not be null",
"value": null
},
{
"name": "addSubscriptions.arg0[0].contacts",
"message": "may not be null",
"value": null
}
]
}
]
答案 9 :(得分:0)
使用 @Validated 注释控制器
使用 @有效注释@RequestBody
答案 10 :(得分:0)
创建实体类:
import javax.validation.Valid;
import java.util.List;
public class ValidList<E> {
@Valid
private List<E> list;
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
}
使用控制器
@RequestMapping(value = "/sku", method = RequestMethod.POST)
public JsonResult createSKU(@Valid @RequestBody ValidList<Entity> entityList, BindingResult bindingResult) {
if (bindingResult.hasErrors())
return ErrorTools.build().handlerError(bindingResult);
return new JsonResult(200, "result");
}
答案 11 :(得分:0)
@Valid
批注可在菱形运算符内部使用:
private List<@Valid MyType> types;
或
@Valid
private List<MyType> types;
现在,每个列表项都将得到验证。
答案 12 :(得分:0)
(此答案在科特林中,对于 Java 请参见https://stackoverflow.com/a/64061936)
对于使用 kotlin 和 jackson 的用户,这是ValidatedList
类,不需要包装,即仍会像通常的列表一样进行序列化/反序列化:
class ValidatedList<E> {
/**
* By default, spring-boot cannot validate lists, as they are generic AND do not conform to the Java Bean definition.
* This is one work-around: create a wrapper that fits the Java Bean definition, and use Jackson annotations to
* make the wrapper disappear upon (de)serialization.
* Do not change anything (such as making the _value field private) or it won't work anymore !
*
* Usage:
* ```
* @PostMapping("/something")
* fun someRestControllerMethod(@Valid @RequestBody pojoList: ValidatedList<SomePOJOClass>){
* // access list with:
* pojoList.values
*}
* ```
*/
@JsonValue
@Valid
@NotNull
@Size(min = 1, message = "array body must contain at least one item.")
var _values: List<E>? = null
val values: List<E>
get() = _values!!
@JsonCreator
constructor(vararg list: E) {
this._values = list.asList()
}
}
优势:
@Validated
批注@Size
)400 Bad Request
(使用javax
和@Validated
注释时则不会这样)示例:
data class N(
@field:Min(value = 0, message = "id must be positive.")
val id: Long? = null,
@field:NotNull
@field:Size(min = 2, max = 32, message = "wrong size: should be 32 chars long.")
val token: String? = null
)
@RestController
class XController {
@PostMapping("/ns")
fun getNs(@Valid @NotNull @RequestBody wrap: ListWrapper<N>) = wrap
}
提交好:
curl -H "Content-Type: application/json" -X POST http://localhost:8080/ns -d '[{"id": 11, "token": "something"}]'
[{"id" : 11, "token" : "something"}]
提交空的正文:
curl -H "Content-Type: application/json" -X POST http://localhost:8080/ns -d '[]'
{
"timestamp" : "2020-09-25T08:49:30.324+00:00",
"message" : "Validation failed for object='listWrapper'. Error count: 1",
"error" : "Bad Request",
"path" : "/ns",
"status" : 400,
"exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
"trace":"org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N> com.example.demo.test.XController.getNs(com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N>): [Field error in object 'listWrapper' on field '_values': rejected value [[]]; codes [Size.listWrapper._values,Size._values,Size.java.util.List,Size]; [...]"
}
提交无效的项目:
curl -H "Content-Type: application/json" -X POST http://localhost:8080/ns -d '[{"id": -11, "token": ""}]'
{
"message" : "Validation failed for object='listWrapper'. Error count: 2",
"path" : "/ns",
"exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
"timestamp" : "2020-09-25T08:49:54.505+00:00",
"error" : "Bad Request",
"status" : 400,
"trace":"org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N> com.example.demo.test.XController.getNs(com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N>) with 2 errors: [...]"
}
答案 13 :(得分:0)
使用 Spring Boot 2.2.2 版本...
这是一段代码:-
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Validated
public class MyController {
@PostMapping(value = "/test", consumes = "application/json", produces = "application/json")
public String test(@Valid @RequestBody List<Student> st) {
System.out.println("-------------test Method-------");
return "Its' Success";
}
}
class Student{
@NotBlank
String name;
@NotBlank
String password;
@NotBlank
String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
JSON 数据列表:-
注意 name 在第二个 Student 对象中为空。
[
{
"name": "Sreepad",
"password": "sddwh",
"email": "sample@gmail.oom"
},
{
"name": "",
"password": "sddwh",
"email": "sample@gmail.oom"
}
]
错误描述:-
javax.validation.ConstraintViolationException: test.st[1].name: must not be blank.
注意:如果在类级别删除@Validated,则不会在方法参数级别验证列表和字符串。
17.验证
只要 JSR-303 实现(例如 Hibernate 验证器)在类路径上,Bean Validation 1.1 支持的方法验证功能就会自动启用。这让 bean 方法可以使用 javax.validation 对其参数和/或返回值的约束进行注释。 具有此类注释方法的目标类需要在类型级别使用@Validated 注释进行注释,以便在其方法中搜索内联约束注释。
答案 14 :(得分:0)
使用更高版本的 spring,您现在可以做到这一点。
@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody List<@Valid CompanyTag> categories,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}
@Valid 注释位于通用参数中。 但是,如果您想验证整个列表,您仍然需要将其包装在一个对象中..