前言

Hibernate Validator 是 Bean Validation 的参考实现 。Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint

在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。

在SpringBoot中可以使用@Validated,注解Hibernate Validator加强版,也可以使用@Valid原来Bean Validation java版本

内置校验注解

Bean Validation 中内置的 constraint

注解 作用
@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
@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(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

注解 作用
@Email 被注释的元素必须是电子邮箱地址
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=, max=) 被注释的元素必须在合适的范围内
@NotBlank 被注释的字符串的必须非空
@URL(protocol=,host=, port=, regexp=, flags=) 被注释的字符串必须是一个有效的url
@CreditCardNumber 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性
@ScriptAssert(lang=, script=, alias=) 要有Java Scripting API 即JSR 223("Scripting for the JavaTM Platform")的实现
@SafeHtml(whitelistType=,additionalTags=) classpath中要有jsoup包

message支持表达式和EL表达式 ,比如message = "姓名长度限制为{min}到{max} ${1+2}")

想把错误描述统一写到properties的话,在classpath下面新建ValidationMessages_zh_CN.properties文件(注意value需要转换为unicode编码),然后用{}格式的占位符

hibernate补充的注解中,最后3个不常用,可忽略。

主要区分下@NotNull @NotEmpty @NotBlank 3个注解的区别:

  1. @NotNull 任何对象的value不能为null
  2. @NotEmpty 集合对象的元素不为0,即集合不为空,也可以用于字符串不为null
  3. @NotBlank 只能用于字符串不为null,并且字符串trim()以后length要大于0

分组校验

如果同一个参数,需要在不同场景下应用不同的校验规则,就需要用到分组校验了。比如:新注册用户还没起名字,我们允许name字段为空,但是在更新时候不允许将名字更新为空字符。

分组校验有三个步骤:

  1. 定义一个分组类(或接口)
public interface Update extends Default{
}
  1. 在校验注解上添加groups属性指定分组
public class UserVO {
@NotBlank(message = "name 不能为空",groups = Update.class)
private String name;
// 省略其他代码...
}
  1. Controller方法的@Validated注解添加分组类
@PostMapping("update")
public ResultInfo update(@Validated({Update.class}) UserVO userVO) {
return new ResultInfo().success(userVO);
}

自定义的Update分组接口继承了Default接口。校验注解(如: @NotBlank)和@validated默认其他注解都属于Default.class分组,这一点在javax.validation.groups.Default注释中有说明

/**
* Default Jakarta Bean Validation group.
* <p>
* Unless a list of groups is explicitly defined:
* <ul>
* <li>constraints belong to the {@code Default} group</li>
* <li>validation applies to the {@code Default} group</li>
* </ul>
* Most structural constraints should belong to the default group.
*
* @author Emmanuel Bernard
*/
public interface Default {
}

在编写Update分组接口时,如果继承了Default,下面两个写法就是等效的:

@Validated({Update.class}),@Validated({Update.class,Default.class})

如果Update不继承Default,@Validated({Update.class})就只会校验属于Update.class分组的参数字段

递归校验

如果 UserVO 类中增加一个 OrderVO 类的属性,而 OrderVO 中的属性也需要校验,就用到递归校验了,只要在相应属性上增加@Valid注解即可实现(对于集合同样适用)

public class OrderVO {
@NotNull
private Long id;
@NotBlank(message = "itemName 不能为空")
private String itemName;
// 省略其他代码...
}
public class UserVO {
@NotBlank(message = "name 不能为空",groups = Update.class)
private String name;
//需要递归校验的OrderVO
@Valid
private OrderVO orderVO;
// 省略其他代码...
}

自定义校验

validation 为我们提供了这么多特性,几乎可以满足日常开发中绝大多数参数校验场景了。但是,一个好的框架一定是方便扩展的。有了扩展能力,就能应对更多复杂的业务场景,毕竟在开发过程中,唯一不变的就是变化本身。 Validation允许用户自定义校验

实现很简单,分两步:

  1. 自定义校验注解
