概述
在构建基于Spring Boot的高效RESTful API时,参数的有效性和完整性是保障系统健壮性的重要一环。Java Bean Validation规范为此提供了一套强大的验证机制,通过一系列注解如@NotNull、@Size和@Pattern等,开发者可以方便地对请求参数进行细致严格的校验。
- 关于 全局异常统一处理 的原理和完整实现逻辑,请参考文章:《SpringBoot 全局异常统一处理(AOP):@RestControllerAdvice + @ExceptionHandler + @ResponseStatus》
- 本文仅详细解析 ConstraintViolationException 的异常处理;其他类型异常的原理和处理方法,请参阅本文所在专栏内的其他文章。
异常基础与应用场景
当我们在Controller层接收HTTP请求,并将带有Bean Validation注解的对象作为方法参数时,如果参数对象使用了来自 javax.validation 包下的注解(如@NotNull、@Size、@Pattern等)进行数据校验,在执行业务逻辑或持久化操作之前,Hibernate Validator(作为Java Bean Validation规范的实现库)会自动进行验证;如果对象的某个属性值不满足这些注解所定义的约束条件,会抛出
捕获并处理ConstraintViolationException可以帮助开发者向客户端返回具体的错误信息,指出是哪些字段不符合验证规则,这对于API开发尤为重要。在Spring Boot应用中,可以创建一个全局异常处理器方法,用@ExceptionHandler注解来捕获并统一处理此类异常。
ConstraintViolationException 校验生效条件
在测试时发现,并非所有参数校验异常都会抛出 ConstraintViolationException;很多的情况下,会抛出 BindException。那么,什么情况下校验失败,会抛出 ConstraintViolationException 呢?
需要满足如下3个条件:
- Controller上添加 @Validated 注解;
- 需要校验的接口入参, 直接 映射到控制器方法参数;
- 参数前添加了校验规则注解(比如 @Pattern)。
这里说的
异常处理代码
核心代码
在Spring Boot应用中,我们可以利用
@RestControllerAdvice public class GlobalExceptionHandler { /** * 参数校验异常:直接参数校验。 * <p> * 此校验适用的接口,需要满足如下条件: * 1. 需要校验的参数“直接”作为接口方法的参数; * 2. Controller上添加 @Validated 注解; * 3. 参数前添加了校验规则注解(比如 @Pattern)。 * <p> * 示例:删除用户接口 */ @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public Result<String> handle(ConstraintViolationException e, HandlerMethod handlerMethod) { logInfo(e, handlerMethod); String userMessage = UserTipGenerator.getUserMessage(e); String format = "ConstraintViolationException(约束违反异常)(错误数量:%s):%s"; String errorMessage = String.format(format, e.getConstraintViolations().size(), e.getMessage()); return Result.fail(userMessage, String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMessage); } // 其他异常处理和打印异常信息... }
相关代码:UserTipGenerator
public static String getUserMessage(ConstraintViolationException e) { Set<ConstraintViolation<?>> violations = e.getConstraintViolations(); if (CollectionUtils.isEmpty(violations)) { return ""; } return violations.stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(";")); }
测试案例
示意图
测试代码
package com.example.web.user.controller; import com.example.web.model.vo.UserVO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.constraints.Pattern; @Validated @RestController @RequestMapping("users") @Tag(name = "用户管理") public class UserController { @GetMapping("{id}") @Operation(summary = "查询用户") @Parameter(name = "id", description = "用户ID", example = "1234567890123456789") public UserVO getUser(@PathVariable @Pattern(regexp = "^\d{19}$", message = "用户ID,应为19位数字") String id) { // 示例代码;实际业务逻辑中应该从数据库中获取数据 UserVO vo = new UserVO(); vo.setId(id); vo.setName("张三"); vo.setMobilePhone("18612345678"); vo.setEmail("[email protected]"); return vo; } }
测试结果
不进行异常处理的接口响应
- 接口相应
- 控制台报错
异常统一处理后的接口响应
参考文章
请参考博文:路径参数@PathVariable,格式校验:@Validated(Controller上)+ @Pattern + ConstraintViolationException(异常处理)