现在互联网生存环境
在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
为什么要前后端都要去做参数校验?
我想很多小伙伴都有这个想法,为什么前端已经判断过参数了,正常情况下人家访问到接口的时候数据已经是可以安全的了,那后端检验的意义在哪里呢?我是这样理解的:’后端进行参数校验,是防止别人通过接口乱刷服务’。也就是 如果一些不安好心的人,通过接口刷我们的服务,随便那个参数我们都允许填入,会导致数据库中大量的脏数据、风险,如果注入的是病毒怎么办?所以,后端进行校验是必须要有的。
前言
在我们了解过后端做参数校验的重要性以后,我们在原始处理参数问题的时候,很经常而且很熟练的使用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了
注意springboot2.x后导入依赖需要手动<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
如果是其他项目,那可以直接添加<!--参数校验:在springboot2.x之后 web起步依赖中不再含有该依赖 需要手动在引入依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
hibernate-validator依赖<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.17.Final</version></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有效 |
| CharSequence | 邮箱地址 | 为null有效,默认正则 '.*' |
|
| @Size | CharSequence、Collection、Map、Array | 大小范围(length/size>0) | 为null有效 |
hibernate-validator扩展约束(部分)
| 注解 | 支持Java类型 | 说明 |
|---|---|---|
| @Length | String | 字符串长度范围 |
| @Range | 数值类型和String | 指定范围 |
| @URL | URL地址验证 |
自定义约束注解
除了以上提供的约束注解(大部分情况都是能够满足的),我们还可以根据自己的需求自定义自己的约束注解
定义自定义约束,有三个步骤
- 创建约束注解
- 实现一个验证器
- 定义默认的错误信息
那么下面就直接来定义一个简单的验证手机号码的注解
/*** @author 王振宇* @Description <h1>验证手机号码</h1>* @create 2021-05-24 17:53*///@Documented:用于标识自定义注解能够使用javadoc命令生成关于注解的文档@Documented/*@Target:用于明确注解用于目标类的哪个位置ElementType枚举类的值:Type:用于注解类Fields:用于注解属性Methods:用于注解方法Parameter:用于注解参数*/@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})/*@Constraint(validatedBy = {MobileValidator.class})该注解是指明我们的自定义约束的验证器*/@Constraint(validatedBy = {MobileValidator.class})/*@Retention:用于标识自定义注解的声明周期RetentionPolicy枚举类的值:SOURCE:没有被编译器编译CLASS:不会在运行时,被JVM保留RUNTIME:生命周期持续到运行时,能够通过反射获取到*/@Retention(RUNTIME)/*@Repeatable注解和List定义可以让该注解在同一个位置重复多次,通常是不同的配置(比如不同的分组和消息)*/@Repeatable(Mobile.List.class)/*自定义约束需要下面3个属性message 错误提示信息,可以写死,也可以填写国际化的keygroups 分组信息,允许指定此约束所属的验证组(下面会说到分组约束)payload 有效负载,可以通过payload来标记一些需要特殊处理的操作*/public @interface Mobile {/*** 错误提示信息,可以写死,也可以填写国际化的key*/String message() default "手机号码不正确"; //目前就演示写死了Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})@Retention(RUNTIME)@Documented@interface List {Mobile[] value();}}
关于注解的配置这里不说了,自定义约束需要下面3个属性
message错误提示信息,可以写死,也可以填写国际化的keygroups分组信息,允许指定此约束所属的验证组(下面会说到分组约束)payload有效负载,可以通过payload来标记一些需要特殊处理的操作
@Repeatable注解和List定义可以让该注解在同一个位置重复多次,通常是不同的配置(比如不同的分组和消息)@Constraint(validatedBy = {MobileValidator.class})该注解是指明我们的自定义约束的验证器
实现一个验证器MovileValidator
/*** @author 王振宇* @Description <h1>Mobile自定义约束的验证器</h1>* @create 2021-05-24 18:02*///ConstraintValidator接口定义了在实现中设置的两个类型参数。第一个指定要验证的注解类(如Mobile),第二个指定验证器可以处理的元素类型(如String);public class MobileValidator implements ConstraintValidator<Mobile,String> {/*** 手机验证规则*/private Pattern pattern;/*** initialize()方法可以访问约束注解的属性值;* @param constraintAnnotation*/@Overridepublic void initialize(Mobile constraintAnnotation) {pattern = Pattern.compile(constraintAnnotation.regexp());}/*** isValid()方法用于验证,返回true表示验证通过* @param value* @param context* @return*/@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {//Bean验证规范建议将空值视为有效。如果null不是元素的有效值,则应使用@NotNull 显式注释if (value == null) {return true;}return pattern.matcher(value).matches();}}
在需要的地方通过@Mobile就可以检验
(更细节的自定义约束注解我会做一次有质量的总结,后续我会发布)
关于文章我只是建议大家参考,因为有很多细节以及很多坑我还没有替大家踩完,可能也有我理解错误的地方,希望大家给出一份宝贵的建议,共同进步才是双赢!!!!
这就是Hibernate Validator一个小小总结,结合了很多优秀的文章内容,站在巨人的肩膀上去进行技术的提升,关于SpringBoot的整合,以及检验后的信息回溯,怎样更优美的进行验证异常处理,我会在写一篇关于springboot在实际项目中的实践.
