一、前言
在 Spring Boot 中,接口参数校验是一个常见的需求,用于确保接收到的数据符合预期的格式和规则。Spring Boot 提供了多种参数校验的方式,包括使用 JSR 303/380 规范的注解(如 Hibernate Validator)、自定义校验注解、AOP 拦截等。
二、JSR 是什么?
JSR 是 Java Specification Requests 的缩写,意思是 Java 规范提案。是指向 JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交 JSR,以向 Java 平台增添新的 API 和服务。JSR 已成为 Java 界的一个重要标准。JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
在Spring中提供了SpringValidation验证框架对参数的验证机制提供了@Validated(Spring’sJSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),结合BindingResult对象可以直接获取错误信息。
三、@Valid 与 @Validated区别?
1. 用法位置:
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上,支持嵌套检测。它通常与@RequestBody一起使用,以验证传入JSON或XML等格式的请求体。
@Validated:可以用在类型、方法和方法参数上,但不能直接用在成员属性(字段)上。它支持分组校验,允许你定义不同的校验组并在需要时应用它们。
2. 嵌套验证:
@Valid 支持嵌套验证,即如果待验证的类中包含其他需要验证的类,那么@Valid也会验证这些嵌套类的属性。
@Validated 默认情况下不支持嵌套验证,但可以通过配置来支持。
3. 分组校验:
@Validated 支持分组校验,允许你定义不同的校验组并在需要时应用它们。这对于在不同场景下应用不同的校验规则非常有用。
@Valid 不直接支持分组校验,但可以通过其他方式(如自定义校验器)实现类似的功能。
4. 与Spring框架的集成:
@Valid 是JSR 303/380规范的一部分,是标准的Java Bean验证(Bean Validation)API的注解。它可以在任何支持Bean Validation的环境中使用,包括Spring。
@Validated 是Spring框架提供的注解,提供了对JSR 303/380规范的扩展和增强。它除了支持标准的Bean Validation功能外,还提供了额外的功能,如分组校验和自定义校验器。
四、参数校验技巧及代码案例
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
准备一个入参实体类
package com.example.yddemo.validate;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
public class User {
@Min(value = 10, message = "ID不能小于 10", groups = V1.class)
@Min(value = 20, message = "ID不能小于 20", groups = V2.class)
private Long id;
@NotEmpty(message = "姓名必需填写")
private String name;
private String phone;
// 分组校验使用
public static interface V1 {}
public static interface V2 {}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
全局异常信息返回
package com.example.yddemo.validate;
import com.alibaba.fastjson.JSON;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {Exception.class})
public ResponseEntity<?> handleValidationExceptions(Exception ex) {
String message = "";
BindingResult bindingResult = null;
// 提取错误信息并返回给前端
// ...
if (ex instanceof ConstraintViolationException) {
ConstraintViolationException e = (ConstraintViolationException) ex;
message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
} else if (ex instanceof MethodArgumentNotValidException) {
bindingResult = ((MethodArgumentNotValidException) ex).getBindingResult();
Optional<List<String>> list = Optional.of(bindingResult.getAllErrors().stream().map(err -> err.getDefaultMessage()).collect(Collectors.toList()));
message = JSON.toJSONString(list.get());
}
return ResponseEntity.badRequest().body(message);
}
}
1. 单个参数校验
@RestController
@Validated
public class TestValidateController1 {
@GetMapping("/user/validate/one")
public String userValidateOne(@NotEmpty(message = "参数 Id 不能为空") String id) {
// 业务逻辑
return "success" + id;
}
}
2. 简单实体类参数校验
package com.example.yddemo.validate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
public class TestValidateController {
@GetMapping("/user/validate1")
public User userValidate1(@Valid @RequestBody User user) {
// 业务逻辑
return user;
}
}
3. 参数校验分组
当需要根据不同的业务场景执行不同的校验规则时,可以使用分组校验。
@GetMapping("/user/validate/v2")
public User userValidateV2(@Validated(User.V2.class) @RequestBody User user) {
// 业务逻辑
return user;
}
@GetMapping("/user/validate/v1")
public User userValidateV1(@Validated(User.V1.class) @RequestBody User user) {
// 业务逻辑
return user;
}
4. 嵌套参数校验
实际的工作中往往参数对象比这复杂的多,User 对象中可能还嵌套有其他的对象,这个其他的对象也可能需要参数的校验
@Valid
private Order order;
package com.example.yddemo.validate;
import javax.validation.constraints.NotEmpty;
public class Order {
@NotEmpty(message = "编码不能为空")
private String code;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
5. 自定义注解校验
当 JSR 303/380 提供的注解不满足需求时,可以自定义校验注解。
构造MobileValidator 验证器
package com.example.yddemo.validate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MobileValidator implements ConstraintValidator<MobileCheck, String> {
// 处理细节
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return false;
}
@Override
public void initialize(MobileCheck constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
}
注解
package com.example.yddemo.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码格式不正确")
public @interface MobileCheck {
String message() default "手机号码格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
在实体类DTO中使用
@MobileCheck
private String phone;
总结:以上是使用注解@Valid 与@Validated 实现SpringBoot 接口参数校验几种实用技巧,也可以使用AOP 拦截实现参数校验,下一篇我们在详细介绍。