坚持年年写博客,不能断了,所以粘贴平时写的一份笔记吧
一、简介
校验参数在以前基本都是使用大量的if/else
,稍微方便一点的可以使用反射+自定义注解
的形式,但是复用性不是很好,并且每个人对于的自定义注解有着自己的使用习惯,不过好在spring开发了validated框架用于注解校验,可以节省很多的校验ifelse
代码。
@PostMapping("/save")
public Object save(@RequestBody User user) {
String mobile = userVO.getMobile();
//手动逐个 参数校验~ 写法
if (StringUtils.isBlank(mobile)) {
return R.bulid(ResultEnum.PARAM_FAIL_CODE,"mobile:手机号码不能为空");
} else if (!Pattern.matches("^[1][3,4,5,6,7,8,9][0-9]{9}$", mobile)) {
return R.bulid(ResultEnum.PARAM_FAIL_CODE,"mobile:手机号码格式不对");
}
//抛出自定义异常等~写法
if (StringUtils.isBlank(userVO.getUsername())) {
throw new ParamException(ResultEnum.PARAM_FAIL_CODE, "用户名不能为空");
}
// 比如写一个map返回
if (StringUtils.isBlank(userVO.getSex())) {
Map<String, Object> result = new HashMap<>(5);
result.put("code", Constant.PARAM_FAIL_CODE);
result.put("msg", "性别不能为空");
return result;
}
//.........各种写法 ...
userService.save(user);
return R.success();
}
二、Spring Validation
spring Validation
是一种参数检验工具,集成在spring-context包中, 常用于spring mvc
中Controller
的参数处理,主要针对整个实体类的多个可选域进行判定,对于不合格的数据信息springMVC会把它保存在错误对象中,这些错误信息我们也可以通过SpringMVC
提供的标签或者前端的脚本等在前端页面上进行展示。
1、实现方式
实现方式和使用方式:一般使用较多的是两个注解:@Validated
、@Valid
-
@Valid和@Validated 两种注释都会导致应用标准Bean验证。
-
如果验证不通过会抛出
BindException
异常,并变成400(BAD_REQUEST)响应; -
或者可以通过Errors或BindingResult参数在控制器内本地处理验证错误。
-
如果参数前有@RequestBody注解,验证错误会抛出
MethodArgumentNotValidException
异常。
2、Java Bean Validation
JSR是Java Specification Requests的缩写,意思是Java 规范提案。关于数据校验这块,最新的是JSR380,也就是我们常说的Bean Validation 2.0。
Bean Validation 2.0 是JSR第380号标准。该标准连接如下:The Java Community Process(SM) Program - Community Update - View Community Update Information for JSR# 380
Bean Validation的主页:Jakarta Bean Validation - Home
Bean Validation的参考实现:https://github.com/hibernate/hibernate-validator
Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API(规范)和Hibernate Validator(实现)。
Bean Validation是Java定义的一套基于注解/xml的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本
3、@Valid
和@Validated
区别
可以理解成@Validated是@Valid升级版
区别 | @Valid | @Validated |
---|---|---|
提供者 | JSR-303规范 | Spring(validator-api-2.0) |
是否支持分组 | 不支持 | 支持 |
标注位置 | METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE | TYPE, METHOD, PARAMETER |
嵌套校验 | 支持 | 不支持 |
Spring最终是调用Hibernate Validator执行校验,Spring Validation只是做了一层封装。
通过源码分析:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
Class<?>[] value() default {};
}
@Valid:没有分组的功能。
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
关于分组功能,此处我们不涉及,有兴趣的同学可以自己研究一下
3、常用注解
validator-api-2.0的约束注解有22个,具体我们看下面表格
-
空与非空检查
注解 | 支持Java类型 | 说明 |
---|---|---|
@Null | Object | 为null |
@NotNull | Object | 不为null |
@NotBlank | CharSequence | 不为null,且必须有一个非空格字符 |
@NotEmpty | CharSequence、Collection、Map、Array | 不为null,且不为空(length/size>0) |
@NotNull:适用于基本数据类型(Integer,Long,Double等等),当 @NotNull 注解被使用在 String 类型的数据上,则表示该数据不能为 Null(但是可以为 Empty,即双引号空) @NotBlank:适用于 String 类型的数据上,加了@NotBlank 注解的参数不能为 Null 且 trim() 之后 size > 0 @NotEmpty:适用于 String、Collection集合、Map、数组等等,加了@NotEmpty 注解的参数不能为 Null 或者 长度为 0
-
Boolean值检查
注解 | 支持Java类型 | 说明 | 备注 |
---|---|---|---|
@AssertTrue | boolean、Boolean | 为true | 为null有效 |
@AssertFalse | boolean、Boolean | 为false | 为null有效 |
-
日期检查
注解 | 说明 | 备注 |
---|---|---|
@Future | 验证日期为当前时间之后 | 为null有效 |
@FutureOrPresent | 验证日期为当前时间或之后 | 为null有效 |
@Past | 验证日期为当前时间之前 | 为null有效 |
@PastOrPresent | 验证日期为当前时间或之前 | 为null有效 |
-
数值检查
注解 | 说明 | 备注 |
---|---|---|
@Max | 小于或等于 | 为null有效 |
@Min | 大于或等于 | 为null有效 |
@DecimalMax | 小于或等于 | 为null有效 |
@DecimalMin | 大于或等于 | 为null有效 |
@Negative | 负数 | 为null有效,0无效 |
@NegativeOrZero | 负数或零 | 为null有效 |
@Positive | 正数 | 为null有效,0无效 |
@PositiveOrZero | 正数或零 | 为null有效 |
@Digits(integer = 3, fraction = 2) | 整数位数和小数位数上限 | 为null有效 |
-
其他
注解 | 说明 | 备注 |
---|---|---|
@Pattern | 匹配指定的正则表达式 | 为null有效 |
邮箱地址 | 为null有效,默认正则 '.*' | |
@Size | 大小范围(length/size>0) | 为null有效 |
-
hibernate-validator扩展约束(部分)
注解 | 支持Java类型 | 说明 |
---|---|---|
@Length | String | 字符串长度范围 |
@Range | 数值类型和String | 指定范围 |
@URL | URL地址验证 |
综合在一起,对于不同的api版本可能出现部分注解过时等情况,注意!
meta-data | comment | version |
---|---|---|
@Null | 对象,为空 | Bean Validation 1.0 |
@NotNull | 对象,不为空 | Bean Validation 1.0 |
@AssertTrue | 布尔,为True | Bean Validation 1.0 |
@AssertFalse | 布尔,为False | Bean Validation 1.0 |
@Min(value) | 数字,最小为value | Bean Validation 1.0 |
@Max(value) | 数字,最大为value | Bean Validation 1.0 |
@DecimalMin(value) | 数字,最小为value | Bean Validation 1.0 |
@DecimalMax(value) | 数字,最大为value | Bean Validation 1.0 |
@Size(max, min) | min<=value<=max | Bean Validation 1.0 |
@Digits (integer, fraction) | 数字,某个范围内 | Bean Validation 1.0 |
@Past | 日期,过去的日期 | Bean Validation 1.0 |
@Future | 日期,将来的日期 | Bean Validation 1.0 |
@Pattern(value) | 字符串,正则校验 | Bean Validation 1.0 |
字符串,邮箱类型 | Bean Validation 2.0 | |
@NotEmpty | 集合,不为空 | Bean Validation 2.0 |
@NotBlank | 字符串,不为空字符串 | Bean Validation 2.0 |
@Positive | 数字,正数 | Bean Validation 2.0 |
@PositiveOrZero | 数字,正数或0 | Bean Validation 2.0 |
@Negative | 数字,负数 | Bean Validation 2.0 |
@NegativeOrZero | 数字,负数或0 | Bean Validation 2.0 |
@PastOrPresent(时间) | 过去或者现在 | Bean Validation 2.0 |
@FutureOrPresent(时间) | 将来或者现在 | Bean Validation 2.0 |
三、通过BindingResult处理错误信息
使用
Validator
,利用BindingResult
获取Errors信息
1、pom引入依赖
只需要spring-boot-starter-validation和web即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2、修改实体类User
package com.woniuxy.ssm.entity;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
*
* @TableName user
*/
@Data
public class User implements Serializable {
/**
*
*/
private Integer id;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
@Length(message = "用户名长度不能超过{max}个字符",max = 10)
private String userName;
/**
* 电话
*/
@Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$",message = "手机号码有误!")
private String tel;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Size(message = "密码不小于{min}位", min= 8)
private String password;
/**
* 年龄
*/
@NotNull(message = "请输入年龄")
@Range(message = "年龄范围为 {min} 到 {max} 之间", min = 1, max = 100)
private Integer age;
/**
*
*/
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd" ,timezone = "GMT+8")
private Date createDate;
/**
*
*/
private String headImg;
/**
*
*/
private Integer deptId;
private static final long serialVersionUID = 1L;
}
3、修改UserController中的add方法
得到所有错误中的第一个返回给用户显示,第一个并不一定是固定的,如果密码长度和年龄同时不满足,多次查询返回的结果并不是固定的,且不会超过这两个错误信息
/**
* @Description TODO
* @Valid 表示对这个对象校验
* @param user
* @param bindingResult 获取的是校验的结果,这个对象有许多方法获取校验信息,可以自定义返回信息
* @Return com.woniuxy.ssm.util.Result
* @Author fengSir
* @Date Create by 2022-06-12 14:58
*/
@PostMapping("addDo")
public Result add(@Valid @RequestBody User user, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return Result.error(bindingResult.getFieldError().getDefaultMessage());
}
int result = userService.insertSelective(user);
return Result.ok();
}
我们也可以@Valid换成@Validated,两者效果相同
4、通过测试工具类测试
三、使用全局异常处理错误信息
虽然使用bindingResult可以解决校验问题,但代码侵入性太强。此时我们可以考虑采用全局异常来解决
1、改造UserController中的add方法
@PostMapping("addDo")
public Result add(@Validated @RequestBody User user){
int result = userService.insertSelective(user);
return Result.ok();
}
2、通过测试工具测试
由于参数里有@RequestBody注解,验证错误会抛出
MethodArgumentNotValidException
异常
3、自定义全局异常
package com.woniuxy.ssm.exception;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author :fengSir
* @date :Created By 2022-06-12 16:12
* @description :TODO
*/
@RestControllerAdvice
public class ValidExceptionHandler {
@ExceptionHandler(BindException.class)
public Result validExceptionHandler(BindException exception) {
return Result.error(exception.getBindingResult().getFieldError().getDefaultMessage());
}
}
4、再次测试
得到和以前一样的测试结果
四、@Valid 和 @Validated 比较
对 @Valid 和 @Validated 两个注解进行总结下:
-
@Valid 和 @Validated 两者都可以对数据进行校验,待校验字段上打的规则注解(@NotNull, @NotEmpty等)都可以对 @Valid 和 @Validated 生效;
-
@Valid 进行校验的时候,需要用 BindingResult 来做一个校验结果接收。当校验不通过的时候,如果手动不 return ,则并不会阻止程序的执行;
-
@Validated 进行校验的时候,也可以用 BindingResult 来做一个校验结果接收。当校验不通过的时候,程序会抛出400异常,阻止方法中的代码执行,这时需要再写一个全局校验异常捕获处理类,然后返回校验提示。
-
总体来说,@Validated 使用起来要比 @Valid 方便一些,它可以帮我们节省一定的代码,并且使得方法看上去更加的简洁。