简述:
在日常的开发工作中,基本上每个接口都要对参数进行校验,这样代码里就会有很多冗余繁琐的if-else。从网上无意看到了@Validated注解,发现@Validated注解的使用不但减轻代码量、减少了各种if-else。使代码更加的易读规整,更提高了校验的复用性。
1. 熟悉对应注解
@Valid与@Validated的区别
@Valid与@Validated都是做数据校验的,只不过注解位置与用法有点不同。
区别一: @Valid 可以注解到成员属性(字段)上,而@Validation不可以。 区别二: 由于第一点的不同,@Valid 可以嵌套验证,而@Validation不能进行嵌套验证。 区别三: @Valid:没有分组功能。@Validated:提供分组功能,可以在参数验证时,根据不同的分组采用不同的验证机制。
因@Validated可以分组,一般用于方法上面。而@Valid可以嵌套验证,一般用于嵌套的字段上面。
⚠️注:什么是嵌套验证 一个待验证的pojo类,其中还包含了待验证的对象,需要在待验证对象上注解@Valid,才能验证待验证对象中的成员属性。因为@Validated不能注解到成员属性上面,所有这里不能使用@Validated。
例如:
@Data
public class TeacherReq {
/**
* 讲师id.
*/
@NotBlank(message = "讲师id不能为空!")
private String id;
/**
* 讲师名称
*/
@NotBlank(message = "讲师名称不能为空!")
private String teName;
}
@Data
public class StudentReq {
/**
* 用户姓名.
*/
@NotBlank(message = "用户姓名不能为空!")
private String userName;
/**
* 用户年龄.
*/
@NotNull(message = "用户年龄不能为空!")
private Integer age;
/**
* 讲师请求类.
*/
@NotEmpty(message = "讲师集合不能为空!")
List
}
这里对TeacherReq只校验了NotEmpty,并没有对teacher信息里面的字段进行校验, 所以,这里需要把 @Valid 加上,如下:
/**
* 讲师请求类.
*/
@Valid
@NotEmpty(message = "讲师不能为空!")
List
@Validated 中常用的注解
空与非空校验
注解描述@Null校验对象必须为null@NotNull校验对象是否不为null, 无法查检长度为0的字符串(用于数值类型)@NotBlank校验字符串是不是Null,长度是否大于0,只对字符串,且会去掉前后空格(用于字符串)@NotEmpty校验元素是否为Null或者是Empty(用于集合)
Booelan校验
注解描述@AssertTrue校验 Boolean 对象是否为 true@AssertFalse校验 Boolean 对象是否为 false
长度校验
注解描述@Size(min=, max=)校验字符串长度必须在min与max之间,也可用于验证(Array,Collection,Map)
日期校验
注解描述@Past校验对象为当前日期之前@PastOrPresent校验对象为当前日期或之前日期@Future校验对象为当前日期之后@FutureOrPresent校验对象为当前日期或之后日期
数值校验
注解描述@Min校验对象不能大于等于指定的值@Max校验对象不能小于等于指定的值@DecimalMax校验对象不能大于等于指定的值,这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度@DecimalMin校验对象不能小于等于指定的值 ,这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度@Digits校验 Number 和 String 的构成是否合法@Digits(integer=,fraction=)校验字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。@Range(min=, max=)校验对象是否位于(包括)指定的最小值和最大值之间。
其他校验
注解描述@Email校验对象必须是Email格式@Pattern(regexp=)校验对象必须满足正则表达式
自定义校验注解
假设有性别枚举,需要校验用户的性别是否属于此范围内,可以按照如下方式操作:
@Getter
@RequiredArgsConstructor
public enum GenderEnum {
MALE(0, "男"),
FEMALE(1, "女");
private final Integer code;
private final String desc;
}
1. 自定义约束注解 InEnum
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = InEnumValidator.class)
public @interface InEnum {
Class extends GenderEnum> enumType();
String message() default "枚举类型不匹配";
Class>[] groups() default { };
Class extends Payload>[] payload() default { };
}
2. 自定义约束校验器 InEnumValidator。
如果校验通过,返回 true;反之返回 false
public class InEnumValidator implements ConstraintValidator
private Class extends GenderEnum> enumType;
@Override
public void initialize(InEnum inEnum) {
enumType = inEnum.enumType();
}
@Override
public boolean isValid(Object object, ConstraintValidatorContext context) {
if (object == null) {
return true;
}
if (enumType == null || !enumType.isEnum()) {
return false;
}
for (GenderEnum basicEnum : enumType.getEnumConstants()) {
if (basicEnum.getCode().equals(object)) {
return true;
}
}
return false;
}
}
3. 参数上增加 @InEnum 注解校验
/**
* 学员性别.
*/
@NotNull(message = "学员性别不能为空!")
@InEnum(enumType = GenderEnum.class, message = "学员性别类型错误!")
private Integer gender;
加上@InEnum注解后,如果数据校验不通过,返回的结果为:
{
"timestamp": "2024-03-22T06:28:23.137+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"InEnum.studentReq.gender",
"InEnum.gender",
"InEnum.java.lang.Integer",
"InEnum"
],
"arguments": [
{
"codes": [
"studentReq.gender",
"gender"
],
"arguments": null,
"defaultMessage": "gender",
"code": "gender"
},
"com.wb.workbook.enums.GenderEnum"
],
"defaultMessage": "学员性别类型错误!",
"objectName": "studentReq",
"field": "gender",
"rejectedValue": 123,
"bindingFailure": false,
"code": "InEnum"
}
],
"message": "Validation failed for object='studentReq'. Error count: 1",
"path": "/user/add"
}
2. 添加异常处理类
validate参数校验失败后,返回的json数据可能并不是咱们最终想要的,如下图所示: 所以需要添加一个异常处理类即可。
@Data
public class ResponseResult {
/**
* 返回码
*/
private Integer code;
/**
* 返回信息
*/
private String message;
/**
* 返回数据
*/
private Object data;
/**
* 时间戳
*/
private long timestamp;
}
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity
StringBuffer sb = new StringBuffer();
ex.getBindingResult().getAllErrors().forEach(x -> sb.append(x.getDefaultMessage()).append(";"));
// 去除最后一个符号
if (sb.length() > 0) {
sb.deleteCharAt(sb.lastIndexOf(";"));
}
ResponseResult res = new ResponseResult();
res.setTimestamp(System.currentTimeMillis());
res.setCode(HttpStatus.BAD_REQUEST.value());
res.setMessage(sb.toString());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(res);
}
}
添加异常处理类之后的返回结果:
3. 实战演练
新增两个接口(新增学员,编辑学员),因为新增编辑接口的请求参数都有相同的判断条件 (姓名、年纪、性别。。。。),但编辑接口比新增接口多传递学员id字段。所以可以使用@Validation进行分组验证
Controller层
@RestController
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String addUser(@Validated(value = {StudentReq.Add.class}) @RequestBody StudentReq studentReq) {
return "添加用户成功";
}
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String updateUser(@Validated(value = {StudentReq.Update.class}) @RequestBody StudentReq studentReq) {
return "修改用户成功";
}
}
Pojo层
因为Controller层使用@Validated的分层功能,在StudentReq的id验证中增加不同的分组验证,这样添加的方法就不会受到该验证的限制。
@Data
public class StudentReq {
/**
* 学员id.
*/
@NotBlank(message = "学员id不能为空!", groups = {StudentReq.Update.class})
private String id;
/**
* 学员姓名.
*/
@NotBlank(message = "学员姓名不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
private String userName;
/**
* 学员年龄.
*/
@NotNull(message = "学员年龄不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
@Min(value = 10, message = "学员年龄不得小于{value}", groups = {StudentReq.Add.class, StudentReq.Update.class})
@Max(value = 50, message = "学员年龄不得大于{value}", groups = {StudentReq.Add.class, StudentReq.Update.class})
private Integer age;
/**
* 学员性别.
*/
@NotNull(message = "学员性别不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
@Range(min = 0, max = 1, message = "学员性别类型错误", groups = {StudentReq.Add.class, StudentReq.Update.class})
private Integer gender;
/**
* 身份证号码.
*/
@NotBlank(message = "身份证号不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$",
message = "身份证号格式错误!", groups = {StudentReq.Add.class, StudentReq.Update.class})
private String idCard;
/**
* 入职日期.
*/
@NotNull(message = "入职日期不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
@PastOrPresent(message = "入职日期不能大于当前时间!", groups = {StudentReq.Add.class, StudentReq.Update.class})
private Date hireDate;
/**
* 邮箱.
*/
@NotBlank(message = "邮箱不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
@Email(message = "邮箱格式错误!", groups = {StudentReq.Add.class, StudentReq.Update.class})
private String email;
/**
* 讲师请求类.
*/
@Valid
@NotEmpty(message = "讲师集合不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
List
public interface Add {
}
public interface Update {
}
}
@Data
public class TeacherReq {
/**
* 讲师id.
*/
@NotBlank(message = "讲师id不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
private String id;
/**
* 讲师名称
*/
@NotBlank(message = "讲师名称不能为空!", groups = {StudentReq.Add.class, StudentReq.Update.class})
private String teName;
}
推荐链接
发表评论