参数分为 路径参数、查询参数、请求体参数。
引入依赖
需要引入校验依赖:
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 | 字符串正则匹配 |
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;
}