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

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

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

<dependencies>
    <dependency>    
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

示例

新建参数类

@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;
}
  1. 文档地址: https://docs.oracle.com/javaee/7/api/javax/validation/constraints/package-frame.html

  2. 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<T> implements Serializable {
    private Integer code;
    private String message;
    private T data;

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

    public static <T> R<T> 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

 相应参数

{
    "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类已经进行了异常拦截,我们只需要找到相应的处理方法,重写就可以了

重写ResponseEntityExceptionHandler类的handleBindException

@Slf4j
@RestControllerAdvice
public class ValidationInterceptor extends ResponseEntityExceptionHandler {

    @Override

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


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


    private R getError(List<ObjectError> 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<? extends Payload>[] payload() default {};

}

编写校验器 验证该注解

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

public class PhoneValidator implements ConstraintValidator<PhoneValidatorAnnotation, String> {
    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

@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 , 不足之处望各位海涵

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值