验证框架主要分为两种,即分层验证与JavaBean验证
分层验证模型
传统的校验模式,即每一层都添加数据验证。但是其验证逻辑重复性大,会出现冗余代码过多的情况
JavaBean验证
JavaBean验证模式指向前端接收数据时,在JavaBean上做数据校验。其校验逻辑无需写在其他层中
Bean Validation
Bean Validation 为 JavaBean 验证定义了相应的元数据模型和API。
---维基百科
分类 | 限制 | 说明 | |
---|---|---|---|
空/非空检查 | @NULL | 限制只能为NULL | |
@NotNull | 限制必须不为NULL | ||
@NotNull | 验证注解的元素值不为Null且不为空(字符串长度不为0,集合大小不为0) | ||
@NotBlack | 验证注解的元素值不为空(不为Null,去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 | ||
Boolean值检查 | @AssertFalse | 限制必须为False | |
@AssertTrue | 限制必须为True | ||
长度检查 | @Size(max,min) | 限制字符长度必须在min到max之间 | |
@Leanth | 限制字符长度必须在min到max之间 | ||
日期检查 | @Future | 限制日期为当前时间之后 | |
@FutureOrPresent | 限制日期为当前时间或之后 | ||
@Past | 限制日期为当前时间之前 | ||
@PastOrPresent | 限制日期为当前时间或之前 | ||
数值检查 | @Max(Value) | 限制必须为一个不大于指定值的数字 | |
@Min(Value) | 限制必须为一个不小于指定值的数字 | ||
@DecimalMin(value) | 限制必须为一个不小于指定值的数字 | ||
@DecimalMax(value) | 限制必须为一个不小于指定值的数字 | ||
@Digits(integer,fraction) | 限制必须为小数,且整数部分的位数不能超过Integer,小数部分的位数不能超过fraction | ||
@Negative | 限制必须为负整数 | ||
@NegativeOrZero(value) | 限制必须为负整数或零 | ||
@Positive(value) | 限制必须为正整数 | ||
@PositiveOrZero(value) | 限制必须为正整数或零 | ||
其他检查 | @Pattern(Value) | 限制必须符合指定的正则表达式 | |
限制必须为email格式 |
@Validated用于Controller层向前端接收参数时,对参数合法性进行校验的开启
@PostMapping("/testMethod")
@ApiOperation(value = "testMethod")
public RespResult testMethod(@RequestBody @Validated TestParam testParam){
... ...
}
复制代码
@Validated只用于对功能的开启,代表本次开启参数校验
真正的参数校验注解在要进行参数合法性校验的类中进行书写
/**
* 测试类
*
* @author WeiYL
* @date 2022/7/27 16:15
*/
@Data
@ApiModel(value = "测试处理类")
public class TestParamDTO{
/**
* 名称
*/
@ApiModelProperty(value = "隐患描述", example = "隐患描述编辑测试")
@Length(max = 512, message = "长度非法,超过{max}字符!")
String testName;
/**
* 描述
*/
@ApiModelProperty(value = "描述", example = "描述编辑测试")
@NotBlank(message = "描述不能为空")
@Length(max = 512, message = "长度非法,超过{max}字符!")
String testDescribe;
/**
* 等级
*/
@ApiModelProperty(value = "等级", example = "1")
@NotBlank(message = "等级不能为空")
@Length(min = 1, max = 1, message = "等级字符长度不为{max}")
String testGrade;
}
复制代码
@Length 长度校验
@Length(min = 1, max = 10, message = "字符长度为{min}-{max}")
复制代码
@NotBlank 字符非空判断
只用于String,不能为null且trim()之后size>0
验证注释的String字符串不为NULL或为空,纯空格的String也是不符合规则的
@NotBlank(message = "等级不能为空")
复制代码
@NotEmpty
不能为NULL,且Size>0。
@NotEmpty注解的String、Collection、Map、数组是不能为NULL或长度为0的(不能为 “ ” )
@NotEmpty(message = "等级不能为空")
复制代码
@NotNull
不能为NULL,但是可以为Empty,没有Size的约束
带注解的元素不能为NULL,但可以为“”。接受任何类型。
@NotNull(message = "等级不能为空")
复制代码
级联验证
@Valid 与 @Validated
@Valid: 没有分组的功能。
@Valid: 可以用在方法、构造函数、方法参数和成员属性(字段)上
@Validated: 提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
@Validated: 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能
分组校验功能
需求: 新增和修改功能使用同一个DTO,新增时对ID字段无要求,修改时校验ID不为空
- 创建分组接口
/**
* <h1>参数验证分组</h1>
* @date 2022/7/23 10:18
*/
public class GroupType{
/**
* <h2>修改</h2>
*/
public interface UpdateGroup{ };
/**
* <h2>新增</h2>
*/
public interface CreateGroup{ };
}
复制代码
- 在DTO的参数校验注解中设置groups属性
/**
* <h1>...操作参数</h1>
*
* @author WeiYL
* @date 2022/7/21 9:56
*/
@Data
public class PeccancyStandardDTO{
/**
* 主键
*/
@NotBlank(groups = {GroupType.UpdateGroup.class})
@ApiModelProperty(value = "主键,修改时必填", required = true, example = "11111")
private String id;
}
复制代码
- 在Controller层的参数列表中设定分组
/**
* ...页面
* @author ...
* @date 2022/7/23 19:33
*/
@Api(tags = "...页面")
@Slf4j
@RestController
@RequestMapping("/check")
@AllArgsConstructor
public class controller{
@PostMapping("/update")
@ApiOperation(value = "修改作业隐患记录")
public String update(@RequestBody @Validated(value = GroupType.UpdateGroup.class) ...DTO DTO){
... ...
}
}
复制代码
组序列
对几个分组同时验证
1. 对分组进行追加
缺点:没有对一个组的验证顺序做明确定义
2. 使用@GroupSequence注解进行排序
/**
* <h1>参数验证分组</h1>
* @date 2022/7/23 10:18
*/
public class GroupType{
/**
* <h2>修改</h2>
*/
public interface UpdateGroup{ };
/**
* <h2>新增</h2>
*/
public interface CreateGroup{ };
@GroupSequence({
LoginGroup.class,
RegisterGroup.class
})
}
复制代码
输入输出参数校验
// 验证器对象
private Validator validator;
// 待验证对象
private UserInfo userInfo;
// 验证结果集合
private Set<ConstraintViolation<UserInfo>> set;
// 验证结果集合
private Set<ConstraintViolation<UserInfoService>> otherSet;
/**
* 对方法输入参数进行约束注解校验
*/
@Test
public void paramValidation() throws NoSuchMethodException {
// 获取校验执行器
ExecutableValidator executableValidator =
validator.forExecutables();
// 待验证对象
UserInfoService service = new UserInfoService();
// 待验证方法
Method method = service.getClass()
.getMethod("setUserInfo", UserInfo.class);
// 方法输入参数
Object[] paramObjects = new Object[]{new UserInfo()};
// 对方法的输入参数进行校验
otherSet = executableValidator.validateParameters(
service,
method,
paramObjects);
}
复制代码
对返回值进行约束校验
// 验证器对象
private Validator validator;
// 待验证对象
private UserInfo userInfo;
// 验证结果集合
private Set<ConstraintViolation<UserInfo>> set;
// 验证结果集合
private Set<ConstraintViolation<UserInfoService>> otherSet;
/**
* 对方法返回值进行约束校验
*/
@Test
public void returnValueValidation()
throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
// 获取校验执行器
ExecutableValidator executableValidator =
validator.forExecutables();
// 构造要验证的方法对象
UserInfoService service = new UserInfoService();
Method method = service.getClass()
.getMethod("getUserInfo");
// 调用方法得到返回值
Object returnValue = method.invoke(service);
// 校验方法返回值是否符合约束
otherSet = executableValidator.validateReturnValue(
service,
method,
returnValue);
}
复制代码
public @Valid UserInfo getUserInfo(){
return new UserInfo();
}
复制代码
对构造函数输入参数进行校验
/**
* 对构造函数输入参数进行校验
*/
@Test
public void constructorValidation()
throws NoSuchMethodException {
// 获取验证执行器
ExecutableValidator executableValidator =
validator.forExecutables();
// 获取构造函数
Constructor constructor =
UserInfoService.class
.getConstructor(UserInfo.class);
Object[] paramObjects = new Object[]{new UserInfo()};
// 校验构造函数
otherSet = executableValidator
.validateConstructorParameters(
constructor, paramObjects);
}
复制代码