简介
本文介绍如何解决@Valid放在Controller的List类型的参数上时校验无效的问题。
问题复现
代码
Controller
package com.knife.example.business.user.controller;
import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {
/**
* 如果入参是List类型,则必须是@RequestBody,否则会报错
*/
@ApiOperation("valid测试基础列表")
@PostMapping("validBaseList")
public List<String> validBaseList(@Valid @RequestBody @NotEmpty List<String> nameList) {
return nameList;
}
@ApiOperation("valid测试对象列表")
@PostMapping("validObjectList")
public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty List<UserBO> userBOList) {
return userBOList;
}
@ApiOperation("valid测试手动校验工具")
@PostMapping("validManualObjectList")
public List<UserBO> validManualObjectList(@RequestBody List<UserBO> userBOList) {
ValidateUtil.validate(userBOList);
return userBOList;
}
}
BO
package com.knife.example.business.user.bo;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
public class UserBO {
@NotBlank(message = "名字不能为空")
private String name;
private Integer age;
@NotBlank(message = "密码不能为空")
private String password;
@NotEmpty(message = "分数不能为空")
private List<Integer> scoreArray;
@Valid
@NotNull(message = "账户不能为空")
private AccountBO accountBO;
@Valid
@NotEmpty(message = "账户列表不能为空")
private List<AccountBO> accountBOList;
}
package com.knife.example.business.user.bo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class AccountBO {
@NotNull(message = "账户ID不能为空")
private Long id;
@NotBlank(message = "电话号码不能为空")
private String phoneNumber;
private String[] emails;
}
测试
从下图可以看到,所有的校验都没有生效。
测试1:空列表

测试2:valid校验对象列表

入参:
[
{
"accountBO": {
"emails": [],
"id": 2,
"phoneNumber": ""
},
"accountBOList": [
{
"emails": [],
"id": null,
"phoneNumber": "12345"
}
],
"age": 0,
"name": "",
"password": "",
"scoreArray": []
}
]
测试3:手动校验对象列表

入参:
[
{
"accountBO": {
"emails": [],
"id": 2,
"phoneNumber": ""
},
"accountBOList": [
{
"emails": [],
"id": null,
"phoneNumber": "12345"
}
],
"age": 0,
"name": "",
"password": "",
"scoreArray": []
}
]
原因分析
@Valid只能校验Java Bean(单个对象),而List<E>不是JavaBean所以校验会失败。
解决方案
方案1:@Validated + @Valid(推荐)
很简单,在接口类上加@Validated即可。
代码
package com.knife.example.business.user.controller;
import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Validated
@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {
/**
* 如果入参是List类型,则必须是@RequestBody,否则会报错
*/
@ApiOperation("valid测试基础列表")
@PostMapping("validBaseList")
public List<String> validBaseList(@Valid @RequestBody @NotEmpty List<String> nameList) {
return nameList;
}
@ApiOperation("valid测试对象列表")
@PostMapping("validObjectList")
public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty List<UserBO> userBOList) {
return userBOList;
}
@ApiOperation("valid测试手动校验工具")
@PostMapping("validManualObjectList")
public List<UserBO> validManualObjectList(@RequestBody List<UserBO> userBOList) {
ValidateUtil.validate(userBOList);
return userBOList;
}
}
测试
可以看到:自动校验都成功了。(手动校验仍然失效)。
测试1:空列表(有效)

测试2:valid校验对象列表(有效)

入参:
[
{
"accountBO": {
"emails": [],
"id": 2,
"phoneNumber": ""
},
"accountBOList": [
{
"emails": [],
"id": null,
"phoneNumber": "12345"
}
],
"age": 0,
"name": "",
"password": "",
"scoreArray": []
}
]
测试3:手动校验对象列表(无效)

