首页 > 技术文章 > Spring boot JSR-303参数校验器

zhangkq 2021-05-21 12:03 原文

1. 运用场景

规定前端传入的内容,否者返回对应的题提示,进一步减少脏数据的出现。不用我们自己判断数据是否合法,拿到我们想要的数据。

2. 进入maven依赖

此实例为Spring boot 2.4.5

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

3. 工具类

返回值对象

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 统一返回值对象
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
@Data
public class R {

    private String msg;
    private Integer code;
    private Boolean success;
    private Map<String, Object> data;

    public static R ok(){
        R r = new R();
        r.setMsg("OK");
        r.setSuccess(true);
        r.setCode(0);
        r.setData(new HashMap<String, Object>());
        return r;
    }
    public static R error(){
        R r = new R();
        r.setMsg("error");
        r.setSuccess(false);
        r.setCode(-1);
        return r;
    }
    public R code(Integer code){
        this.code = code;
        return this;
    }
    public R msg(String msg){
        this.msg = msg;
        return this;
    }
    public R success(Boolean success){
        this.success = success;
        return this;
    }
    public R data(Map map){
        this.data = map;
        return this;
    }

    public R data(String key,Object val){
        if(this.data==null)
            this.data = new HashMap<String, Object>();
        this.data.put(key,val);
        return this;
    }
}

  

统一异常处理类

import com.zkq.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 异常信息处理器
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
@RestControllerAdvice
@Slf4j
public class ExceptionHandle {

    // 固定处理JSR-303 抛出的异常
    @ExceptionHandler(BindException.class)
    public R validException(BindException e){
        Map<String,String> map = new HashMap<String, String>();
        e.getBindingResult().getFieldErrors().forEach((fieldError) -> {
            map.put(fieldError.getField(),fieldError.getDefaultMessage());
        });
        return R.ok().msg("参数错误!").data(map);
    }

    // 统一处理不是上面JSR-303 的错误,当然也可以细分,我在此就部分了,统一处理。
    @ExceptionHandler
    public R error(Throwable t){
        // 记录响应的错误信息
//        log.error("",t.getMessage());
        return R.error().msg("系统错误!");
    }
}

  

4. 代码实例

4.1 普通校验 @Valid

  验证对象User

import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 * @Description 普通校验
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
@Data
public class User {
    @NotNull(message = "id不能为空")
    private Long id;
    @NotBlank(message = "姓名不能为空")
    private String name;
    @NotNull(message = "年龄不能为空")
    private Integer age;
}

  

测试接口

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
public class ZkqController{
    // 测试 @Valid
    @GetMapping("/zkq")
    public R zkq(@Valid User user){
        return R.ok().msg("标准校验").data("user",user);
    }
}

  

测试结果:

不符合规范

符合规范

4.2 分组校验 @Validated

这里我就分一组演示

创建校验器分组类

/**
 * @Description 校验器分组,GetGroup这个不固定可随意写 
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
public interface GetGroup {
}

  

创建校验对象

import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 * @Description 分组校验
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */

@Data
public class User2 {
    // groups = {GetGroup.class,User.class} 可分多组这里我就分了一组,根据需求自行分组
    @NotNull(message = "id不能为空",groups = GetGroup.class)
    private Long id;
    @NotBlank(message = "姓名不能为空",groups = GetGroup.class)
    private String name;
    @NotNull(message = "年龄不能为空",groups = GetGroup.class)
    private Integer age;
}

  

测试接口

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ZkqController{
    // 测试 @Validated
    @GetMapping("/zkq2")
    public R zkq2(@Validated(GetGroup.class) User2 user){
        return R.ok().msg("分组校验").data("user",user);
    }
}

  

校验结果

不符合规范

符合校验

4.3 自定义校验器校验 @Valid

