SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截

SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截

         本文主要包括:基本注解使用及说明,全局异常捕捉,自定义注解的实现,@Validated与@Valid的简单对比及不同实现

 使用 Spring Boot 程序的话只需要spring-boot-starter-web 就够了,它的子依赖包含了我们所需要的东西。除了这个依赖,下面的演示还用到了 lombok ,所以不要忘记添加上相关依赖。


    
        org.springframework.boot
        spring-boot-starter-web
    

    
        org.projectlombok
        lombok
        true
    
    
    
        org.springframework.boot
        spring-boot-starter-test
        test
    

SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截_第1张图片

新建参数类

@Data
public class ValidationModel {

    /**
     * 主键
     */
    @NotNull(message = "主键不能为空")
    private Long id;

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

    /**
     * 邮箱
     */
    @NotBlank(message = "email 不能为空")
    @Email(message = "email 格式不正确")
    private String email;

    /**
     * 年龄
     */
    @Range(min = 18, max = 60, message = "年龄必须在 {min} 至 {max} 之间")
    private String age;

}
文档地址: https://docs.oracle.com/javaee/7/api/javax/validation/constraints/package-frame.html  
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation

    JSR提供的校验注解:
    @AssertFalse            被注释的元素只能为false
    @AssertTrue             被注释的元素只能为true
    @DecimalMax             被注释的元素必须小于或等于{value}
    @DecimalMin             被注释的元素必须大于或等于{value}
    @Digits                 被注释的元素数字的值超出了允许范围(只允许在{integer}位整数和{fraction}位小数范围内)
    @Email                  被注释的元素不是一个合法的电子邮件地址
    @Future                 被注释的元素需要是一个将来的时间
    @FutureOrPresent        被注释的元素需要是一个将来或现在的时间
    @Max                    被注释的元素最大不能超过{value}
    @Min                    被注释的元素最小不能小于{value}
    @Negative               被注释的元素必须是负数
    @NegativeOrZero         被注释的元素必须是负数或零
    @NotBlank               被注释的元素不能为空
    @NotEmpty               被注释的元素不能为空
    @NotNull                被注释的元素不能为null
    @Null                   被注释的元素必须为null
    @Past                   被注释的元素需要是一个过去的时间
    @PastOrPresent          被注释的元素需要是一个过去或现在的时间
    @Pattern                被注释的元素需要匹配正则表达式"{regexp}"
    @Positive               被注释的元素必须是正数
    @PositiveOrZero         被注释的元素必须是正数或零
    @Size                   被注释的元素个数必须在{min}和{max}之间

    Hibernate Validator提供的校验注解:

    @CreditCardNumber       被注释的元素不合法的信用卡号码
    @Currency               被注释的元素不合法的货币 (必须是{value}其中之一)
    @EAN                    被注释的元素不合法的{type}条形码
    @Email                  被注释的元素不是一个合法的电子邮件地址  (已过期)
    @Length                 被注释的元素长度需要在{min}和{max}之间
    @CodePointLength        被注释的元素长度需要在{min}和{max}之间
    @LuhnCheck              被注释的元素${validatedValue}的校验码不合法, Luhn模10校验和不匹配
    @Mod10Check             被注释的元素${validatedValue}的校验码不合法, 模10校验和不匹配
    @Mod11Check             被注释的元素${validatedValue}的校验码不合法, 模11校验和不匹配
    @ModCheck               被注释的元素${validatedValue}的校验码不合法, ${modType}校验和不匹配  (已过期)
    @NotBlank               被注释的元素不能为空  (已过期)
    @NotEmpty               被注释的元素不能为空  (已过期)
    @ParametersScriptAssert 被注释的元素执行脚本表达式"{script}"没有返回期望结果
    @Range                  被注释的元素需要在{min}和{max}之间
    @SafeHtml               被注释的元素可能有不安全的HTML内容
    @ScriptAssert           被注释的元素执行脚本表达式"{script}"没有返回期望结果
    @URL                    被注释的元素需要是一个合法的URL

    @DurationMax            被注释的元素必须小于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
    @DurationMin            被注释的元素必须大于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}

 

在Controller中校验数据

@Slf4j
@RestController
@RequestMapping(value = "/index")
public class IndexController {


    @RequestMapping(value = "/save", method = RequestMethod.POST)
    public R save(@Validated ValidationModel validationModel) {

        log.info("接收到参数为 [{}]", validationModel);

        return R.success(validationModel);
    }


}

简单封装一个返回参数类

