Jtoss Jtoss
首页
  • 数据结构与算法

    • 数据结构与算法 - 概述
    • 数据结构与算法 - 复杂度分析
    • 数据结构 - 线性表
    • 算法 - 常见排序算法
  • 代码规范

    • 代码简洁之道
    • 阿里巴巴开发手册
    • 谷歌Java编程风格指南
  • 设计模式

    • 编写高质量代码概述
    • 面向对象
    • 设计原则
    • 设计模式-创建型
    • 设计模式-结构型
    • 设计模式-行为型(上)
    • 设计模式-行为型(下)
    • 浅析框架源码中的设计模式
    • 业务框架实战案例
  • MySQL 基础

    • MySQL - 数据库设计规范
    • MySQL - 必知必会
  • MySQL 进阶

    • MySQL - 基础架构
    • MySQL - InnoDB存储引擎
    • MySQL - InnoDB缓冲池
    • MySQL - 事务与锁
    • MySQL - 索引
    • MySQL - 查询执行计划
    • MySQL - 性能优化
  • Redis 系列

    • Redis入门 - 基础相关
    • Redis进阶 - 数据结构
    • Redis进阶 - 持久化RDB和AOF
    • Redis进阶 - 事件机制
    • Redis进阶 - 事务
    • Redis进阶 - 高可用高可扩展
    • Redis进阶 - 缓存问题
    • Redis进阶 - 性能调优
  • Java 基础

    • Java 基础 - 知识点
    • Java 基础 - 面向对象
    • Java 基础 - Q/A
  • Java 进阶 - 集合框架

    • Java 集合框架详解
  • Java 进阶 - 多线程与并发

    • Java 并发 - 理论基础
    • Java 并发 - 线程基础
    • Java 并发 - 各种锁
    • Java 并发 - 关键字 volatile
    • Java 并发 - 关键字 synchronized
    • JUC - CAS与原子操作
    • JUC - 锁核心类AQS
    • JUC - 锁接口和类简介
    • JUC - 并发容器简介
    • JUC - 通信工具类
    • JUC - Fork-Join框架
    • JUC - 线程池
  • Java 进阶 - JVM

    • JVM - 概述
    • JVM - 类加载机制
    • JVM - 内存结构
    • JVM - 垃圾回收机制
    • JVM - 性能调优
  • Maven系列

    • Maven基础知识
    • Maven项目构建
    • Maven多模块配置
  • Spring 框架

    • Spring 框架 - 框架介绍
    • Spring 框架 - IOC详解
    • Spring 框架 - AOP详解
    • Spring 框架 - SpringMVC详解
  • Spring Boot 系列

    • Spring Boot - 开发入门
    • Spring Boot - 接口相关
  • Spring Cloud 系列
  • Mybatis 系列

    • Mybatis - 总体框架设计
    • Mybatis - 初始化基本过程
    • Mybatis - sqlSession执行过程
    • Mybatis - 插件机制
    • Mybatis - 事务管理机制
    • Mybatis - 缓存机制
  • 业务常见问题

    • Java 业务开发常见错误(一)
    • Java 业务开发常见错误(二)
    • Java 业务开发常见错误(三)
    • Java 业务开发常见错误(四)
    • Java 业务开发常见错误(五)
    • Java 业务开发常见错误(六)
  • IDEA系列

    • IDEA 2021开发环境配置
    • IDEA 快捷键
  • Git系列

    • git status中文乱码
  • 其他

    • Typora+Picgo 自动上传图片
    • hsdis 和 jitwatch
  • 实用技巧
  • 收藏
  • 摄影
  • 学习
  • 标签
  • 归档

Jason Huang

