现在互联网生存环境

在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
head.jpeg

为什么要前后端都要去做参数校验?

我想很多小伙伴都有这个想法,为什么前端已经判断过参数了,正常情况下人家访问到接口的时候数据已经是可以安全的了,那后端检验的意义在哪里呢?我是这样理解的:’后端进行参数校验,是防止别人通过接口乱刷服务’。也就是 如果一些不安好心的人,通过接口刷我们的服务,随便那个参数我们都允许填入,会导致数据库中大量的脏数据、风险,如果注入的是病毒怎么办?所以,后端进行校验是必须要有的。

前言

在我们了解过后端做参数校验的重要性以后,我们在原始处理参数问题的时候,很经常而且很熟练的使用if else的判断校验方式(这个说的是我自己,哈哈哈 后来因为经理的一顿指责,哎,看来板砖也得要有好的小推车啊!!!!)
数据校验是在平时的编码过程中常做的工作,在系统的各个层可能都要去实现一些校验逻辑,再去做业务处理。这些繁琐的校验与我们的业务代码在一块就会显得臃肿。而且这些校验通常是业务无关的。这样让我们的工作事倍功半!!!
下面开始进入正题

Bean Validation 2.0(JSR 380)

JSR 380是用于bean验证的Java API的规范,是JavaEE和JavaSE的一部分,它使用@NotNull、@Min和@Max等注释确保bean的属性满足特定的标准。
此版本需要Java 8或更高版本,并利用Java 8中添加的新功能(如类型注释),并支持Optional和LocalDate等新类型。
有关规范的完整信息,请继续阅读JSR 380

Hibernate Validator的简述

Hibernate Validator是Bean Validation 2.0(JSR 380)的实现方式,这盘文章主要就是说HIbernate Validator的使用
注意:hibernate-validator 与 持久层框架 **hibernate** 没有什么关系,hibernate-validator 是 hibernate 组织下的一个开源项目
它和 **hibernate** 没什么关系,放心大胆的使用吧。

Hibernate Validator的作用

  • 验证逻辑与业务逻辑之间进行了分离,降低了程序耦合度
  • 统一且规范的验证方式,无需你再次编写重复的验证代码

    Hibernate Validator的使用

    依赖

    如果是springBoot项目,那么spring-boot-starter-web中就已经依赖hibernate-validator
    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-web</artifactId>
    4. </dependency>
    注意springboot2.x后导入依赖需要手动
    1. <!--参数校验:在springboot2.x之后 web起步依赖中不再含有该依赖 需要手动在引入依赖-->
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-validation</artifactId>
    5. </dependency>
    如果是其他项目,那可以直接添加hibernate-validator依赖
    1. <dependency>
    2. <groupId>org.hibernate.validator</groupId>
    3. <artifactId>hibernate-validator</artifactId>
    4. <version>6.0.17.Final</version>
    5. </dependency>

    约束注解

    validator-api-2.0的约束注解有22个,具体我们看下面表格

    空与非空检查

    | 注解 | 支持Java类型 | 说明 | | —- | —- | —- | | @Null | Object | 为null | | @NotNull | Object | 不为null | | @NotBlank | CharSequence | 不为null,且必须有一个非空格字符 | | @NotEmpty | CharSequence、Collection、Map、Array | 不为null,且不为空(length/size>0) |

Boolean值检查

注解 支持Java类型 说明 备注
@AssertTrue boolean、Boolean 为true 为null有效
@AssertFalse boolean、Boolean 为false 为null有效

日期检查

注解 支持Java类型 说明 备注
@Future Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间之后 为null有效
@FutureOrPresent Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间或之后 为null有效
@Past Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间之前 为null有效
@PastOrPresent Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间或之前 为null有效

数值检查

注解 支持Java类型 说明 备注
@Max BigDecimal、BigInteger,byte、short、int、long以及包装类 小于或等于 为null有效
@Min BigDecimal、BigInteger,byte、short、int、long以及包装类 大于或等于 为null有效
@DecimalMax BigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类 小于或等于 为null有效
@DecimalMin BigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类 大于或等于 为null有效
@Negative BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 负数 为null有效,0无效
@NegativeOrZero BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 负数或零 为null有效
@Positive BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 正数 为null有效,0无效
@PositiveOrZero BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 正数或零 为null有效
@Digits(integer = 3, fraction = 2) BigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类 整数位数和小数位数上限 为null有效

其他

注解 支持Java类型 说明 备注
@Pattern CharSequence 匹配指定的正则表达式 为null有效
@Email CharSequence 邮箱地址 为null有效,默认正则 '.*'
@Size CharSequence、Collection、Map、Array 大小范围(length/size>0) 为null有效

hibernate-validator扩展约束(部分)

注解 支持Java类型 说明
@Length String 字符串长度范围
@Range 数值类型和String 指定范围
@URL URL地址验证

