SpringBoot @Validated参数校验

简述:

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

}

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

    /**
     * 讲师请求类.
     */
    @Valid
    @NotEmpty(message = "讲师不能为空!")
    List<TeacherReq> 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<? extends GenderEnum> enumType();

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

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

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

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

public class InEnumValidator implements ConstraintValidator<InEnum, Object> {

    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<ResponseResult> 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<TeacherReq> 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;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值