springboot学习第3期 - 参数校验

参数分为 路径参数、查询参数、请求体参数。

引入依赖

需要引入校验依赖:

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

常用的校验注解包含:

注解 作用类型 说明
@NotNull 所有引用类型 对象不能为null
@NotEmpty String 字符串不能为空字符串
@NotBlank String 字符串不能为空格和空字符串
@Length(min=,max=) String 字符串的长度限制
@Size(min=, max=) 集合类型 集合类型的元素个数限制
@Min / @Max Integer 数字的大小限制
@Pattern(regexp="") String 字符串正则匹配
@Email String 字符串为邮件格式

路径参数

校验路径参数,除了变量前面加校验注解外,类上还需要加 @Validated 注解,

java 复制代码
@RestController
@RequestMapping("/api/hello")
@Validated
public class HelloController {

    @GetMapping("/{name}")
    public ApiResponse<String> hello(@PathVariable @Length(min = 2, max = 10) String name) {
        return ApiResponse.success("Hello World!" + name);
    }

}

postman 接口调试:

后端异常信息:

查询参数

查询参数和路径参数一样。

同样是变量前面添加校验注解,类上添加 @Validated 注解。

java 复制代码
@RestController
@RequestMapping("/api/hello")
@Validated
public class HelloController {

    @GetMapping
    public ApiResponse<String> hello(@RequestParam @Length(min = 2, max = 10) String name) {
        return ApiResponse.success("Hello World!" + name);
    }

}

查询参数封装

这种形式类上不需要加 @Validated。

封装的对象前面需要添加 @Valid 注解,这里也可以换成 @Validated 注解,如果需要分组校验,则使用 @Validated 注解,但实际开发中,很少会使用分组校验。

java 复制代码
@RestController
@RequestMapping("/api/hello")
public class HelloController {

    @GetMapping
    public ApiResponse<String> hello(@Valid Param param) {
        return ApiResponse.success("Hello World!" + param.getName());
    }

}
java 复制代码
@Data
public class Param {
    @Length(min = 2, max = 50)
    private String name;
    private String age;
}

postman接口调试:

后端服务器异常信息:

请求体参数

请求体参数的校验和查询参数封装一样的。

java 复制代码
@RestController
@RequestMapping("/api/hello")
public class HelloController {

    @PostMapping
    public ApiResponse<String> hello(@Valid @RequestBody Request request) {
        return ApiResponse.success("Hello World!" + request.getName());
    }
}

而封装的属性前面,添加具体的校验注解:

java 复制代码
@Data
public class Request {

    @Length(min = 2, max = 100)
    private String name;

    @JsonProperty("my_age")
    private String myAge;
}

使用postman调试接口:

服务端抛出的异常信息,抛出的异常是 MethodArgumentNotValidException:

包含自定义对象属性

请求体对象包含自定义对象 Person, 并且 Person 属性包含校验注解。

则Person变量前面需要添加 @Valid 注解。

java 复制代码
@Data
public class Request {

    @Length(min = 2, max = 100)
    private String name;

    @Valid   // 关键
    private Person person;

//  集合嵌套自定义对象,也是如此
//    @Valid
//    private List<Person> person;

//  或者这种形式也是可以的
// 	  private List<@Valid Person> person;
}
java 复制代码
@Data
public class Person {

    @Length(min = 4, max = 10)
    private String name;
}

集合普通元素

java 复制代码
@Data
public class Request {

    @Length(min = 2, max = 100)
    private String name;

    private List<@Length(min=2) String> tags;
}

tags 的每个元素都会应用 @Length(min=2) 注解校验。

全局异常处理

通过前面的postman接口调试看出,即使校验不通过,响应信息晦涩难懂,客户端根本不知道是因为什么而接口调不通。这次通过全局异常处理机制,优化校验不通过的提示信息。

通过前面三种参数的校验,可以知道如果校验不通过,会返回两种类型的异常,MethodArgumentNotValidException 和 ConstraintViolationException,主要在响应之前全局拦截这两个异常,将异常信息提取出来,友好的返回给客户端即可。

全局异常处理器使用注解 @RestControllerAdvice + @ExceptionHandler,当程序中抛出指定的异常信息,就会被拦截到,经过自定义处理,然后返回给客户端。

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<List<String>> handleValidException(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        return ApiResponse.error(400, "参数校验失败", errors);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<List<String>> handleViolationException(ConstraintViolationException ex) {
        List<String> errors = ex.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.toList());
        return ApiResponse.error(400, "参数校验失败", errors);
    }
}

前面忘记提到,所有的校验注解都有个 message 属性, 当校验失败的时候,会提示 message 信息,而上述全局异常处理器,就是收集所有校验注解的message,返回给客户端

java 复制代码
@RestController
@RequestMapping("/api/hello")
@Validated
public class HelloController {

    @PostMapping("/{name}")
    public ApiResponse<String> hello(@PathVariable @Length(min = 2, max = 10, message = "Name length should be between 2 and 10") String name,
                                     @RequestParam @Min(value = 18, message = "Age should be between 0 and 100") String age) {
        return ApiResponse.success("Hello World!" + name);
    }

}

可以为参数校验失败设置特定的code, 比如 400001(400 + 001),然后前端响应拦截器判定到是400001,则展示所有的异常信息。关于响应码的设计,这是个大学问。

自定义校验注解

比如自定义一个校验电话号码的检验注解。

java 复制代码
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手机号格式错误";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
java 复制代码
public class PhoneValidator implements ConstraintValidator<Phone, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.matches("^1[3-9]\\d{9}$");
    }
}

使用:

java 复制代码
@Data
public class Request {

    @Length(min = 2, max = 100, message = "name 长度应该在 2 到 100 之间")
    private String name;

    @Phone(message = "phone 格式不正确")
    private String phone;
}
相关推荐
程序员张311 小时前
Mybatis条件判断某属性是否等于指定字符串
java·spring boot·mybatis
invicinble12 小时前
从逻辑层面理解Shiro在JVM中是如何工作的
jvm·spring boot
好好研究15 小时前
SpringBoot注解的作用
java·spring boot·spring
Libby博仙15 小时前
Spring Boot 条件化注解深度解析
java·spring boot·后端
子非鱼92116 小时前
SpringBoot快速上手
java·spring boot·后端
我爱娃哈哈16 小时前
SpringBoot + XXL-JOB + Quartz:任务调度双引擎选型与高可用调度平台搭建
java·spring boot·后端
Coder_Boy_16 小时前
基于SpringAI的在线考试系统-AI智能化拓展
java·大数据·人工智能·spring boot
内存不泄露16 小时前
二手物品交易平台
spring boot·小程序·django
n***333516 小时前
TCP/IP协议栈深度解析技术文章大纲
java·spring boot