后端程序猿
首页
  • 数据结构与算法

    • 数据结构与算法 - 概述
    • 数据结构与算法 - 复杂度分析
    • 数据结构 - 线性表
    • 算法 - 常见排序算法
  • 代码规范

    • 代码简洁之道
    • 阿里巴巴开发手册
    • 谷歌Java编程风格指南
  • 设计模式

    • 编写高质量代码概述
    • 面向对象
    • 设计原则
    • 设计模式-创建型
    • 设计模式-结构型
    • 设计模式-行为型(上)
    • 设计模式-行为型(下)
    • 浅析框架源码中的设计模式
    • 业务框架实战案例
  • MySQL 基础

    • MySQL - 数据库设计规范
    • MySQL - 必知必会
  • MySQL 进阶

    • MySQL - 基础架构
    • MySQL - InnoDB存储引擎
    • MySQL - InnoDB缓冲池
    • MySQL - 事务与锁
    • MySQL - 索引
    • MySQL - 查询执行计划
    • MySQL - 性能优化
  • Redis 系列

    • Redis入门 - 基础相关
    • Redis进阶 - 数据结构
    • Redis进阶 - 持久化RDB和AOF
    • Redis进阶 - 事件机制
    • Redis进阶 - 事务
    • Redis进阶 - 高可用高可扩展
    • Redis进阶 - 缓存问题
    • Redis进阶 - 性能调优
  • Java 基础

    • Java 基础 - 知识点
    • Java 基础 - 面向对象
    • Java 基础 - Q/A
  • Java 进阶 - 集合框架

    • Java 集合框架详解
  • Java 进阶 - 多线程与并发

    • Java 并发 - 理论基础
    • Java 并发 - 线程基础
    • Java 并发 - 各种锁
    • Java 并发 - 关键字 volatile
    • Java 并发 - 关键字 synchronized
    • JUC - CAS与原子操作
    • JUC - 锁核心类AQS
    • JUC - 锁接口和类简介
    • JUC - 并发容器简介
    • JUC - 通信工具类
    • JUC - Fork-Join框架
    • JUC - 线程池
  • Java 进阶 - JVM

    • JVM - 概述
    • JVM - 类加载机制
    • JVM - 内存结构
    • JVM - 垃圾回收机制
    • JVM - 性能调优
  • Maven系列

    • Maven基础知识
    • Maven项目构建
    • Maven多模块配置
  • Spring 框架

    • Spring 框架 - 框架介绍
    • Spring 框架 - IOC详解
    • Spring 框架 - AOP详解
    • Spring 框架 - SpringMVC详解
  • Spring Boot 系列

    • Spring Boot - 开发入门
    • Spring Boot - 接口相关
  • Spring Cloud 系列
  • Mybatis 系列

    • Mybatis - 总体框架设计
    • Mybatis - 初始化基本过程
    • Mybatis - sqlSession执行过程
    • Mybatis - 插件机制
    • Mybatis - 事务管理机制
    • Mybatis - 缓存机制
  • 业务常见问题

    • Java 业务开发常见错误(一)
    • Java 业务开发常见错误(二)
    • Java 业务开发常见错误(三)
    • Java 业务开发常见错误(四)
    • Java 业务开发常见错误(五)
    • Java 业务开发常见错误(六)
  • IDEA系列

    • IDEA 2021开发环境配置
    • IDEA 快捷键
  • Git系列

    • git status中文乱码
  • 其他

    • Typora+Picgo 自动上传图片
    • hsdis 和 jitwatch
  • 实用技巧
  • 收藏
  • 摄影
  • 学习
  • 标签
  • 归档
  • Maven系列

  • Spring 框架

  • Spring Boot

    • SpringBoot - Logback
    • SpringBoot - 热部署
    • SpringBoot - 常用注解
    • SpringBoot - 接口统一返回格式
    • SpringBoot - 接口参数校验
      • 为什么需要参数校验
      • SpringBoot中集成参数校验
        • 第一步,引入依赖
        • 第二步,定义要参数校验的实体类
        • 第三步,定义校验类进行测试
        • 第四步,体验效果
      • 参数异常加入全局异常处理器
        • 体验效果
      • 自定义参数校验
        • 第一步,创建自定义注解
        • 第二步,自定义校验逻辑
        • 第三步,在字段上增加注解
        • 第四步,体验效果
      • 分组校验
        • 第一步:定义分组接口
        • 第二步,在模型中给参数分配分组
        • 第三步,给需要参数校验的方法指定分组
        • 第四步,体验效果
      • 小结
      • @Validated 和 @Valid什么区别?
      • @RestControllerAdvice 简化参数
      • 示例源码
      • 参考
    • SpringBoot - 接口文档之Swagger
    • SpringBoot - 接口文档之Smart-Doc
    • SpringBoot - 接口版本
  • Spring Cloud

  • Spring
  • Spring Boot