package cn.soboys.core.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*; /**
* @author kenx
* @version 1.0
* @date 2021/1/21 20:49
* 日期验证 约束注解类
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsDateTimeValidator.class}) // 标明由哪个类执行校验逻辑
public @interface IsDateTime { // 校验出错时默认返回的消息
String message() default "日期格式错误";
//分组校验
Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; //下面是我自己定义属性
boolean required() default true; String dateFormat() default "yyyy-MM-dd"; }

注意:message用于显示错误信息这个字段是必须的,groups和payload也是必须的

@Constraint(validatedBy = { HandsomeBoyValidator.class})用来指定处理这个注解逻辑的类

  1. 编写校验者类
package cn.soboys.core.validator;

import cn.hutool.core.util.StrUtil;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; /**
* @author kenx
* @version 1.0
* @date 2021/1/21 20:51
* 日期验证器
*/
public class IsDateTimeValidator implements ConstraintValidator<IsDateTime, String> { private boolean required = false;
private String dateFormat = "yyyy-MM-dd"; /**
* 用于初始化注解上的值到这个validator
* @param constraintAnnotation
*/
@Override
public void initialize(IsDateTime constraintAnnotation) {
required = constraintAnnotation.required();
dateFormat = constraintAnnotation.dateFormat();
} /**
* 具体的校验逻辑
* @param value
* @param context
* @return
*/
public boolean isValid(String value, ConstraintValidatorContext context) {
if (required) {
return ValidatorUtil.isDateTime(value, dateFormat);
} else {
if (StrUtil.isBlank(value)) {
return true;
} else {
return ValidatorUtil.isDateTime(value, dateFormat);
}
}
} }

注意这里验证逻辑我抽出来单独写了一个工具类,ValidatorUtil

package cn.soboys.core.validator;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil; import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.regex.Matcher;
import java.util.regex.Pattern; /**
* @author kenx
* @version 1.0
* @date 2021/1/21 20:51
* 验证表达式
*/
public class ValidatorUtil {
private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");
private static final Pattern money_pattern = Pattern.compile("^[0-9]+\\.?[0-9]{0,2}$"); /**
* 验证手机号
*
* @param src
* @return
*/
public static boolean isMobile(String src) {
if (StrUtil.isBlank(src)) {
return false;
}
Matcher m = mobile_pattern.matcher(src);
return m.matches();
} /**
* 验证枚举值是否合法 ,所有枚举需要继承此方法重写
*
* @param beanClass 枚举类
* @param status 对应code
* @return
* @throws Exception
*/
public static boolean isEnum(Class<?> beanClass, String status) throws Exception {
if (StrUtil.isBlank(status)) {
return false;
} //转换枚举类
Class<Enum> clazz = (Class<Enum>) beanClass;
/**
* 其实枚举是语法糖
* 是封装好的多个Enum类的实列
* 获取所有枚举实例
*/
Enum[] enumConstants = clazz.getEnumConstants(); //根据方法名获取方法
Method getCode = clazz.getMethod("getCode");
Method getDesc = clazz.getMethod("getDesc");
for (Enum enums : enumConstants) {
//得到枚举实例名
String instance = enums.name();
//执行枚举方法获得枚举实例对应的值
String code = getCode.invoke(enums).toString();
if (code.equals(status)) {
return true;
}
String desc = getDesc.invoke(enums).toString();
System.out.println(StrFormatter.format("实列{}---code:{}desc{}", instance, code, desc));
}
return false;
} /**
* 验证金额0.00
*
* @param money
* @return
*/
public static boolean isMoney(BigDecimal money) {
if (StrUtil.isEmptyIfStr(money)) {
return false;
}
if (!NumberUtil.isNumber(String.valueOf(money.doubleValue()))) {
return false;
}
if (money.doubleValue() == 0) {
return false;
}
Matcher m = money_pattern.matcher(String.valueOf(money.doubleValue()));
return m.matches();
} /**
* 验证 日期
*
* @param date
* @param dateFormat
* @return
*/
public static boolean isDateTime(String date, String dateFormat) {
if (StrUtil.isBlank(date)) {
return false;
}
try {
DateUtil.parse(date, dateFormat);
return true;
} catch (Exception e) {
return false;
}
} }

我自定义了补充了很多验证器,包括日期验证,枚举验证,手机号验证,金额验证

自定义校验注解使用起来和内置注解无异,在需要的字段上添加相应注解即可

校验流程解析

使用 Validation API 进行参数效验步骤整个过程如下图所示,用户访问接口,然后进行参数效验 ,如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理

全局异常出来请参考我这篇文章SpringBoot优雅的全局异常处理

关注公众号猿人生获取更多干货分享

发送参数校验 获取完整自定义源码

