在实际项目开发中,参数校验是保障接口安全性与数据正确性的第一道防线。而在 Spring Boot 中,我们通常会用 @Valid
(或 @Validated
)来触发校验,再结合全局异常处理器 @RestControllerAdvice
来实现统一的异常返回。
很多同学知道它们可以一起工作,但并不清楚 它们是怎么联系起来的。本文将带你从底层原理出发,彻底搞懂它们的配合机制。
一、@Valid 的作用
@Valid
是 JSR-303(Java Bean Validation) 规范中的注解,用于触发数据校验。
它一般用在 Controller 的方法参数上,例如:
java
@PostMapping("/register")
public ResponseResult<Void> register(@Valid @RequestBody UserDTO userDTO) {
// 业务逻辑... return ResponseResult.success();
}
DTO 示例:
java
@Data
public class UserDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Size(min = 6, message = "密码长度至少6位")
private String password;
}
当请求中传入非法参数时,Spring 会自动执行校验,并在不符合规则时抛出异常。
二、触发校验失败会发生什么?
当 @Valid
检测到参数不合法时,Spring 不会让 Controller 方法继续执行,而是抛出一个异常:
java
MethodArgumentNotValidException
举个例子,前端传入:
java
{
"username": "",
"email": "abc",
"password": "123"
}
Spring 框架内部的校验逻辑检测失败后,会抛出:
java
throw new MethodArgumentNotValidException(...)
此时方法体还没执行,异常会被直接上抛到 Spring MVC 的核心调度器 DispatcherServlet
。
三、Spring 是怎么"找到"全局异常处理器的?
Spring MVC 的核心组件 DispatcherServlet
会捕获 Controller 执行过程中的所有异常。
内部会执行类似以下逻辑(伪代码):
java
try {
handlerAdapter.handle(request, response, handler);
} catch (Exception ex) {
processDispatchResult(request, response, handler, mv, ex);
}
接着,它会去找有没有能处理这种异常的处理器:
-
当前 Controller 类里有没有定义:
java@ExceptionHandler(MethodArgumentNotValidException.class)
-
如果没有,再去扫描项目中所有带有:
java@ControllerAdvice 或 @RestControllerAdvice
于是,它就发现了你的全局异常处理器:
java
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseResult<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {}", message);
return ResponseResult.error(message);
}
}
四、全局异常处理器如何工作?
@RestControllerAdvice
可以理解为在所有 Controller 上方挂了一张"异常网"。
一旦某个接口抛出异常,Spring 会自动把异常交给这张网中的 @ExceptionHandler
方法处理。
上面的代码中,我们通过 getFieldErrors()
提取所有字段的错误信息,然后统一返回一个结构化的响应对象:
java
{
"code": 400,
"msg": "用户名不能为空, 邮箱格式不正确, 密码长度至少6位",
"data": null
}
相比 Spring 默认返回的 HTML 错误页面,这种写法对前后端分离项目更加友好。
五、@Valid 与 @RestControllerAdvice 的工作链路总结
整个链路可以概括为:
css
@Valid 参数校验
↓
校验失败抛出 MethodArgumentNotValidException
↓
异常上抛至 DispatcherServlet
↓
Spring MVC 寻找 @ExceptionHandler 处理方法
↓
匹配到 GlobalExceptionHandler 中的处理逻辑
↓
返回统一格式的 ResponseResult JSON 响应
一句话总结:
@Valid 负责"发现错误",
@RestControllerAdvice 负责"优雅处理错误"。
六、总结
-
@Valid
/@Validated
用于触发参数校验。 -
校验失败后,Spring 自动抛出异常。
-
@RestControllerAdvice
+@ExceptionHandler
捕获异常并返回统一格式。 -
无需手动调用,两者通过 Spring MVC 的异常机制自动关联。
七、推荐实践
-
统一返回结构(如
ResponseResult
) -
日志中记录详细的异常信息,方便定位问题
-
对常见异常(校验异常、登录异常、业务异常)分别编写处理方法
-
保持响应格式一致,前端可统一处理
结语:
很多时候,优秀的后端并不体现在写了多少业务逻辑,而在于"出错时的优雅"。
@Valid
与全局异常处理器的配合,就是后端代码优雅性的象征。