Mushroom Notes Mushroom Notes
🍄首页
  • JavaSE

    • 基础篇
    • 数据结构
    • IO流
    • Stream流
    • 函数式接口
    • JUC
    • 反射
    • 网络编程
    • 设计模式
  • JavaEE

    • Servlet
    • JDBC
    • 会话技术
    • 过滤器监听器
    • 三层架构
  • JDK

    • 总览
  • JVM

    • 总览
  • 常用mate
  • CSS
  • JavaScript
  • rds 数据库

    • MySQL
    • MySQL 进阶
    • MySQL 库表规范
  • nosql 数据库

    • Redis
    • Redis 进阶
    • Redis 底层
    • MongoDB
  • Spring生态

    • Spring
    • Spring MVC
    • Spring boot
    • Spring Validation
  • Spring Cloud生态

    • Spring Cloud
    • 服务治理
    • 远程调用
    • 网关路由
    • 服务保护
    • 分布式事务
    • 消息中间件
  • 数据库

    • Mybatis
    • Mybatis Plus
    • Elasticsearch
    • Redisson
  • 通信

    • Netty
📚技术
  • 方案专题
  • 算法专题
  • BUG专题
  • 安装专题
  • 网安专题
  • 面试专题
  • 常用网站
  • 后端常用
  • 前端常用
  • 分类
  • 标签
  • 归档

kinoko

一位兴趣使然的热心码农
🍄首页
  • JavaSE

    • 基础篇
    • 数据结构
    • IO流
    • Stream流
    • 函数式接口
    • JUC
    • 反射
    • 网络编程
    • 设计模式
  • JavaEE

    • Servlet
    • JDBC
    • 会话技术
    • 过滤器监听器
    • 三层架构
  • JDK

    • 总览
  • JVM

    • 总览
  • 常用mate
  • CSS
  • JavaScript
  • rds 数据库

    • MySQL
    • MySQL 进阶
    • MySQL 库表规范
  • nosql 数据库

    • Redis
    • Redis 进阶
    • Redis 底层
    • MongoDB
  • Spring生态

    • Spring
    • Spring MVC
    • Spring boot
    • Spring Validation
  • Spring Cloud生态

    • Spring Cloud
    • 服务治理
    • 远程调用
    • 网关路由
    • 服务保护
    • 分布式事务
    • 消息中间件
  • 数据库

    • Mybatis
    • Mybatis Plus
    • Elasticsearch
    • Redisson
  • 通信

    • Netty
📚技术
  • 方案专题
  • 算法专题
  • BUG专题
  • 安装专题
  • 网安专题
  • 面试专题
  • 常用网站
  • 后端常用
  • 前端常用
  • 分类
  • 标签
  • 归档
  • Spring

    • Spring
    • Spring MVC
    • Spring Boot
    • Spring Validation
      • JSR-303 简介
      • 依赖引入
      • JSR-303 基本的校验规则
        • 空校验
        • 布尔校验
        • 长度校验
        • 日期校验
        • 正则校验
        • 数值检查
        • 对象校验
      • 使用范例
      • 全局异常捕获
      • @Validated和@Valid的区别
        • 分组校验
        • 嵌套校验
  • SpringCloud

  • 数据库

  • 通信

  • 框架
  • Spring
kinoko
2023-12-17
目录

Spring Validation

提示

内容参考自:链接 (opens new window)

# JSR-303 简介

JSR-303 是 JavaEE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator。

此实现与 Hibernate ORM 没有任何关系。JSR-303 用于对 Java Bean 中的字段的值进行验证。 Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中使用注解的方式对表单提交的数据方便地验证。Spring 4.0 开始支持 Bean Validation 功能。在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。

# 依赖引入

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.0.1.Final</version>
</dependency>
1
2
3
4
5

如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。 如果spring-boot版本大于2.3.x,则需要手动引入依赖:

# JSR-303 基本的校验规则

# 空校验

  • @Null 验证对象是否为 null
  • @NotNull 验证对象是否不为 null, 无法查检长度为 0 的字符串
  • @NotBlank 检查约束字符串是不是 Null 还有被 Trim 的长度是否大于 0,只对字符串,且会去掉前后空格
  • @NotEmpty 检查约束元素是否为 NULL 或者是EMPTY

# 布尔校验

  • @AssertTrue 验证Boolean对象是否为 true
  • @AssertFalse 验证 Boolean对象是否为false

# 长度校验

  • @Size(min=, max=) 验证对象(Array, Collection , Map, String)长度是否在给定的范围之内
  • @Length(min=, max=)验证字符串长度介于 min 和 max 之间

# 日期校验

  • @Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
  • @Future 验证Date和Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期

# 正则校验

  • @Pattern 验证 String对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式
    • regexp:正则表达式
    • flags:指定Pattern.Flag的数组,表示正则表达式的相关选项

# 数值检查