SpringBoot Validation参数校验 详解自定义注解规则和分组校验的更多相关文章

  1. Spring_数据校验和自定义检验规则和分组校验

    @Validated  :绑定需要校验的数据. 数据校验规则:为数据绑定校验的规则 private Long booId;@NotNull(message = "不能为空")pri ...

  2. SpringBoot各类扩展点详解

    一.前言 上篇文章我们深入分析了SpringBoot的一站式启动流程.然后我们知道SpringBoot的主要功能都是依靠它内部很多的扩展点来完成的,那毋容置疑,这些扩展点是我们应该深入了解的,那么本次 ...

  3. Solon详解(六)- Solon的校验扩展框架使用与扩展

    Solon详解系列文章: Solon详解(一)- 快速入门 Solon详解(二)- Solon的核心 Solon详解(三)- Solon的web开发 Solon详解(四)- Solon的事务传播机制 ...

  4. Springboot@Configuration和@Bean详解

    Springboot@Configuration和@Bean详解 一.@Configuration @Target({ElementType.TYPE}) @Retention(RetentionPo ...

  5. SpringBoot配置文件 application.properties详解

    SpringBoot配置文件 application.properties详解   本文转载:https://www.cnblogs.com/louby/p/8565027.html 阅读过程中若发现 ...

  6. Scala 深入浅出实战经典 第62讲:Scala中上下文界定内幕中的隐式参数实战详解

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt ...

  7. Scala 深入浅出实战经典 第60讲:Scala中隐式参数实战详解以及在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  8. Oracle GoldenGate中HANDLECOLLISIONS参数使用详解

    Oracle GoldenGate中HANDLECOLLISIONS参数使用详解   HANDLECOLLISIONS 是一个 replicat 进程参数,主要在 initial load 中使用.在 ...

  9. 机器学习——随机森林,RandomForestClassifier参数含义详解

    1.随机森林模型 clf = RandomForestClassifier(n_estimators=200, criterion='entropy', max_depth=4) rf_clf = c ...

随机推荐

  1. localectl set-locale LANG=en_US.UTF-8

    localectl set-locale LANG=en_US.UTF-8    

  2. 运维实战案例之“Too many open files”错误与解决方法

    运维实战案例之"Too many open files"错误与解决方法   技术小甜 2017-11-16 15:02:00 浏览869 服务器 shell tomcat 脚本 o ...

  3. 串口1配合DMA接收不定长数据(空闲中断+DMA接收)

    1.空闲中断和别的接收完成(一个字节)中断,发送完成(发送寄存器控)中断的一样是串口中断: 2.空闲中断是接收到一个数据以后,接收停顿超过一字节时间  认为桢收完,总线空闲中断是在检测到在接收数据后, ...

  4. MySQL 通过.frm文件和.ibd文件实现InnoDB引擎的数据恢复

    起因是这样的,公司的领导表示说服务器崩了,修理好之后,只剩下数据库目录下的物理文件(即.frm文件与.ibd文件).然后,整了一份压缩包给我,叫我瞅一下能不能把数据恢复出来.我当场愣了一下,这都啥文件 ...

  5. rabbit_消费者

    import pika import json import time import os import ast import uuid import time import json import ...

  6. MCU,硅片,BOM

    MCU,硅片,BOM BOM(Bill of Material,物料清单),就是指一个东西的各个材料的的成本价格 BOM成本要控制,有三点要注意的. 一,是否有芯片替代料,在性能不降低的情况下,替代料 ...

  7. 构建编译TVM方法

    构建编译TVM方法 本文提供如何在各种系统上构建和安装TVM包的说明.它包括两个步骤: 1.     首先从C代码构建共享库( libtvm.so for linux, libtvm.dylib fo ...

  8. TensorFlow优化器及用法

    TensorFlow优化器及用法 函数在一阶导数为零的地方达到其最大值和最小值.梯度下降算法基于相同的原理,即调整系数(权重和偏置)使损失函数的梯度下降. 在回归中,使用梯度下降来优化损失函数并获得系 ...

  9. 基于ARM Cortex-M的SoC存储体系结构和实战

    基于ARM Cortex-M的SoC存储体系结构和实战 System on Chip Architecture Tutorial Memory Architecture for ARM Cortex- ...

  10. 重新整理 .net core 实践篇—————中间件[十九]

    前言 简单介绍一下.net core的中间件. 正文 官方文档已经给出了中间件的概念图: 和其密切相关的是下面这两个东西: IApplicationBuilder 和 RequestDelegate( ...