入参:
[
{
"accountBO": {
"emails": [],
"id": 2,
"phoneNumber": ""
},
"accountBOList": [
{
"emails": [],
"id": null,
"phoneNumber": "12345"
}
],
"age": 0,
"name": "",
"password": "",
"scoreArray": []
}
]
如上所示,校验无效,解决方法见:本文最后。
方案2:自定义List
新建一个类,实现List接口,使这个类即具有了JavaBean的特性,又具有了List的特性。
这样写有些麻烦,要实现几十个方法。
package com.example.demo.common;
import javax.validation.Valid;
import java.util.*;
public class ValidList<E> implements List<E> {
@Valid
private List<E> list;
public ValidList() {
this.list = new ArrayList<>();
}
public ValidList(List<E> list) {
this.list = list;
}
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
// 实现其他方法
}
用法
Controller将原来的List改为ValidList即可:
package com.knife.example.business.user.controller;
import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {
/**
* 如果入参是List类型,则必须是@RequestBody,否则会报错
*/
@ApiOperation("valid测试基础列表")
@PostMapping("validBaseList")
public List<String> validBaseList(@Valid @RequestBody @NotEmpty ValidList<String> nameList) {
return nameList;
}
@ApiOperation("valid测试对象列表")
@PostMapping("validObjectList")
public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty ValidList<UserBO> userBOList) {
return userBOList;
}
@ApiOperation("valid测试手动校验工具")
@PostMapping("validManualObjectList")
public List<UserBO> validManualObjectList(@RequestBody ValidList<UserBO> userBOList) {
ValidateUtil.validate(userBOList);
return userBOList;
}
}
方案3:包装List
把List封装成Java Bean,定义一个ListWrapper类。
包装List
package com.example.demo.business.validAndValidated.common;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
@Data
public class ListWrapper<E> {
@Valid
private List<E> list;
public ListWrapper() {
list = new ArrayList<>();
}
public ListWrapper(List<E> list) {
this.list = list;
}
}
用法
- 将Controller的List换成ListWrapper;
- 传参的时候[{},{}..]要改为{“list”: [{},{}..]}
package com.knife.example.business.user.controller;
import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {
/**
* 如果入参是List类型,则必须是@RequestBody,否则会报错
*/
@ApiOperation("valid测试基础列表")
@PostMapping("validBaseList")
public List<String> validBaseList(@Valid @RequestBody @NotEmpty ListWrapper<String> nameList) {
return nameList;
}
@ApiOperation("valid测试对象列表")
@PostMapping("validObjectList")
public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty ListWrapper<UserBO> userBOList) {
return userBOList;
}
@ApiOperation("valid测试手动校验工具")
@PostMapping("validManualObjectList")
public List<UserBO> validManualObjectList(@RequestBody ListWrapper<UserBO> userBOList) {
ValidateUtil.validate(userBOList);
return userBOList;
}
}
解决手动校验List无效的问题
只能手动遍历列表,挨个去校验单个对象。
代码
原来的ValidateUtil
package com.knife.example.common.util;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.util.Set;
/**
* hibernate validator的校验工具
*/
public class ValidateUtil {
private static final Validator validator =
Validation.buildDefaultValidatorFactory().getValidator();
/**
* 校验实体类
*/
public static <T> void validate(T t) {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
if (constraintViolations.size() > 0) {
StringBuilder validateError = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
validateError.append(constraintViolation.getMessage()).append(";");
}
throw new ValidationException(validateError.toString());
}
}
/**
* 通过组来校验实体类
*/
public static <T> void validate(T t, Class<?>... groups) {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t, groups);
if (constraintViolations.size() > 0) {
StringBuilder validateError = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
validateError.append(constraintViolation.getMessage()).append(";");
}
throw new ValidationException(validateError.toString());
}
}
}
修改后的ValidateUtil(加了个重载)
package com.knife.example.common.util;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.util.Collection;
import java.util.Set;
/**
* hibernate validator的校验工具
*/
public class ValidateUtil {
private static final Validator validator =
Validation.buildDefaultValidatorFactory().getValidator();
/**
* 校验实体类
*/
public static <T> void validate(T t) {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
if (constraintViolations.size() > 0) {
StringBuilder validateError = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
validateError.append(constraintViolation.getMessage()).append(";");
}
throw new ValidationException(validateError.toString());
}
}
/**
* 校验实体类集合
*/
public static <T> void validate(Collection<T> tCollection) {
for (T t : tCollection) {
validate(t);
}
}
/**
* 通过组来校验实体类
*/
public static <T> void validate(T t, Class<?>... groups) {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t, groups);
if (constraintViolations.size() > 0) {
StringBuilder validateError = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
validateError.append(constraintViolation.getMessage()).append(";");
}
throw new ValidationException(validateError.toString());
}
}
/**
* 通过组来校验实体类列表
*/
public static <T> void validate(Collection<T> tCollection, Class<?>... groups) {
for (T t : tCollection) {
validate(t, groups);
}
}
}
测试
如下图所示:校验生效了!

入参:
[
{
"accountBO": {
"emails": [],
"id": 2,
"phoneNumber": ""
},
"accountBOList": [
{
"emails": [],
"id": null,
"phoneNumber": "12345"
}
],
"age": 0,
"name": "",
"password": "",
"scoreArray": []
}
]
下载源码
此隐藏内容仅限VIP查看升级VIP

请先 !