Jason
目录

SpringBoot - 接口参数校验

# SpringBoot - 接口参数校验

# 为什么需要参数校验

在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数做校验,例如登录的时候需要校验用户名密码是否为空,创建用户的时候需要校验邮件、手机号码格式是否准确。靠代码对接口参数一个个校验的话就太繁琐了,代码可读性极差。

Validator框架就是为了解决开发人员在开发的时候少写代码,提升开发效率;Validator专门用来进行接口参数校验,例如常见的必填校验,email格式校验,用户名必须位于6到12之间 等等...

Validator校验框架遵循了JSR-303验证规范(参数校验规范), JSR是 Java Specification Requests的缩写。

接下来我们看看在SpringbBoot中如何集成参数校验框架。

# SpringBoot中集成参数校验

# 第一步,引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9

注:从 springboot-2.3开始,校验包被独立成了一个 starter组件,所以需要引入validation和web,而 springboot-2.3之前的版本只需要引入 web 依赖就可以了。

# 第二步,定义要参数校验的实体类

/**
 * @author jason
 */
@Data
public class ValidVO {
    private String id;

    @Length(min = 6, max = 12, message = "appId长度必须位于6到12之间")
    private String appId;

    @NotBlank(message = "名字为必填项")
    private String name;

    @Email(message = "请填写正确的邮箱地址")
    private String email;

    private String sex;

    @NotEmpty(message = "级别不能为空")
    private String level;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在实际开发中对于需要校验的字段都需要设置对应的业务提示,即message属性。

常见的约束注解如下:

注解 功能
@AssertFalse 可以为null,如果不为null的话必须为false
@AssertTrue 可以为null,如果不为null的话必须为true
@DecimalMax 设置不能超过最大值
@DecimalMin 设置不能超过最小值
@Digits 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
@Future 日期必须在当前日期的未来
@Past 日期必须在当前日期的过去
@Max 最大不得超过此最大值
@Min 最大不得小于此最小值
@NotNull 不能为null,可以是空
@Null 必须为null
@Pattern 必须满足指定的正则表达式
@Size 集合、数组、map等的size()值必须在指定范围内
@Email 必须是email格式
@Length 长度必须在指定范围内
@NotBlank 字符串不能为null,字符串trim()后也不能等于“”
@NotEmpty 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“”
@Range 值必须在指定范围内
@URL 必须是一个URL

注:此表格只是简单的对注解功能的说明,并没有对每一个注解的属性进行说明;可详见源码。

# 第三步,定义校验类进行测试

@RestController
@Slf4j
@Validated
public class ValidController {
    @PostMapping("/valid/test1")   
    public String test1(@Validated @RequestBody ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test1 valid success";
    }

    @PostMapping(value = "/valid/test2")
    public String test2(@Validated ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test2 valid success";
    }

    @PostMapping(value = "/valid/test3")
    public String test3(@Email String email){
        log.info("email is {}", email);
        return "email valid success";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

这里我们先定义三个方法test1,test2,test3,test1使用了 @RequestBody注解,用于接受前端发送的json数据,test2模拟表单提交,test3模拟单参数提交。注意,当使用单参数校验时需要在Controller上加上@Validated注解,否则不生效。

# 第四步,体验效果

  1. 调用test1方法,提示的是org.springframework.web.bind.MethodArgumentNotValidException异常
POST http://localhost:8096/valid/test1
Content-Type: application/json

{
  "id": 1,
  "level": "12",
  "email": "47693899",
  "appId": "ab1c"
}

{
    "code": 500,
    "message": "Validation failed for argument [0] in public java.lang.String cn.jtoss.springbootvalidation.controller.DemoController.test1(cn.jtoss.springbootvalidation.domain.ValidVO) with 3 errors: [Field error in object 'validVO' on field 'email': rejected value [47693899]; codes [Email.validVO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@6a04bdae,.*]; default message [请填写正确的邮箱地址]] [Field error in object 'validVO' on field 'name': rejected value [null]; codes [NotBlank.validVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.name,name]; arguments []; default message [name]]; default message [名字为必填项]] [Field error in object 'validVO' on field 'appId': rejected value [ab1c]; codes [Length.validVO.appId,Length.appId,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.appId,appId]; arguments []; default message [appId],12,6]; default message [appId长度必须位于6到12之间]] ",
    "data": null,
    "timestamp": 1682062026515
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 调用test2方法,提示的是org.springframework.validation.BindException异常
POST http://localhost:8096/valid/test2
Content-Type: application/x-www-form-urlencoded

id=1&level=12&email=476938977&appId=ab1c
{
    "code": 500,
    "message": "org.springframework.validation.BeanPropertyBindingResult: 3 errors\nField error in object 'validVO' on field 'appId': rejected value [ab1c]; codes [Length.validVO.appId,Length.appId,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.appId,appId]; arguments []; default message [appId],12,6]; default message [appId长度必须位于6到12之间]\nField error in object 'validVO' on field 'name': rejected value [null]; codes [NotBlank.validVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.name,name]; arguments []; default message [name]]; default message [名字为必填项]\nField error in object 'validVO' on field 'email': rejected value [47693899]; codes [Email.validVO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@6a04bdae,.*]; default message [请填写正确的邮箱地址]",
    "data": null,
    "timestamp": 1682062204610
}
1
2
3
4
5
6
7
8
9
10
  1. 调用test3方法,提示的是javax.validation.ConstraintViolationException异常
POST http://localhost:8096/valid/test3
Content-Type: application/x-www-form-urlencoded

email=476938977

{
    "code": 500,
    "message": "test3.email: 不是一个合法的电子邮件地址",
    "data": null,
    "timestamp": 1682062267002
}
1
2
3
4
5
6
7
8
9
10
11

通过加入 Validator校验框架可以帮助我们自动实现参数的校验。

# 参数异常加入全局异常处理器

虽然我们之前定义了全局异常拦截器,也看到了拦截器确实生效了,但是 Validator校验框架返回的错误提示太臃肿了,不便于阅读,为了方便前端提示,我们需要将其简化一下。

直接修改之前定义的 RestExceptionHandler,单独拦截参数校验的三个异常:javax.validation.ConstraintViolationException,org.springframework.validation.BindException,org.springframework.web.bind.MethodArgumentNotValidException,代码如下:

/**
 * 抓取validation校验异常
 */
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<ResultResponse<String>> handleValidatedException(Exception e) {
    ResultResponse<String> response = null;

    if (e instanceof MethodArgumentNotValidException) {
        MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
        String message = ex.getBindingResult().getAllErrors().stream()
                .map(ObjectError::getDefaultMessage)
                .collect(Collectors.joining("; "));
        response = ResultResponse.createFailureResponse(HttpStatus.BAD_REQUEST.value(), message, null);
    } else if (e instanceof ConstraintViolationException) {
        ConstraintViolationException ex = (ConstraintViolationException) e;
        String message = ex.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining("; "));
        response = ResultResponse.createFailureResponse(HttpStatus.BAD_REQUEST.value(), message, null);
    } else if (e instanceof BindException) {
        BindException ex = (BindException) e;
        String message = ex.getAllErrors().stream()
                .map(ObjectError::getDefaultMessage)
                .collect(Collectors.joining("; "));
        response = ResultResponse.createFailureResponse(HttpStatus.BAD_REQUEST.value(), message, null);
    }

    log.error("参数校验异常:{}", response.getMessage());
    return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
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

# 体验效果

POST http://localhost:8096/valid/test1
Content-Type: application/json

{
  "id": 1,
  "level": "12",
  "email": "47693899",
  "appId": "ab1c"
}

{
    "code": 400,
    "message": "请填写正确的邮箱地址; 名字为必填项; appId长度必须位于6到12之间",
    "data": null,
    "timestamp": 1682062446709
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

是不是感觉清爽多了?

# 自定义参数校验

虽然Spring Validation 提供的注解基本上够用,但是面对复杂的定义,我们还是需要自己定义相关注解来实现自动校验。

比如上面实体类中的sex性别属性,只允许前端传递传 M,F 这2个枚举值,如何实现呢?

# 第一步,创建自定义注解

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumString.List.class)
@Documented
@Constraint(validatedBy = EnumStringValidator.class)//标明由哪个类执行校验逻辑
public @interface EnumString {
    String message() default "value not in enum values.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    /**
     * @return date must in this value array
     */
    String[] value();

    /**
     * Defines several {@link EnumString} annotations on the same element.
     *
     * @see EnumString
     */
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @interface List {

        EnumString[] value();
    }
}
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

# 第二步,自定义校验逻辑

public class EnumStringValidator implements ConstraintValidator<EnumString, String> {
    private List<String> enumStringList;

    @Override
    public void initialize(EnumString constraintAnnotation) {
        enumStringList = Arrays.asList(constraintAnnotation.value());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(value == null){
            return true;
        }
        return enumStringList.contains(value);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 第三步,在字段上增加注解

@EnumString(value = {"F","M"}, message="性别只允许为F或M")
private String sex;
1
2

# 第四步,体验效果

POST http://localhost:8096/valid/test2
Content-Type: application/x-www-form-urlencoded

id=1&name=javadaily&level=12&email=476938977@qq.com&appId=ab1cdddd&sex=N

{
    "code": 400,
    "message": "性别只允许为F或M",
    "data": null,
    "timestamp": 1682062972079
}
1
2
3
4
5
6
7
8
9
10
11

# 分组校验

一个VO对象在新增的时候某些字段为必填,在更新的时候又非必填。如上面的 ValidVO中 id 和 appId 属性在新增操作时都是非必填,而在编辑操作时都为必填,name在新增操作时为必填,面对这种场景你会怎么处理呢?

在实际开发中我见到很多同学都是建立两个VO对象,ValidCreateVO,ValidEditVO来处理这种场景,这样确实也能实现效果,但是会造成类膨胀,而且极其容易被开发老鸟们嘲笑。

其实 Validator校验框架已经考虑到了这种场景并且提供了解决方案,就是分组校验,只不过很多同学不知道而已。要使用分组校验,只需要三个步骤:

# 第一步:定义分组接口

public interface ValidGroup extends Default {
  
    interface Crud extends ValidGroup{
        interface Create extends Crud{

        }

        interface Update extends Crud{

        }

        interface Query extends Crud{

        }

        interface Delete extends Crud{

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

这里我们定义一个分组接口ValidGroup让其继承 javax.validation.groups.Default,再在分组接口中定义出多个不同的操作类型,Create,Update,Query,Delete。至于为什么需要继承Default我们稍后再说。

# 第二步,在模型中给参数分配分组

@Data
@ApiModel(value = "参数校验类")
public class ValidVO {
    @ApiModelProperty("ID")
    @Null(groups = ValidGroup.Crud.Create.class)
    @NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
    private String id;

    @Null(groups = ValidGroup.Crud.Create.class)
    @NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
    @ApiModelProperty(value = "应用ID",example = "cloud")
    private String appId;

    @ApiModelProperty(value = "名字")
    @NotBlank(groups = ValidGroup.Crud.Create.class,message = "名字为必填项")
    private String name;
  
  	@ApiModelProperty(value = "邮箱")
    @Email(message = "请填写正取的邮箱地址")
    privte String email;

   	...

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

给参数指定分组,对于未指定分组的则使用的是默认分组。

# 第三步,给需要参数校验的方法指定分组

@RestController
@Api("参数校验")
@Slf4j
@Validated
public class ValidController {

    @ApiOperation("新增")
    @PostMapping(value = "/valid/add")
    public String add(@Validated(value = ValidGroup.Crud.Create.class) ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test3 valid success";
    }


    @ApiOperation("更新")
    @PostMapping(value = "/valid/update")
    public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test4 valid success";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这里我们通过 value属性给 add()和 update()方法分别指定Create和Update分组。

# 第四步,体验效果

POST http://localhost:8096/valid/add
Content-Type: application/x-www-form-urlencoded

name=javadaily&level=12&email=476938977@qq.com&sex=F
1
2
3
4

在Create时我们没有传递id和appId参数,校验通过。

当我们使用同样的参数调用update方法时则提示参数校验错误。

{
    "code": 400,
    "message": "应用ID不能为空; ID不能为空",
    "data": null,
    "timestamp": 1682063423156
}
1
2
3
4
5
6

由于email属于默认分组,而我们的分组接口 ValidGroup已经继承了 Default分组,所以也是可以对email字段作参数校验的。如:

POST http://localhost:8096/valid/add
Content-Type: application/x-www-form-urlencoded

name=javadaily&level=12&email=476938977&sex=F

{
    "code": 400,
    "message": "请填写正确的邮箱地址",
    "data": null,
    "timestamp": 1682063489927
}
1
2
3
4
5
6
7
8
9
10
11

当然如果你的ValidGroup没有继承Default分组,那在代码属性上就需要加上 @Validated(value = {ValidGroup.Crud.Create.class, Default.class}才能让 email字段的校验生效。

# 小结

参数校验在实际开发中使用频率非常高,但是很多同学还只是停留在简单的使用上,像分组校验,自定义参数校验这2个高阶技巧基本没怎么用过,经常出现譬如建立多个VO用于接受Create,Update场景的情况,很容易被老鸟被所鄙视嘲笑,希望大家好好掌握。

# @Validated 和 @Valid什么区别?

在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

  • 分组

    @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。@Valid:作为标准JSR-303规范,还没有吸收分组的功能。

  • 注解地方

    @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

    @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上

  • 嵌套类型

    比如下面的address是ValidVO的一个嵌套属性, 只能用@Valid

    /**
     * @author jason
     */
    @Data
    public class ValidVO {
        private String id;
    
        @Length(min = 6, max = 12, message = "appId长度必须位于6到12之间")
        private String appId;
    
        @NotBlank(message = "名字为必填项")
        private String name;
    
        @Email(message = "请填写正确的邮箱地址")
        private String email;
    
        private String sex;
    
        @NotEmpty(message = "级别不能为空")
        private String level;
        
        @Valid
        private AddressVO address;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

# @RestControllerAdvice 简化参数

除了通过@ExceptionHandler注解用于全局异常的处理之外,@RestControllerAdvice还有两个用法:

  1. @InitBinder注解

    用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;

    比如,在@ControllerAdvice注解的类中添加如下方法,来统一处理日期格式的格式化

    @InitBinder
    public void handleInitBinder(WebDataBinder dataBinder){
        dataBinder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
    }
    
    1
    2
    3
    4
    5

    Controller中传入参数(string类型)自动转化为Date类型

    @GetMapping("testDate")
    public Date processApi(Date date) {
        return date;
    }
    
    1
    2
    3
    4
  2. @ModelAttribute注解

    用来预设全局参数,比如最典型的使用Spring Security时将添加当前登录的用户信息(UserDetails)作为参数。

    @ModelAttribute("currentUser")
    public UserDetails modelAttribute() {
        return (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
    
    1
    2
    3
    4

    所有controller类中requestMapping方法都可以直接获取并使用currentUser

    @PostMapping("saveSomething")
    public ResponseEntity<String> saveSomeObj(@ModelAttribute("currentUser") UserDetails operator) {
        // 保存操作,并设置当前操作人员的ID(从UserDetails中获得)
        return ResponseEntity.success("ok");
    }
    
    1
    2
    3
    4
    5

@RestControllerAdvice 原理介绍可见这篇文章:https://pdai.tech/md/spring/springboot/springboot-x-interface-exception.html#%E8%BF%9B%E4%B8%80%E6%AD%A5%E7%90%86%E8%A7%A3

# 示例源码

  • https://github.com/hengwen/spring-demo/tree/main/springbootvalidation

# 参考

  • https://juejin.cn/post/7000641635730063396
  • https://pdai.tech/md/spring/springboot/springboot-x-interface-exception.html
#Spring Boot#参数校验
上次更新: 2023-04-21
SpringBoot - 接口统一返回格式
SpringBoot - 接口文档之Swagger

← SpringBoot - 接口统一返回格式 SpringBoot - 接口文档之Swagger→

最近更新
01
开始
01-09
02
AI工具分享
01-09
03
AI 导读
01-07
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Jason Huang | 闽ICP备2025088096号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式