注意: 建议使用在String ,Integer 类型,不建议使用在int类型上,因为表单值为 “”时无法转换为int,但可以转换为String 为 “”,Integer 为null

  • @Min 验证 Number 和 String 对象是否大等于指定的值
  • @Max 验证 Number 和 String 对象是否小等于指定的值
  • @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过 BigDecimal 定义的最大值的字符串表示 .小数 存在精度
  • @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过 BigDecimal 定义的最小值的字符串表示 .小数 存在精度
  • @Digits 验证Number 和 String的构成是否合法
  • @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,integer 指定整数精度,fraction 指定小数精度
  • @Range(min=, max=) 被指定的元素必须在合适的范围内
  • @Range(min=10000,max=50000,message=”range.bean.wage”)
  • @CreditCardNumber 信用卡验证
  • @Email 验证是否是邮件地址,如果为 null,不进行验证,算通过验证
  • @ScriptAssert(lang= ,script=, alias=)
  • @URL(protocol=,host=, port=,regexp=, flags=)
@Range(min = 1, max = 4, message = "统筹类型不合法")
@NotNull(message = "统筹类型不能为空!")
private Integer overallType;


@Digits(integer = 1, fraction = 2, message = "统筹比例格式不正确!")
@DecimalMin(message = "请填写0-1的比例!", value = "0.00")
@DecimalMax(message = "请填写0-1的比例!",value = "1.00")
@NotNull(message = "统筹比例不能为空!")
private BigDecimal overallProportion;
1
2
3
4
5
6
7
8
9
10
  1. @Digits:digit是数位的意思,这里的integer意思整数最多有几位,fraction意思小数最多有几位,只是划定了传入参数的范围,前端传值的时候可以用字符串(只要字符串里面都是数字就行)、数字;
  2. @DecimalMin:decimal的最小值,传入参数必须大于等于value里面的值;
  3. @NotNull:传入参数不为空,对于BigDecimal格式,用@NotNull注解,如果传参是空字符串或者只有空格的字符串,也无法通过校验。如果字段的类型是String,建议使用@NotBlank注解。

# 对象校验

  • @Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
  • @Validated 被注解的元素是一个对象或者一个类,需要检查此对象的所有字段值

# 使用范例

Controller

@PostMapping("/register")
@Operation(summary = "新用户注册", description = "新用户注册")
public Result<Object> register(@RequestBody @Validated RegisterReq req) {
    return toAjax(authService.register(req));
}
1
2
3
4
5

Bean

@Data
public class RegisterReq {

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
    @NotBlank(message = "请输入手机号")
    private String phone;

    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z]).{8,}$", message = "密码格式错误")
    @NotBlank(message = "请填写密码")
    private String password;

    @Email(message = "邮箱格式错误")
    private String email;

    @NotBlank(message = "请输入验证码")
    @Schema(name = "验证码")
    private String captchaAnswer;

    @NotBlank(message = "请输入验证码")
    private String requestId;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

单参数校验

@GetMapping("/test3")
public AjaxResult test3(@NotNull(message = "name不能为空") String name){
   System.out.println(name);
   return AjaxResult.success(name+" "+email);
}
1
2
3
4
5

# 全局异常捕获

由于校验异常默认抛出的message结构比较复杂,一般会二次处理后再响应,这里给出通用的全局异常捕获示例

/**
 * @author kinoko
 */
@Slf4j
@RestControllerAdvice
@ResponseBody
public class BaseExceptionAdvice implements ExceptionAdvice {

    /**
     * JSON参数校验异常(校验抛出的异常就在这)
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Object> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        String errorMsg = objectError.getDefaultMessage();
        log.error("[错误参数异常]" + errorMsg, e);
        return Result.error(errorMsg, ErrorCodeEnum.USER_ERROR_A0400.getCode());
    }

    /**
     * form表单参数校验异常
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Object> methodArgumentNotValidExceptionHandler(BindException e) {
        log.error("[form表单参数校验异常]" + e.getMessage(), e);
        return Result.error(ErrorCodeEnum.USER_ERROR_A0400);
    }

    /**
     * 单参数校验异常
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Object> methodArgumentNotValidExceptionHandler(ConstraintViolationException e) {
        log.error("[单参数校验异常]" + e.getMessage(), e);
        return Result.error(ErrorCodeEnum.USER_ERROR_A0400);
    }

    /**
     * 非法参数异常
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public Result<Object> illegalArgumentExceptionHandler(IllegalArgumentException e) {
        log.error("[非法参数异常]" + e.getMessage(), e);
        return Result.error(ErrorCodeEnum.USER_ERROR_A0400);
    }

    /**
     * 参数缺失异常
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result<Object> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e) {
        log.error("[参数缺失异常]" + e.getMessage(), e);
        return Result.error(ErrorCodeEnum.USER_ERROR_A0400);
    }

    /**
     * 重复键异常
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(DuplicateKeyException.class)
    public Result<Object> duplicateKeyExceptionHandler(DuplicateKeyException e) {
        log.error("[重复键异常]" + e.getMessage(), e);
        return Result.error(ErrorCodeEnum.SYSTEM_ERROR_B0322);
    }

    /**
     * 未找到处理器异常
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<Object> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e) {
        log.error("[未找到处理器异常]" + e.getMessage(), e);
        return Result.error(ErrorCodeEnum.SERVICE_ERROR_C0113);
    }

    /**
     * 远程调用异常
     * @param e 异常
     * @return 错误信息
     */
    @Override
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(FeignException.class)
    public Result<Object> feignExceptionHandler(FeignException e) {
        log.error("[远程调用异常]{}", e.getMessage());
        // 获取响应体
        String body = e.contentUTF8();
        Result<Object> result = JSON.parseObject(body, Result.class);
        // 无法转化常规格式的先初始化再返回
        if (result == null || StringUtils.isBlank(result.getCode())) {
            return Result.error();
        }
        return result;
    }

