一、前言
JSR 是 Java Specification Requests 的缩写,含义为 JAVA 规范提案。
JSR 303 - Bean Validation 规范, 正是一套基于 JavaBean 参数校验的标准。
Hibernate Validator 是 JSR 303 的实现,它提供了 JSR 303 规范中所有约束(constraint)的实现,同时也对其作出一些拓展。
Spring Validation 是对 Hibernate validation 的二次封装,用于支持 Spring MVC 参数校验。
😊 JSR 303 包含的注解:
验证注解 | 验证数据类型 | 说明 |
@Null | 任意类型 | 元素值为 Null |
@NotNull | 任意类型 | 元素值不为 Null |
@AssertTrue | Bool | 元素为 true |
@AssertFalse | Bool | 元素为 false |
@Min(value = 最小值) | BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型 | 元素值需大于等于指定值 |
@Max(value = 最大值) | BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型 | 元素值需小于等于指定值 |
@DecimalMin(value = 最小值) | BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型 | 元素值需大于等于指定值 |
@DecimalMax(value = 最大值) | BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型 | 元素值需小于等于指定值 |
@Size(min = 最小值, max = 最大值) | String、Collection、Array 等 | 元素值的字符长度/集合大小需在指定的区间范围内 |
@Digits(integer = 整数位数, fraction = 小数位数) | BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型 | 元素值整数位、小数位需小于对应指定值 |
@Past | DATE、Calendar、Time 等日期 | 元素值需在指定时间之前 |
@Future | DATE、Calendar、Time 等日期 | 元素值需在指定时间之后 |
@Pattern(regexp = 正则式, flag = 标志的模式) | String 等 | 元素值与指定的正则式匹配 |
😊hibernate.validator 扩展的注解:
验证注解 | 验证数据类型 | 说明 |
@NotBlank | String 等 CharSequence 子类型 | 元素值不为空串(字符串不为 Null 且去除首尾空格后长度不为 0) |
@Email(regexp = 正则式, flag = 标志的模式) | String 等 | 元素值为电子邮箱格式,可通过 regexp、flag 属性指定格式 |
@Length(min = 最小值, max = 最大值) | String 等 | 元素值长度在指定区间范围内 |
@NotEmpty | String、Collection、Array 等 | 元素值不为 Null 且长度不为 0 |
@Range(min = 最小值, max = 最大值) | BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型 | 元素值在指定区间范围内 |
@URL | String 等 | 元素值必须时合法的URL |
二、Spring Validation的使用
1、在项目pom.xml中引入依赖
<!-- JSR 303 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<!-- 在 SpringBoot 项目中,若 SpringBoot 版本小于 2.3.x ,则此依赖已包含在 spring-boot-starter-web 中,无需添加额外依赖;
若 SpringBoot 版本大于 2.3.x ,则需手动引入依赖。-->
<!-- Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
2、 参数注解校验的使用场景
场景一:直接入参的校验
①在类上添加 @Validated 注解,否则参数校验无法生效;
②入参中使用注解 @NotEmpty 等;
@Validated
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/saveUserName")
public String saveUserInfo(@NotEmpty String userName) {
System.out.println("userName:"+ userName +",保存成功");
return "success";
}
}
测试:
【请求URL】:
http://192.168.1.7:27100/user/saveUserName?userName=
【执行结果】:
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintViolationException: saveUserInfo.userName: 不能为空] with root cause
javax.validation.ConstraintViolationException: saveUserInfo.userName: 不能为空
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:116)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
……
场景二:对象模型属性的校验
1、创建 User对象
@Data
public class User {
private Integer id;
@NotEmpty(message = "用户名不能为空")
private String userName;
private Integer age;
@NotNull(message = "用户密码不能为空")
@Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
private String password;
}
2、全局异常捕获
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 方法直接入参校验
*
* @param ex
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public BaseResult resolveConstraintViolationException(ConstraintViolationException ex) {
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if (!CollectionUtils.isEmpty(constraintViolations)) {
String errorMessage = constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(", "));
return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, errorMessage);
}
return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, ex.getMessage());
}
/**
* 对象模型属性校验
*
* @param ex
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseResult resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
if (!CollectionUtils.isEmpty(allErrors)) {
String errorMessage = allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(", "));
return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, errorMessage);
}
return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, ex.getMessage());
}
}
3、通用返回结构体
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class BaseResult<T> {
private Integer code;
private String message;
private T data;
public static <T> BaseResult<T> success() {
return new BaseResult(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMsg(), new JSONObject());
}
public static <T> BaseResult<T> fail(ResultEnum resultEnum, T data) {
return fail(resultEnum.getCode(), resultEnum.getMsg(), data);
}
@Override
public String toString() {
return "BaseResult{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
4、错误提示码枚举类
/**
* 错误提示码
*/
public enum ResultEnum {
/** 状态码:未知错误 **/
UNKNOWN_ERROR(-1, "未知错误"),
/** 状态码:成功 **/
SUCCESS(0, "成功"),
/** 状态码:失败 **/
FAIL(1,"失败"),
/** 状态码:非法参数 **/
ILLEGAL_PARAMETER(2,"非法参数");
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
5、暴露的接口
① 在类上添加 @Validated 注解,否则参数校验无法生效;
② 在入参实体前添加 @Validated 注解;
@Validated
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/saveUserInfo")
public String saveUserInfo(@RequestBody @Validated User user) {
System.out.println("userName:"+ user.getUserName() +",保存成功");
return "success";
}
}
6、测试:
【请求URL】:http://192.168.1.7:27100/user/saveUserInfo
【请求方式】:POST
【请求参数】:
{
"userName":"张三"
}
【执行结果】:
{
"code": 2,
"message": "非法参数",
"data": "用户密码不能为空"
}
场景三:参数注解校验的分组验证
1、新建分组接口
/**
* 分组接口:新增
*/
public interface Add {
}
/**
* 分组接口:更新
*/
public interface Update {
}
2、在校验注解中使用 groups 属性标明分组
@Data
public class User {
@NotNull(message = "更新数据时,主键id不能为空", groups = Update.class)
private Integer id;
@NotEmpty(message = "用户名不能为空")
private String userName;
private Integer age;
@NotNull(message = "用户密码不能为空")
@Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
private String password;
}
【注】需要注意的是实体类中所有字段注解校验规则默认属于 Default 分组,接口入参 @Validated 注解默认检验 Default 分组的校验规则。当接口入参使用 @Validated 显式声明非默认分组时,实体类中所有未显式声明分组的注解校验将不会生效。
3、处理器方法
@Validated
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/updateUserInfo")
public String updateUserInfo(@RequestBody @Validated({Update.class}) User user) {
System.out.println("id:" + user.getId() + ",userName:"+ user.getUserName() +",更新成功");
return "success";
}
}
4、测试
【请求URL】:http://192.168.1.7:27100/user/updateUserInfo
【请求方式】:POST
【请求参数】:
{
"id":1,
"userName":"李四"
}
【执行结果】:
id:1,userName:李四,更新成功
【注】入参中没有传“密码”,也没有校验,若默认属于 Default 分组的字段也需要校验,可以写成:
public String updateUserInfo(@RequestBody @Validated({Update.class, Default.class}) User user) {
场景四:级联校验
1、在实体类模型中,可能会存在集合类型或实体类型的成员变量,该成员中的字段仍然需要校验。
在要校验的对象类型的属性/ list上使用 @Valid 注解:
@Data
public class User {
@NotNull(message = "更新数据时,主键id不能为空", groups = Update.class)
private Integer id;
@NotEmpty(message = "用户名不能为空")
private String userName;
private Integer age;
@NotNull(message = "用户密码不能为空")
@Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
private String password;
@Valid
private List<Car> cars;
}
@Data
public class Car {
@NotNull(message = "车牌号码不能为空")
private String plateCode;
@NotNull(message = "车牌颜色不能为空")
private String plateColor;
}
2、处理器方法
@Validated
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/saveUserInfo")
public String saveUserInfo(@RequestBody @Validated User user) {
System.out.println("userName:"+ user.getUserName() +",保存成功");
return "success";
}
}
3、测试
【请求URL】:http://192.168.1.7:27100/user/saveUserInfo
【请求方式】:POST
【请求参数】:
{
"userName": "李四",
"password": "123456",
"cars": [
{
"plateCode": "京A0001",
"plateColor": "1"
},
{
"plateCode": "京A0002"
}
]
}
【执行结果】:
{
"code": 2,
"message": "非法参数",
"data": "车牌颜色不能为空"
}
场景五:自定义注释校验
如下:校验用户下车牌号码必须包含“京A”,否则返回提示;
1、自定义注释
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {MustContainKeyValidator.class})
public @interface MustContainKey {
//默认错误信息
String message() default "必须含有指定关键字";
//分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
}
2、真正的校验者类,实现 ConstraintValidator 接口
public class MustContainKeyValidator implements ConstraintValidator<MustContainKey,String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!StringUtils.isEmpty(value) && !value.contains("京A")) {
// 获取默认提示消息
String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
System.out.println(defaultConstraintMessageTemplate);
// 禁用默认提示信息
// context.disableDefaultConstraintViolation();
//设置提示语
// context.buildConstraintViolationWithTemplate("must contain key").addConstraintViolation();
return false;
}
return true;
}
}
3、在实体类的属性上添加该注释
@Data
public class User {
@NotNull(message = "更新数据时,主键id不能为空", groups = Update.class)
private Integer id;
@NotEmpty(message = "用户名不能为空")
private String userName;
private Integer age;
@NotNull(message = "用户密码不能为空")
@Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
private String password;
@Valid
private List<Car> cars;
}
@Data
public class Car {
@MustContainKey
@NotNull(message = "车牌号码不能为空")
private String plateCode;
@NotNull(message = "车牌颜色不能为空")
private String plateColor;
}
4、测试
【请求URL】:http://192.168.1.7:27100/user/saveUserInfo
【请求方式】:POST
【请求参数】:
{
"userName": "李四",
"password": "123456",
"cars": [
{
"plateCode": "京A0001",
"plateColor": "1"
},
{
"plateCode": "浙C0002",
"plateColor": "2"
}
]
}
【执行结果】:
{
"code": 2,
"message": "非法参数",
"data": "必须含有指定关键字"
}
以上就是所有关于 Spring Validation 的介绍,感兴趣的同学欢迎点赞+收藏!