SpringBoot - Bean validation 参数校验

前言

后台开发中对参数的校验是不可缺少的一个环节,为了解决如何优雅的对参数进行校验?

  • JSR303(Java Specification Requests)应运而生,JSR303 是JavaBean参数校验的标准。
  • Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。
  • Hibernate validator 5 是 Bean Validation 1.1的实现。

常见注解

  • Bean Validation中定义的注解:
注解 详细信息
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式
  • Hibernate validator 在JSR303的基础上对校验注解进行了扩展,扩展注解如下:
注解 详细信息
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内

参数校验的应用

依赖

  • 注:SpringBoot 2.3.0之后放弃了默认对javax.validation 的支持,需引入一下依赖,2.3.0之前spring-boot-starter-web中内置。
<!-- validation -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

简单的参数校验示例

  • 要想开启参数校验,需要在类上标注@Validated注解
  • 控制器
 @PostMapping(value = "/test")
 public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
     return evt;
 }
  • 实体验证
@Getter
@Builder
public class ValidOneEvt {

    @NotEmpty(message = "名称不能为空")
    private String name;

    private String sex;
    
}
  • 测试结果

在这里插入图片描述


级联校验

  • 级联校验需要在校验的实体上添加@Valid。
  • 控制器
@PostMapping(value = "/test")
public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
    return evt;
}
  • 实体验证
@Getter
@Builder
public class ValidOneEvt {

    @NotEmpty(message = "名称不能为空")
    private String name;

    private String sex;

    @Valid
    private ValidTwoEvt validTwoEvt;

}
  • 级联实体
@Getter
@Setter
public class ValidTwoEvt {

    @Length(min = 2)
    private String name;

}
  • 请求示例

在这里插入图片描述

  • 校验结果

在这里插入图片描述


@Validated 与 @Valid

两者具有相似性
注解地方

  • @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上。
  • @Validated:可以用在类型、方法和方法参数上,不能用在成员属性(字段)上。

@Validated和@Valid在级联验证功能上的区别

  • @Valid:用在方法入参上无法单独提供级联验证功能。能够用在成员属性(字段)上,提示验证框架进行级联验证。

  • @Validated:用在方法入参上无法单独提供级联验证功能。不能用在成员属性(字段)上,也无法提示框架进行级联验证。能配合级联验证注解@Valid进行级联验证。

  • 总结: 通常使用@Validated, 级联验证使用@Valid。


自定义校验注解

自定义校验注解用于基础校验注解不能满足业务需求。

  • 自定义效验注解验证密码是否相等
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @Description 自定义参数校验注解
 *      @Documented 注解中的注释加入文档
 *      @Retention 注解保留阶段 RetentionPolicy.RUNTIME 运行阶段
 *      @Target 作用目标,注解的使用范围 TYPE:用于描述类、接口(包括注解类型) 或enum声明
 *      @Constraint 将注解和注解关联类关联到一起
 * @author coisini
 * @date Aug 10, 2021
 * @Version 1.0
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordEquals {

    int min() default 4;

    int max() default 6;

    String message() default "passwords are not equal";

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

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

}
  • 自定义校验注解关联类
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @Description 自定义校验注解关联类
 *      ConstraintValidator的第一个参数:注解的类型
 *      ConstraintValidator的第二个参数:自定义注解修饰的目标的类型
 * @author coisini
 * @date Aug 10, 2021
 * @Version 1.0
 */
public class PasswordValidator implements ConstraintValidator<PasswordEquals, ValidEvt> {

    private int min;
    private int max;

    /**
     * 初始化获取注解参数
     * @param constraintAnnotation
     */
    @Override
    public void initialize(PasswordEquals constraintAnnotation) {
        this.min = constraintAnnotation.min();
        this.max = constraintAnnotation.max();
    }

    /**
     * 校验参数
     * @param value
     * @param context
     * @return
     */
    @Override
    public boolean isValid(ValidEvt value, ConstraintValidatorContext context) {
        String password1 = value.getPassword1();
        String password2 = value.getPassword2();

        return password1.equals(password2) && this.validLength(password1, password2);
    }

    /**
     * 校验密码长度
     * @param password1
     * @param password2
     * @return
     */
    private boolean validLength(String password1, String password2) {
        return password1.length() > min && password1.length() < max
                && password2.length() > min && password2.length() < max;
    }

}
  • 自定义注解校验类
/**
 * @Description 自定义注解校验类
 * @author coisini
 * @date Aug 10, 2021
 * @Version 1.0
 */
@Getter
@Builder
@PasswordEquals(min = 1, message = "Incorrect password length or passwords are not equal")
public class ValidEvt {

    private String password1;
    private String password2;

}
  • 参数验证异常统一处理
@ControllerAdvice
public class GlobalExceptionAdvice {
	/**
     * 参数校验异常处理器
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public UnifyMessage handleBeanValidation(HttpServletRequest request, MethodArgumentNotValidException e) {
        String method = request.getMethod();
        String requestUrl = request.getRequestURI();
        System.out.println(e);

        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        String message = formatAllErrorMessages(errors);

        return new UnifyMessage(10001, message,method + " " + requestUrl);
    }

    /**
     * 自定义注解校验异常处理器
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public UnifyMessage handleConstrainException(HttpServletRequest req, ConstraintViolationException e){
        String method = req.getMethod();
        String requestUrl = req.getRequestURI();
        String message = e.getMessage();
        return new UnifyMessage(10001, message,method + " " + requestUrl);
    }

    /**
     * 异常消息拼接
     * @param errors
     * @return
     */
    private String formatAllErrorMessages(List<ObjectError> errors){
        StringBuffer errorMsg = new StringBuffer();
        errors.forEach(error ->
                errorMsg.append(error.getDefaultMessage()).append(";")
        );
        return errorMsg.toString();
    }
}
  • 统一消息返回
/**
 * @Description 统一消息返回
 * @author coisini
 * @date Aug 9, 2021
 * @Version 1.0
 */
public class UnifyMessage {

    private int code;
    private String message;
    private String requestUrl;

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public String getRequestUrl() {
        return requestUrl;
    }

    public UnifyMessage(int code, String message, String requestUrl) {
        this.code = code;
        this.message = message;
        this.requestUrl = requestUrl;
    }

}
  • 测试类
@PostMapping(value = "/test1")
public ValidEvt test1(@RequestBody @Validated ValidEvt evt){
    return evt;
}
  • 测试结果

在这里插入图片描述

在这里插入图片描述

- End -
梦想是咸鱼
关注一下吧
posted @ 2021-08-11 17:54  Maggieq8324  阅读(257)  评论(0编辑  收藏  举报