自定义约束注解

除了以上提供的约束注解(大部分情况都是能够满足的),我们还可以根据自己的需求自定义自己的约束注解
定义自定义约束,有三个步骤

  • 创建约束注解
  • 实现一个验证器
  • 定义默认的错误信息

那么下面就直接来定义一个简单的验证手机号码的注解

  1. /**
  2. * @author 王振宇
  3. * @Description <h1>验证手机号码</h1>
  4. * @create 2021-05-24 17:53
  5. */
  6. //@Documented:用于标识自定义注解能够使用javadoc命令生成关于注解的文档
  7. @Documented
  8. /*@Target:用于明确注解用于目标类的哪个位置
  9. ElementType枚举类的值:
  10. Type:用于注解类
  11. Fields:用于注解属性
  12. Methods:用于注解方法
  13. Parameter:用于注解参数*/
  14. @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
  15. /*@Constraint(validatedBy = {MobileValidator.class})该注解是指明我们的自定义约束的验证器*/
  16. @Constraint(validatedBy = {MobileValidator.class})
  17. /*@Retention:用于标识自定义注解的声明周期
  18. RetentionPolicy枚举类的值:
  19. SOURCE:没有被编译器编译
  20. CLASS:不会在运行时,被JVM保留
  21. RUNTIME:生命周期持续到运行时,能够通过反射获取到*/
  22. @Retention(RUNTIME)
  23. /*@Repeatable注解和List定义可以让该注解在同一个位置重复多次,通常是不同的配置(比如不同的分组和消息)*/
  24. @Repeatable(Mobile.List.class)
  25. /*自定义约束需要下面3个属性
  26. message 错误提示信息,可以写死,也可以填写国际化的key
  27. groups 分组信息,允许指定此约束所属的验证组(下面会说到分组约束)
  28. payload 有效负载,可以通过payload来标记一些需要特殊处理的操作*/
  29. public @interface Mobile {
  30. /**
  31. * 错误提示信息,可以写死,也可以填写国际化的key
  32. */
  33. String message() default "手机号码不正确"; //目前就演示写死了
  34. Class<?>[] groups() default {};
  35. Class<? extends Payload>[] payload() default {};
  36. String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
  37. @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
  38. @Retention(RUNTIME)
  39. @Documented
  40. @interface List {
  41. Mobile[] value();
  42. }
  43. }

关于注解的配置这里不说了,自定义约束需要下面3个属性

  • message 错误提示信息,可以写死,也可以填写国际化的key
  • groups 分组信息,允许指定此约束所属的验证组(下面会说到分组约束)
  • payload 有效负载,可以通过payload来标记一些需要特殊处理的操作

@Repeatable注解和List定义可以让该注解在同一个位置重复多次,通常是不同的配置(比如不同的分组和消息)
@Constraint(validatedBy = {MobileValidator.class})该注解是指明我们的自定义约束的验证器
实现一个验证器MovileValidator

  1. /**
  2. * @author 王振宇
  3. * @Description <h1>Mobile自定义约束的验证器</h1>
  4. * @create 2021-05-24 18:02
  5. */
  6. //ConstraintValidator接口定义了在实现中设置的两个类型参数。第一个指定要验证的注解类(如Mobile),第二个指定验证器可以处理的元素类型(如String);
  7. public class MobileValidator implements ConstraintValidator<Mobile,String> {
  8. /**
  9. * 手机验证规则
  10. */
  11. private Pattern pattern;
  12. /**
  13. * initialize()方法可以访问约束注解的属性值;
  14. * @param constraintAnnotation
  15. */
  16. @Override
  17. public void initialize(Mobile constraintAnnotation) {
  18. pattern = Pattern.compile(constraintAnnotation.regexp());
  19. }
  20. /**
  21. * isValid()方法用于验证,返回true表示验证通过
  22. * @param value
  23. * @param context
  24. * @return
  25. */
  26. @Override
  27. public boolean isValid(String value, ConstraintValidatorContext context) {
  28. //Bean验证规范建议将空值视为有效。如果null不是元素的有效值,则应使用@NotNull 显式注释
  29. if (value == null) {
  30. return true;
  31. }
  32. return pattern.matcher(value).matches();
  33. }
  34. }

在需要的地方通过@Mobile就可以检验
(更细节的自定义约束注解我会做一次有质量的总结,后续我会发布)

关于文章我只是建议大家参考,因为有很多细节以及很多坑我还没有替大家踩完,可能也有我理解错误的地方,希望大家给出一份宝贵的建议,共同进步才是双赢!!!!

这就是Hibernate Validator一个小小总结,结合了很多优秀的文章内容,站在巨人的肩膀上去进行技术的提升,关于SpringBoot的整合,以及检验后的信息回溯,怎样更优美的进行验证异常处理,我会在写一篇关于springboot在实际项目中的实践.