@Data
public class R implements Serializable {

    private Integer code;

    private String message;

    private T data;

    public R() {
        this.code = 200;
        this.message = "SUCCESS";
    }


    public static  R success(T data) {
        R r = new R();
        r.setData(data);
        return r;
    }

    public static R fail() {
        R r = new R();
        r.setCode(500);
        r.setMessage("FAIL");
        return r;
    }

    public static R fail(String message) {
        R r = new R();
        r.setCode(500);
        r.setMessage(message);
        return r;
    }
}

postman

SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截_第2张图片

 相应参数:

{
    "timestamp": "2019-09-14T15:17:25.919+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Range.validationModel.age",
                "Range.age",
                "Range.java.lang.String",
                "Range"
            ],
            "arguments": [
                {
                    "codes": [
                        "validationModel.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                },
                60,
                18
            ],
            "defaultMessage": "年龄必须在 18 至 60 之间",
            "objectName": "validationModel",
            "field": "age",
            "rejectedValue": "10",
            "bindingFailure": false,
            "code": "Range"
        }
    ],
    "message": "Validation failed for object='validationModel'. Error count: 1",
    "path": "/index/save"
}

实现全局统一返回参数:

Spring MVC 在 org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler类已经进行了异常拦截,我们只需要找到相应的处理方法,重写就可以了

SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截_第3张图片

SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截_第4张图片

重写ResponseEntityExceptionHandler类的handleBindException

SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截_第5张图片

SpringBoot使用Validation校验参数以及自定义注解进行参数校验全局异常拦截_第6张图片

@Slf4j
@RestControllerAdvice
public class ValidationInterceptor extends ResponseEntityExceptionHandler {

    @Override
    public ResponseEntity handleBindException(
           BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(getError(ex.getBindingResult().getAllErrors()) , status);
    }


    /**
     * 解决 JSON 请求统一返回参数
     */
    @Override
    protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(getError(ex.getBindingResult().getAllErrors()) , status);
    }

    private R getError(List allErrors) {
        StringBuffer message = new StringBuffer();
        for(ObjectError error: allErrors){
            message.append(error.getDefaultMessage()).append(" & ");
        }
        log.error(message.substring(0, message.length() - 3));  // 因为&两边空格
        return R.fail(message.substring(0, message.length() - 3));
    }
} 
  

postman 再次请求,返回参数

{
    "code": 500,
    "message": "年龄必须在 18 至 60 之间",
    "data": null
}

自定义注解

        虽然Bean Validation和Hibernate Validator已经提供了非常丰富的校验注解,但是在实际业务中,难免会碰到一些现有注解不足以校验的情况;这时,我们可以考虑自定义Validation注解。

创建自定义注解:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {PhoneValidator.class}) // 指定此注解的实现,即:验证器
public @interface PhoneValidatorAnnotation {

    boolean required() default true;

    String message() default "请输入正确的手机格式";

    Class[] groups() default {};

    Class[] payload() default {};
}

编写校验器 验证该注解

实现 ConstraintValidator 接口 并且重写 isValid 方法

public class PhoneValidator implements ConstraintValidator {

    private boolean required = false;

    @Override
    public void initialize(PhoneValidatorAnnotation phoneValidatorAnnotation) {
        required = phoneValidatorAnnotation.required();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return RegexUtils.regCheck(value, RegularConstants.PHONE_REGEXP);
    }
}

正则常量

public class RegularConstants {

    /**
     * 匹配电话
     */
    public static final Pattern PHONE_REGEXP = Pattern.compile("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$");

}
正则工具类
public class RegexUtils {

    /**
     * @param str 被匹配的字符串
     * @param pattern 正则表达式
     * @return  是否匹配成功
     */
    public static boolean regCheck(String str, Pattern pattern){
        if (str == null || str.equals("")) {
            return false;
        }

        Matcher matcher = pattern.matcher(str);
        return matcher.matches();
    }

}

使用自定义注解

@Data
public class ValidationModel {

    /**
     * 主键
     */
    @NotNull(message = "主键不能为空")
    private Long id;

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

    /**
     * 邮箱
     */
    @NotBlank(message = "email 不能为空")
    @Email(message = "email 格式不正确")
    private String email;

    /**
     * 年龄
     */
    @Range(min = 18, max = 60, message = "年龄必须在 {min} 至 {max} 之间")
    private String age;

    /**
     * 手机号
     */
    @PhoneValidatorAnnotation
    private String phone;

}

postman测试