创建检验器注解@ZkqNumRange

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @Description 自定义校验器注解 (message和groups,payload 是必须的)
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
@Documented
// 支持校验的类型 Long ,Float ,Integer 暂时这三种类型,有需要可以自己扩展
@Constraint(validatedBy = {ZkqNumRangeConstraintValidatorLong.class, ZkqNumRangeConstraintValidatorFloat.class, ZkqNumRangeConstraintValidatorInteger.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ZkqNumRange {
    // 必须的不符合的的提示语,也可以象注解一样写{javax.validation.constraints.ZkqNumRange .message}
    // 但是要在 resources 创建对应的文件 ValidationMessages.properties
    String message() default "参数不符!";
    // 必须的适用于分组校验
    Class<?>[] groups() default {};
    // 必须的
    Class<? extends Payload>[] payload() default {};

    // 这个是我们自己定义是否为必须的参数,默认为不必须
    boolean required() default false;
    // 这个是我们自己定义的最小值,默认为 0
    int min() default 0;
    // 这个是我们自己定义的最大值,默认为 2147483647  Integer.MAX_VALUE = 2147483647 Integer.MIN_VALUE = -2147483648
    int max() default Integer.MAX_VALUE;
}

  

定义校验器支持的类型 这里我们就写三种类型Long ,Float ,Integer 。需要其他的自己扩展

Float 校验器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @Description 参数校验器 Float 类型
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
public class ZkqNumRangeConstraintValidatorFloat implements ConstraintValidator<ZkqNumRange, Float> {

    private Integer max;
    private Integer min;
    private boolean required;

    @Override
    public void initialize(ZkqNumRange constraintAnnotation) {
        max = constraintAnnotation.max();
        min = constraintAnnotation.min();
        required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(Float value, ConstraintValidatorContext context) {
        if(required || value!=null) {
            if(value==null)
                return false;
            if ((min <= value) && (value<= max)) {
                return true;
            }
            return false;
        }
        return true;
    }
}

  

Integer 校验器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @Description  参数校验器 Integer 类型
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
public class ZkqNumRangeConstraintValidatorInteger implements ConstraintValidator<ZkqNumRange, Integer> {

    private Integer max;
    private Integer min;
    private boolean required;

    @Override
    public void initialize(ZkqNumRange constraintAnnotation) {
        max = constraintAnnotation.max();
        min = constraintAnnotation.min();
        required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        if(required || value!=null) {
            if(value==null)
                return false;
            if ((min <= value) && (value<= max)) {
                return true;
            }
            return false;
        }
        return true;
    }
}

  

Long 校验器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @Description 参数校验器 Long 类型
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
public class ZkqNumRangeConstraintValidatorLong  implements ConstraintValidator<ZkqNumRange, Long> {

    private Integer max;
    private Integer min;
    private boolean required;

    @Override
    public void initialize(ZkqNumRange constraintAnnotation) {
        max = constraintAnnotation.max();
        min = constraintAnnotation.min();
        required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(Long value, ConstraintValidatorContext context) {
        if(required || value!=null) {
            if(value==null)
                return false;
            if ((min <= value) && (value<= max)) {
                return true;
            }
            return false;
        }
        return true;
    }
}

  

测试对象User3

import lombok.Data;

/**
 * @Description 自定义校验器 校验
 * @Author 张凯强
 * @Date Created in 2021/5/20
 * @E-mail 862166318@qq.com
 */
@Data
public class User3 {
    // required 默认为不必须
    @ZkqNumRange(message = "id不正确!")
    private Long id;
    @ZkqNumRange(message = "钱数不正确!", required = true)
    private Float money;
    // 最大值为150
    @ZkqNumRange(max = 150, message = "年龄不正确!", required = true)
    private Integer age;
}

  

测试接口

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;

@RestController
public class ZkqController{
    // 测试 自定义校验器 @ZkqNumRange
    @GetMapping("/zkq3")
    public R zkq2(@Valid User3 user){
        return R.ok().msg("自定义校验器校验").data("user",user);
    }
}

  

不符合规范

符合校验

4. 自带校验注解

我把截图放这,想看的自行研究。

也可以自行阅读校验器源码,例如 @NotNull 和 @NotBlank 区别了

@NotBlank 做了toString(),然后去空格后的长度大于0。

@NotNull 就做了 object != null

有问题质询QQ:248048521,欢迎技术交流

 

推荐阅读