前言

日常开发中过程中,时刻存在着数据的校验,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。

最普通的做法就像下面这样。我们通过 if/else 语句对请求的每一个参数一一校验。

    @ApiOperation(value = "validator测试接口")
    @PostMapping("/demo/validator")
    public Result validatorTest(@RequestBody ValidatorDemoDTO demo) {
        if (Objects.isNull(demo.getName())){
            //抛出异常
            throw new BaseException("name is empty");
        }
        return Result.success();
    }

这样的代码,小伙伴们在日常开发中一定不少见,很多开源项目都是这样对请求入参做校验的。

但是,不太建议这样来写,这样的代码明显违背了 单一职责原则。大量的非业务代码混杂在业务代码中,非常难以维护,还会导致业务层代码冗杂!

这里就要介绍到 spring-boot-starter-validation

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

我们只需要验证的参数上加上了**@Valid**注解,如果验证失败,它将抛出MethodArgumentNotValidException。默认情况下,Spring 会将此异常转换为 HTTP Status 400(错误请求)。

一定一定不要忘记在类上加上 @Validated 注解了,这个参数可以告诉 Spring 去校验方法参数


自定义 Validator

在实际开发需求中,如果自带的校验注解无法满足你的需求的话,你还可以自定义实现注解。

1.第一步,你需要创建一个注解 例如:Region

代码如下(示例):

@Documented
@Constraint(
        validatedBy = RegionValidator.class
)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Region {
    String message() default "值不在可选范围内";

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

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

可参考已有注解 例如:@NotBlank

@Documented
@Constraint(
    validatedBy = {}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotBlank.List.class)
public @interface NotBlank {
    String message() default "{javax.validation.constraints.NotBlank.message}";

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

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

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        NotBlank[] value();
    }
}

2.第二步,你需要实现 RegionValidator接口,并重写isValid 方法。

代码如下(示例):

public class RegionValidator implements ConstraintValidator<Region, String> {

    @Override
    public boolean isValid(String region, ConstraintValidatorContext constraintValidatorContext) {
        //校验逻辑 
        String regExp = "^((1[0-5])|[1-9])?\\d$";
        return region.matches(regExp);
    }
}

现在你就可以使用这个注解:

@Data
public class ValidatorDemoDTO {
    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotBlank(message = "年龄不能为空")
    @Region(message = "年龄值不在合理范围内")
    private String age;
}

通过测试验证

    @PostMapping("/demo/validator")
    public Result validatorTest(@RequestBody  @Valid ValidatorDemoDTO demo) {
        return Result.success();
    }

测试结果:
【自定义 Validator】-LMLPHP


总结

JSR303 定义了 Bean Validation(校验)的标准 validation-api,并没有提供实现。Hibernate Validation是对这个规范/规范的实现 hibernate-validator,并且增加了 @Email、@Length、@Range 等注解。Spring Validation 底层依赖的就是Hibernate Validation。

JSR 提供的校验注解:

@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(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 提供的校验注解:

@NotBlank(message =) 验证字符串非 null,且长度必须大于 0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
01-18 19:11