{
    "code": 500,
    "message": "请输入正确的手机格式",
    "data": null
}

分组校验


       当使用Validation校验框架的时候,一般都会将校验信息配置在对于的参数类中,如上面的ValidationModel 实体类。这样一来,所有使用该参数类的Controller类对应的方法都要进行一次校验。

      直接定义在参数类中的校验注解,需要满足当参数类被多个Controller所共用时,每个Controller方法对该参数类有不同的校验规则。

创建两个接口,不需要任何参数

public interface AddGroups {

}
public interface DeleteGroups {
    
}
public interface UpdateGroups {

}

参数类

@Data
public class ValidationModel {

    /**
     * 主键
     */
    @NotNull(message = "主键不能为空", groups = {AddGroups.class, UpdateGroups.class, DeleteGroups.class})
    private Long id;

    /**
     * 名称
     */
    @NotBlank(message = "名称不能为空", groups = {AddGroups.class, UpdateGroups.class})
    private String name;

    /**
     * 邮箱
     */
    @NotBlank(message = "email 不能为空", groups = {AddGroups.class, UpdateGroups.class})
    @Email(message = "email 格式不正确", groups = {AddGroups.class, UpdateGroups.class})
    private String email;

    /**
     * 年龄
     */
    @Range(min = 18, max = 60, message = "年龄必须在 {min} 至 {max} 之间", groups = {AddGroups.class, UpdateGroups.class})
    private String age;

    /**
     * 手机号
     */
    @PhoneValidatorAnnotation(groups = {AddGroups.class, UpdateGroups.class})
    private String phone;

    /**
     * 描述
     */
    @NotBlank(message = "描述不能为空")
    @Length(min = 10, max = 100, message = "描述长度必须在 {min} 至 {max} 之间")
    private String presentation;
}

在Controller中校验数据,只需要在该实体类前面的@Validated注解中添加一个value值即可,该value值指定校验规则所在的接口

@Slf4j
@RestController
@RequestMapping(value = "/index")
public class IndexController {

    @RequestMapping(value = "/save", method = RequestMethod.POST)
    public R save(@Validated(AddGroups.class) ValidationModel validationModel) {

        log.info("save 接收到参数为 [{}]", validationModel);

        return R.success(validationModel);
    }

    @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
    public R delete(@Validated(DeleteGroups.class) ValidationModel validationModel) {

        log.info("delete 接收到参数为 [{}]", validationModel);

        return R.success(validationModel);
    }

    @RequestMapping(value = "/update", method = RequestMethod.PUT)
    public R update(@Validated(UpdateGroups.class) ValidationModel validationModel) {

        log.info("delete 接收到参数为 [{}]", validationModel);

        return R.success(validationModel);
    }

    @RequestMapping(value = "/jsonSave", method = RequestMethod.POST)
    public R jsonSave(@Validated @RequestBody ValidationModel validationModel) {

        log.info("jsonSave 接收到参数为 [{}]", validationModel);

        return R.success(validationModel);
    }

}

@Validated与@Valid的简单对比说明
        @Valid注解与@Validated注解功能大部分类似;两者的不同主要在于:@Valid属于javax下的,而@Validated属于spring下;@Valid支持嵌套校验、而@Validated不支持,@Validated支持分组,而@Valid不支持。

@Validated分组上面已经写过就不再多写,下面为@Valid

参数类 如果一个参数类中包含第二个bean,这时要检验第二个bean中某个字段,即嵌套校验,必须要在第一个参数类对象中使用@Valid标注到表示第二个bean对象的字段上,然后再第二个bean对象里面的字段上加上校验类型

@Data
public class ValidModel {

    @NotNull(message = "ValidModel主键不能为空")
    private Long id;

    @Valid
    private ValidBean validBean;

    @Data
    private class ValidBean {

        @NotNull(message = "ValidBean主键不能为空")
        private Long id;

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

在Controller中校验数据,只需要在该实体类前面加上@Valid即可

@Slf4j
@RestController
@RequestMapping(value = "/valid")
public class ValidController {

    @RequestMapping(value = "/jsonSave", method = RequestMethod.POST)
    public R jsonSave(@Valid @RequestBody ValidModel validModel) {

        log.info("jsonSave 接收到参数为 [{}]", validModel);

        return R.success(validModel);
    }

}

项目已上传至 GitHub https://github.com/soul0928/liuyun-validator , 不足之处望各位海涵

 

 

 

 

 

 

 

你可能感兴趣的:(java)