    /**
     * 未知异常
     * @param e 未知异常
     * @return 错误信息
     */
    @Override
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public Result<Object> exceptionHandler(Exception e) {
        // 未知异常打印异常链
        log.error("[未知异常]:" + e.getMessage(), e);
        return Result.error();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

# @Validated和@Valid的区别

一般情况下,若不需要分组校验的话:使用@Valid和@Validated并无特殊差异。

  1. @Valid:@Valid注解时javax包下的注解,是jdk给提供的。标准JSR-303规范的标记型注解
  2. @Validated:@Validated是@Valid 的一次封装,是Spring提供的校验机制使用。Spring的注解。是标准JSR-303的一个变种(补充),提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。
  3. @Validated只能用在类、方法和参数上,而@Valid可用于方法、字段、构造器和参数上。两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能,显然~注解可以提供嵌套校验的功能,@Validated除了没有嵌套校验的功能,其他@Valid注解有的功能,它都有,@Valid注解没有的功能,它也有。

总结:

  • @Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
  • @Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

# 分组校验

在实际开发中经常会遇到这种情况:添加用户时,id是由后端生成的,不需要校验id是否为空,但是修改用户时需要校验id是否为空。如果在接收参数的User实体类的id属性上添加NotNull,显然无法实现。这时候就可以定义分组,在需要校验id的时候校验,不需要的时候不校验。

1、定义分组类

/**
 *分组校验 (配合spring的@Validated功能分组使用)
 */
public class ValidGroup {
    // 新增使用
    public interface Insert{}

    // 更新使用
    public interface Update{}

    // 删除使用
    public interface Delete{}

    // 属性必须有这两个分组的才验证
    @GroupSequence({Insert.class, Update.class,Delete.class})
    public interface All{}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

2、 实体类中属性validation注解使用上面定义的组

//只能在Delete和Update的时候才能够进行生效.
@Min(value = 1,message = "ID不能小于1",groups = {ValidGroup.Delete.class,ValidGroup.Update.class})
private int id;

@NotBlank(message = "用户名不能为空",groups = {ValidGroup.Update.class,ValidGroup.Insert.class})
private String username;

@NotBlank(message = "密码不能为空",groups = {ValidGroup.Update.class,ValidGroup.Insert.class})
@Length(min = 6,max = 20,message = "密码长度在6-20之间")
private String password;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不合理")
private String email;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

3、controller中使用@Validated指定使用哪个组

@RequestMapping("/saveUserInfo")
public UserInfo saveUserInfo(@Validated() UserInfo userInfo){
    //save userInfo:将userInfo进行保存
    //userInfoService.save(userInfo);
    return userInfo;
}

@RequestMapping("/updateUserInfo")
public UserInfo updateUserInfo(@Validated({ValidGroup.Update.class}) UserInfo userInfo){
    //save userInfo:将userInfo进行保存
    //userInfoService.update(userInfo);
    return userInfo;
}


@RequestMapping("/deleteUserInfo")
public UserInfo deleteUserInfo(@Validated({ValidGroup.Delete.class}) UserInfo userInfo){
    //save userInfo:将userInfo进行保存
    //userInfoService.delete(userInfo);
    return userInfo;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 嵌套校验

@RestController
public class ItemController {

    @RequestMapping("/item/add")
    public void addItem(@Validated Item item, BindingResult bindingResult) {
        doSomething();
    }
}
1
2
3
4
5
6
7
8
public class Item {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;

    @Valid // 嵌套验证必须用@Valid
    @NotNull(message = "props不能为空")
    @Size(min = 1, message = "props至少要有一个自定义属性")
    private List<Prop> props;
}
1
2
3
4
5
6
7
8
9
10
11
public class Prop {

    @NotNull(message = "pid不能为空")
    @Min(value = 1, message = "pid必须为正整数")
    private Long pid;

    @NotNull(message = "vid不能为空")
    @Min(value = 1, message = "vid必须为正整数")
    private Long vid;

    @NotBlank(message = "pidName不能为空")
    private String pidName;

    @NotBlank(message = "vidName不能为空")
    private String vidName;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#spring#validation
上次更新: 2023/12/29 11:32:56
Spring Boot
Spring Cloud

← Spring Boot Spring Cloud→

最近更新
01
JVM 底层
09-13
02
JVM 理论
09-13
03
JVM 应用
09-13
更多文章>
Theme by Vdoing | Copyright © 2022-2024 kinoko | MIT License | 粤ICP备2024165634号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式