简述:

在日常的开发工作中,基本上每个接口都要对参数进行校验,这样代码里就会有很多冗余繁琐的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 teacherReqList;

}

这里对TeacherReq只校验了NotEmpty,并没有对teacher信息里面的字段进行校验, 所以,这里需要把 @Valid 加上,如下:

/**

* 讲师请求类.

*/

@Valid

@NotEmpty(message = "讲师不能为空!")

List teacherReqList;

@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 enumType();

String message() default "枚举类型不匹配";

Class[] groups() default { };

Class[] payload() default { };

}

2. 自定义约束校验器 InEnumValidator。

如果校验通过,返回 true;反之返回 false

public class InEnumValidator implements ConstraintValidator {

private Class 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 handleApiException(MethodArgumentNotValidException ex) {

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 teacherReqList;

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;

}

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: