• 优雅的springboot参数校验(一)


    目录

    前言

    1. 依赖引入

    2. 参数形式

    3. 常用到的约束注解

    4. 参数基础校验

    4.1 @RequestBody参数

    4.2 @RequestParam参数/@PathVariable参数

    4.3 异常统一处理

    5. 嵌套校验

    6. 分组校验


    前言

            在后端的接口开发过程,实际上每一个接口都或多或少有不同规则的参数校验,有一些是基础校验,如非空校验、长度校验、大小校验、格式校验;也有一些校验是业务校验,如学号不能重重复、手机号不能重复注册等;对于业务校验,是需要和数据库交互才能知道校验结果;对于参数的基础校验,是有一些共有特征可以抽象出来,可以做成一个通用模板(java就是一种面向对象的编程语言,还记得天天快要说烂问烂的面向对象的三大特性吗?)。基于实际场景的需要,java API中定义了一些Bean校验的规范标准(JSR303:validation-api),但是没有具体实现,不过hibernate validation和spring validation都提供了一些比较优秀的实现。如果在项目里,你还是像类似这样的方式来进行参数校验就太low了,活该加班到天亮(当然如果你所在公司目前仍然用统计代码量来考核你的工作,就算我没说,你可以继续使用这种方式)。

    1. @PostMapping("/add")
    2. public String add(Student student) {
    3. if (null == student) {
    4. throw new RuntimeException("学生不为空");
    5. }
    6. if ("".equals(student.getStuCode())) {
    7. throw new RuntimeException("学号不能为空");
    8. }
    9. if ("".equals(student.getStuName())) {
    10. throw new RuntimeException("学生姓名不能为空");
    11. }
    12. if (null == student.getTeacher()) {
    13. throw new RuntimeException("学生的老师的不能为空");
    14. }
    15. if ("".equals(student.getTeacher().getTecName())) {
    16. throw new RuntimeException("学生的老师的姓名不能为空");
    17. }
    18. if ("".equals(student.getTeacher().getSubject())) {
    19. throw new RuntimeException("学生的老师的所授科目不为能空");
    20. }
    21. return "success";
    22. }

    1. 依赖引入

            分享的这篇文章里的校验参数注解使用方法,我是在一个springboot项目里亲自重新测试验证过的,springboot的版本是2.3.9.RELEASE,另外也引入了关于参数校验的starter包,这样就不用额外去引关于参数校验的其他包了;

    1. org.springframework.boot
    2. spring-boot-starter
    3. org.springframework.boot
    4. spring-boot-starter-web
    5. org.springframework.boot
    6. spring-boot-starter-validation
    7. 2.3.9.RELEASE

    2. 参数形式

            在java项目中,前端请求后端的接口中,常用的请求类型主要是post和get。

            在post请求中,通常使用requestBody传递参数,即前端以json报文的格式传递到后端controller层,spring会把json报文自动映射到@RequestBody修饰的形参实例;

            在get请求中,通常使用requestParam/PathVariable传递参数,其中requestParam是指前端以key-value的形式把参数传递到后端,spring会把参数自动映射到@RequestParam修饰的形参数实例对象(@RequestParam可以,也可以没有,只要参数key与controller层方法内形参类型的属性名称可以对应的上);@PathVariable是指spring可以将请求URL中占位符参数绑定到controller层方法的形参上;

    3. 常用到的约束注解

    @Null

    被注释的元素必须为 null

    @NotNull

    被注释的元素必须不为 null

    @AssertTrue

    被注释的元素必须为 true

    @AssertFalse

    被注释的元素必须为 false

    @Min(value)

    被注释的元素必须是一个数字,其值必须大于等于指定的最小值

    @Max(value)

    被注释的元素必须是一个数字,其值必须小于等于指定的最大值

    @DecimalMin(value)

    被注释的元素必须是一个数字,其值必须大于等于指定的最小值

    @DecimalMax(value)

    被注释的元素必须是一个数字,其值必须小于等于指定的最大值

    @Size(max, min)

    被注释的元素的大小必须在指定的范围内

    @Digits (integer, fraction)

    被注释的元素必须是一个数字,其值必须在可接受的范围内

    @Past

    被注释的元素必须是一个过去的日期

    @Future

    被注释的元素必须是一个将来的日期

    @Pattern(value)

    被注释的元素必须符合指定的正则表达式

            Hibernate Validator 附加的 constraint

    注解

    作用

    @Email

    被注释的元素必须是电子邮箱地址

    @Length(min=, max=)

    被注释的字符串的大小必须在指定的范围内

    @NotEmpty

    被注释的字符串的必须非空

    @Range(min=, max=)

    被注释的元素必须在合适的范围内

    4. 参数基础校验

            参数的基础校验,通常是指的非空、长度、最大值、最小值、格式(数字、邮箱、正则)等这些场景的校验。

    4.1 @RequestBody参数

            1.在controller层的方法的形参数前面加一个@Valid或@Validated的注解;

            2.在用@RequestBody修饰的类的属性上加上约束注解,如@NotNull、@Length、@NotBlank;

            3.@RequestBody参数在触发校验规则时,会抛出MethodArgumentNotValidException,这里使用统一的异常处理机制来处理异常;

            总结:第1步的valid的作用就是一个标记,标明这个参数需要进行校验;第2步的约束注解的上注明校验的规则;第3步的统一校验机制是前后台请求后台接口时,如果校验参数的校验规则后会抛出异常,异常附带有约束注解上的提示信息,那么通过异常统一处理机制就可以统一处理异常信息,并以合适的方式返回给前台(所谓合适的方式是指异常信息的格式可以自行制定)。

    1. @PostMapping("/add")
    2. public Student add( @Valid@RequestBody Student student){
    3. System.out.println(student.getStuName());
    4. return student;
    5. }
    1. @Data
    2. public class Student {
    3. @NotNull(message = "学号不能为空")
    4. @Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
    5. private String stuCode;
    6. @NotNull(message = "姓名不能为空")
    7. @Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)")
    8. private String stuName;
    9. }

    4.2 @RequestParam参数/@PathVariable参数

            1.在controller层的控制类上添加@Validated注解;

            2.在controller层方法的校验参数上添加约束注解,如@NotNull、@Pattern;

            3.@RequestParam参数/@PathVariable参数在触发校验规则时,会抛出ConstraintViolationException类型的异常,所以在统一异常处理机制中添加对这种类型异常的处理机制;

    1. @RestController
    2. @RequestMapping("/student")
    3. @Validated
    4. public class StudentController {
    5. @GetMapping("/{sex}/info")
    6. public String getBySex(@PathVariable("sex") @Pattern(regexp = "boy||girl",message = "学生性别只能是boy或girl") String sex) {
    7. System.out.println("学生性别:" + sex);
    8. return "success";
    9. }
    10. @GetMapping("/getOne")
    11. public String getOne(@NotNull(message = "学生姓名不能为空") String stuName, @NotNull(message = "学生学号不能为空") String stuCode) {
    12. System.out.println("stuName:" + stuName + ",stuCode:" + stuCode);
    13. return "success";
    14. }
    15. }

    4.3 异常统一处理

    1. @RestControllerAdvice
    2. public class CommonExceptionHandler {
    3. /**
    4. * 用于捕获@RequestBody类型参数触发校验规则抛出的异常
    5. *
    6. * @param e
    7. * @return
    8. */
    9. @ExceptionHandler(value = MethodArgumentNotValidException.class)
    10. public String handleValidException(MethodArgumentNotValidException e) {
    11. StringBuilder sb = new StringBuilder();
    12. List allErrors = e.getBindingResult().getAllErrors();
    13. if (!CollectionUtils.isEmpty(allErrors)) {
    14. for (ObjectError error : allErrors) {
    15. sb.append(error.getDefaultMessage()).append(";");
    16. }
    17. }
    18. return sb.toString();
    19. }
    20. /**
    21. * 用于捕获@RequestParam/@PathVariable参数触发校验规则抛出的异常
    22. *
    23. * @param e
    24. * @return
    25. */
    26. @ExceptionHandler(value = ConstraintViolationException.class)
    27. public String handleConstraintViolationException(ConstraintViolationException e) {
    28. StringBuilder sb = new StringBuilder();
    29. Set> conSet = e.getConstraintViolations();
    30. for (ConstraintViolation con : conSet) {
    31. String message = con.getMessage();
    32. sb.append(message).append(";");
    33. }
    34. return sb.toString();
    35. }
    36. }

    5. 嵌套校验

            在实际项目中有这样一种场景,用来接收参数的类的属性字段也是一个对象,属性对象的字段也需要进行必要的参数校验,这个时候可以使用嵌套校验来解决这个问题,hibernate-validator提供了具体的解决方式。

            1.在controller层方法的形参数前添加@Validated注解,如果有分组校验的场景,则注明分组信息;如果校验不需要分组,可以不注明分组信息;

            2.在接收参数的类的属性是对象的字段上添加@Valide注解,这里需要注意的是一定是@Valid,不是@Validated,因为@Valid的实现是由hibernate-validator提供,有嵌套校验的能力,而@Validated是由spring-validation提供的具体实现方式,@Validated有分组校验的能力,但是没有嵌套校验的能力;(java API规范(JSR303)定义了Bean的校验标准validation-api,但是没有具体的实现,所以各有各的实现,在功能上也是有区别的)

            3.嵌套属性类上的约束注解的用法,与用来接收参数的对象属性上的约束注解的用法是一样的;

            总结:@Valid的实现是由hibernate-validator提供,有嵌套校验的能力,但是没有分组校验的能力,@Validated是由spring-validation提供的具体实现方式,@Validated有分组校验的能力,但是没有嵌套校验的能力,在使用的过程须特别注意,要根据实际需要进行剪裁。

    1. @PostMapping("/addStuaAndTeach")
    2. public String addStuaAndTeach(@Validated(AddStuAndTeach.class) @RequestBody Student student){
    3. System.out.println("学生的工号:"+student.getStuCode()+",学生的老师的姓名:"+student.getTeacher().getTecName());
    4. return "success";
    5. }
    1. @Data
    2. public class Student {
    3. @NotNull(message = "学生id不能为空",groups = QueryDetail.class)
    4. private Integer id;
    5. @NotNull(message = "学号不能为空",groups = AddStudent.class)
    6. @Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
    7. private String stuCode;
    8. @NotNull(message = "姓名不能为空",groups = AddStudent.class)
    9. @Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)",groups = AddStudent.class)
    10. private String stuName;
    11. @Valid
    12. @NotNull(message = "学生的老师不能为空",groups = AddStuAndTeach.class)
    13. private Teacher teacher;
    14. }
    1. @Data
    2. public class Teacher {
    3. @NotNull(message = "学生的老师姓名不能为空",groups = AddStuAndTeach.class)
    4. private String tecName;
    5. @NotNull(message = "学生的老师教授科目不能为空",groups = AddStuAndTeach.class)
    6. private String subject;
    7. }
    1. public interface AddStuAndTeach {
    2. }

    6. 分组校验

            在实际的项目中,可能多个方法使用同一个类来接收参数,但是不同的方法的校验规则又是不同的,这个时候就可以使用分组校验的方式来解决这个问题了,spring-validation提供了具体的实现方式。

            1.声明分组用的接口,比如添加和查询详情的时候,校验的规则肯定是不一样的,添加的时候一般不用传id,由后台自增长生成,查询详情的时候id是必须传的;

            2.在controller层方法的校验参数上添加@Validated参数,同时注解里要注明校验参数的分组信息;

            3.在校验参数的类上的线束注解上,也要注明校验参数的分组信息;

            总结:在接口的入口方法参数上、校验参数上都注明了分组的信息,那么接口被用的时候,就可以根据不同的分组信息执行不同约束注解的校验逻辑了,这个能力是spring-validation提供的,所以这种场景下,controller层方法的上注解要用@Validated,@Valid注解没有这种能力。

    1. //用于添加场景参数校验分组
    2. public interface AddStudent {
    3. }
    1. //用于查询详情场景参数校验分组
    2. public interface QueryDetail {
    3. }
    1. @PostMapping("/add")
    2. public Student add(@Validated(AddStudent.class) @RequestBody Student student) {
    3. System.out.println(student.getStuName());
    4. return student;
    5. }
    6. @PostMapping("/detail")
    7. public String detail(@Validated(QueryDetail.class)@RequestBody Student student){
    8. System.out.println("学生id:"+student.getId());
    9. return "success";
    10. }
    1. @Data
    2. public class Student {
    3. @NotNull(message = "学生id不能为空",groups = QueryDetail.class)
    4. private Integer id;
    5. @NotNull(message = "学号不能为空",groups = AddStudent.class)
    6. @Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
    7. private String stuCode;
    8. @NotNull(message = "姓名不能为空",groups = AddStudent.class)
    9. @Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)",groups = AddStudent.class)
    10. private String stuName;

    Springboot扩展点系列实现方式、工作原理集合:

    Springboot扩展点之ApplicationContextInitializer

    Springboot扩展点之BeanDefinitionRegistryPostProcessor

    Springboot扩展点之BeanFactoryPostProcessor

    Springboot扩展点之BeanPostProcessor

    Springboot扩展点之InstantiationAwareBeanPostProcessor

    Springboot扩展点之SmartInstantiationAwareBeanPostProcessor

    Springboot扩展点之ApplicationContextAwareProcessor

    Springboot扩展点之@PostConstruct

    Springboot扩展点之InitializingBean

    Springboot扩展点之DisposableBean

    Springboot扩展点之SmartInitializingSingleton

    Springboot核心功能工作原理:

    Springboot实现调度任务的工作原理

    Springboot事件监听机制的工作原理

  • 相关阅读:
    WebRTC janus安装编译教程
    WebAssembly 和javaScript的差异
    cloudenative1-2: go语言特性
    【超实用】3 分钟,教你用 Docker 部署一个 Python 应用!
    自然语言处理(NLP)—— 信息提取与文档分类
    前端测试工具 SwitchHosts + Charles + Proxy SwitchyOmega
    nvm详解(mac环境nvm安装步骤及踩坑问题)
    纯干货内容:关于ivx和mendix的对比 还在犹豫选择那个低代码平台的小伙伴看过来
    笔记本电脑windows10有线连接开无线热点方法已经成功
    CentOS系统利用kickstart自动生成工具通过图形化配置的方式生成ks.cfg文件
  • 原文地址:https://blog.csdn.net/fox9916/article/details/128167554