视频看了几百小时还迷糊?关注我,几分钟让你秒懂!
在上一篇中,我们讲了全局异常处理,解决了“出错后怎么统一返回”的问题。
但你有没有想过:在请求刚进来时,就拦截非法参数,不让脏数据进入业务层?
这就是今天要讲的核心内容——Spring Boot 中的参数校验(Bean Validation + 全局异常处理)。
一、需求场景
你正在开发一个用户注册接口:
POST /api/user/register Content-Type: application/json { "username": "zhangsan", "email": "invalid-email", "age": -5 }业务规则要求:
username:必填,长度 2~20;email:必须是合法邮箱格式;age:必须 ≥ 18。
如果前端传了非法数据,不能等业务逻辑执行到一半才发现错误,而应该在入口处直接拒绝!
二、解决方案:使用 JSR-303 / Bean Validation + @Valid
✅ 正确做法(推荐)
1. 引入依赖(Spring Boot 默认已包含)
<!-- Spring Boot Web 已默认引入 spring-boot-starter-validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>如果你用的是 Spring Boot 2.3+,需要显式添加该依赖,因为从 2.3 开始 validation 不再默认包含。
2. 定义 DTO 并添加校验注解
// UserRegisterDTO.java import javax.validation.constraints.*; public class UserRegisterDTO { @NotBlank(message = "用户名不能为空") @Size(min = 2, max = 20, message = "用户名长度必须在2~20之间") private String username; @Email(message = "邮箱格式不正确") private String email; @Min(value = 18, message = "年龄必须大于等于18岁") private Integer age; // Getter / Setter }3. Controller 中使用 @Valid
@PostMapping("/register") public CommonResult<String> register(@Valid @RequestBody UserRegisterDTO dto) { // 如果走到这里,说明参数合法! userService.register(dto); return CommonResult.success("注册成功"); }⚠️ 注意:必须加上
@Valid或@Validated,否则校验不会生效!
4. 全局捕获校验异常(配合上一篇的异常处理器)
// 在 GlobalExceptionHandler.java 中新增: @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<CommonResult<Void>> handleValidationException(MethodArgumentNotValidException ex) { // 获取第一个错误信息(也可拼接所有) String errorMsg = ex.getBindingResult() .getFieldError() .getDefaultMessage(); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(CommonResult.error(400, errorMsg)); }💡 进阶:如果想返回所有错误字段,可以遍历
getFieldErrors()拼成 Map。
三、反例(千万别这么写!)
❌ 反例1:手动 if 判断每个字段
@PostMapping("/register") public ResponseEntity<?> registerBad(@RequestBody UserRegisterDTO dto) { if (dto.getUsername() == null || dto.getUsername().trim().isEmpty()) { return ResponseEntity.badRequest().body("用户名不能为空"); } if (dto.getUsername().length() < 2 || dto.getUsername().length() > 20) { return ResponseEntity.badRequest().body("用户名长度不对"); } if (!dto.getEmail().contains("@")) { return ResponseEntity.badRequest().body("邮箱格式错误"); } // ...更多 if }问题:
- 代码臃肿,可读性差;
- 无法复用,换个接口又要重写;
- 容易漏判,维护成本高。
❌ 反例2:加了 @Valid 但没处理异常
@PostMapping("/register") public String register(@Valid @RequestBody UserRegisterDTO dto) { return "ok"; }后果:
当参数非法时,Spring 会抛出MethodArgumentNotValidException,但如果没有全局处理,默认返回 400 + HTML 错误页(在 REST API 中完全不可接受!)。
四、注意事项(面试高频考点!)
@Valid 和 @Validated 的区别?
@Valid:JSR-303 原生注解,支持嵌套校验;@Validated:Spring 扩展,支持分组校验(如:注册 vs 修改密码用不同规则)。
分组校验示例(加分项!)
public interface RegisterGroup {} public interface UpdateGroup {} public class UserDTO { @NotBlank(groups = RegisterGroup.class) private String username; @Email(groups = {RegisterGroup.class, UpdateGroup.class}) private String email; } // Controller public void update(@Validated(UpdateGroup.class) @RequestBody UserDTO dto) { ... }- 自定义校验注解(体现深度)
比如校验手机号:
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PhoneValidator.class) public @interface Phone { String message() default "手机号格式不正确"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } public class PhoneValidator implements ConstraintValidator<Phone, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) return true; // 非空由 @NotBlank 控制 return value.matches("^1[3-9]\\d{9}$"); } }- 路径变量/请求参数校验?用 @Validated!
@RestController @Validated // 必须加在类上 public class UserController { @GetMapping("/user/{id}") public User getUser(@Min(1) @PathVariable Long id) { return userService.getById(id); } }注意:对
@PathVariable、@RequestParam校验,必须用@Validated注解在类级别。
五、总结
| 能力 | 价值 |
|---|---|
| ✅ 自动拦截非法请求 | 提升系统健壮性 |
| ✅ 减少 if 判断 | 代码更简洁 |
| ✅ 校验规则集中管理 | 易于维护和测试 |
| ✅ 与 Swagger 集成 | 自动生成文档约束 |
掌握参数校验,是你从“能跑就行”迈向“专业工程”的关键一步!
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!