版本依赖
- JDK 17
- Spring Boot 3.2.0
源码地址:Gitee
Spring Boot validation
spring-boot-starter-validation
是基于hibernate-validator
的实现,在Spring Boot项目中直接导入spring-boot-starter-validation
即可。
@Valid 和 @Validated 的区别
- 适用范围:
@Valid
是 Java 校验(JSR-303)的一部分,通常用于标注在方法参数或方法返回值上,以触发参数校验或返回结果的校验。@Validated
是 Spring 提供的,用于在方法级别进行校验。它支持分组校验,并且可以标注在类或方法上。
- 分组校验:
@Valid
支持分组校验,可以通过定义校验接口的不同分组来控制不同情况下的校验规则。@Validated
也支持分组校验,但是它的分组校验是通过在校验注解上指定分组来实现的。
- 校验方式:
@Valid
主要用于标注在方法参数或返回值上,触发 Bean Validation 校验。@Validated
主要用于方法级别的校验,并且支持 Spring 提供的校验方式,例如 Spring 的@NotEmpty
、@Range
等。
- 引入依赖:
- 使用
@Valid
需要引入 Java Bean Validation 的相关依赖,比如 Hibernate Validator。 - 使用
@Validated
需要引入 Spring 的相关依赖,它是 Spring 提供的一个校验框架。
- 使用
- 支持的校验注解:
@Valid
支持 JSR-303(Bean Validation)提供的注解,比如@NotNull
、@Size
等。@Validated
支持 Spring 提供的校验注解,例如@NotEmpty
、@Range
、@Email
等。
- 参数校验异常类:
@Valid
异常类为org.springframework.web.bind.MethodArgumentNotValidException
Validated
异常类为jakarta.validation.ConstraintViolationException
参数校验常用注解
@Null | 验证对象是否为null |
---|---|
@NotNull | 验证对象是否不为null, 无法查检长度为0的字符串 |
@NotBlank | 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. |
@NotEmpty | 检查约束元素是否为NULL或者是EMPTY. |
@AssertTrue | 验证 Boolean 对象是否为 true |
@AssertFalse | 验证 Boolean 对象是否为 false |
@Size(min=, max=) | 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length(min=, max=) | 验证注解的元素值长度在min和max区间内 |
@Past | 验证 Date 和 Calendar 对象是否在当前时间之前 |
@Future | 验证 Date 和 Calendar 对象是否在当前时间之后 |
@Pattern | 验证 String 对象是否符合正则表达式的规则 |
@Min | 验证 Number 和 String 对象是否大等于指定的值 |
@Max | 验证 Number 和 String 对象是否小等于指定的值 |
@DecimalMax | 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度 |
@DecimalMin | 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度 |
@Digits | 验证 Number 和 String 的构成是否合法 |
@Digits(integer=,fraction=) | 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。 |
@Range(min=, max=) | 验证注解的元素值在最小值和最大值之间 |
用例代码
导入依赖
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
定义对象参数
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
@Data
public class RequestVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "手机号码不能为空")
@Length(min = 11, max = 11, message = "手机号码格式错误")
private String phoneNumber;
}
定义测试Controller
package com.yiyan.study.controller;
import com.yiyan.study.model.RequestVO;
import com.yiyan.study.model.Result;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 参数校验及全局异常处理测试Controller
*/
@RestController
@RequestMapping("/ex")
@Validated
public class ExController {
@GetMapping("/param_ex")
public Result<RequestVO> paramEx(@Valid RequestVO request) {
return Result.success();
}
@GetMapping("/param_in_query")
public Result<String> paramInQuery(@NotBlank(message = "PARAM 不能为空") String param,
@Min(value = 1, message = "number不能小于1") Integer number) {
return Result.success();
}
}
Spring Boot 全局异常处理
参数注释
@RestControllerAdvice
: 能够捕获应用中所有控制器抛出的异常@ExceptionHandler
:方法中定义统一的返回格式,比如将异常信息封装成一个标准的 JSON 对象,并设置响应状态码等。@ResponseStatus
:定义响应的HttpStatus,如400,401,403等
自定义业务异常类
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
/**
* 自定义业务异常
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class BizException extends RuntimeException implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private final String errorCode;
/**
* 错误信息
*/
private final String errorMessage;
public BizException(String errorCode, String errorMessage) {
super(errorCode);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public BizException(String errorCode, String errorMessage, Throwable cause) {
super(errorCode, cause);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
自定义API统一返回结构
示例
package com.yiyan.study.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 接口统一返回
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Result<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 请求Status
*/
private String code;
/**
* 业务信息
*/
private String message;
/**
* 返回数据
*/
private T data;
/**
* Instantiates a new Result.
*/
public Result() {
}
public Result(String code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> success() {
return new Result<>("200", "请求成功", null);
}
public static <T> Result<T> success(String code, String message, T data) {
return new Result<>(code, message, data);
}
public static <T> Result<T> error(String code, String message, T data) {
return new Result<>(code, message, data);
}
public static <T> Result<T> error(String code, String message) {
return error(code, message, null);
}
}
自定义全局异常处理类
import com.yiyan.study.model.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* 全局异常处理
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理自定义的业务异常
*
* @param req the req
* @param e the e
* @return result
*/
@ExceptionHandler(value = BizException.class)
public Result<BizException> bizExceptionHandler(HttpServletRequest req, BizException e) {
log.error("[ {} ] {} 请求异常: {}", req.getMethod(), req.getRequestURL(), e.getErrorCode());
return Result.error(e.getErrorCode(), e.getErrorMessage());
}
/**
* 参数异常信息返回
*
* @param req the req
* @param e the e
* @return result
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result<Map<String, String>> methodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) {
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
log.error("[ {} ] {} 请求参数校验错误", req.getMethod(), req.getRequestURL());
Map<String, String> paramExceptionInfo = new TreeMap<>();
for (ObjectError objectError : allErrors) {
FieldError fieldError = (FieldError) objectError;
log.error("参数 {} = {} 校验错误:{}", fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage());
paramExceptionInfo.put(fieldError.getField(), fieldError.getDefaultMessage());
}
return Result.error(HttpStatus.BAD_REQUEST.toString(), "PARAM_EXCEPTION", paramExceptionInfo);
}
/**
* 参数异常信息返回
*
* @param req the req
* @param e the e
* @return result
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ConstraintViolationException.class)
public Result<String> constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException e) {
log.error("[ {} ] {} 请求参数校验错误", req.getMethod(), req.getRequestURL());
return Result.error(HttpStatus.BAD_REQUEST.toString(), "PARAM_EXCEPTION", e.getMessage());
}
/**
* 处理其他异常
*
* @param req the req
* @param e the e
* @return result
*/
@ExceptionHandler(value = Exception.class)
public Result<String> exceptionHandler(HttpServletRequest req, Exception e) {
log.error("[ {} ] {} 未定义异常: {}", req.getMethod(), req.getRequestURL(), e.getMessage());
return Result.error("500", e.getMessage());
}
}