Spring Boot 参数校验的最佳实践
参数校验是后端开发中最基础但也最容易忽视的部分。本文总结几种常用的校验方式及其适用场景。
🔧 基础校验:使用 Validation 注解
最简单的方式是使用 javax.validation 注解:
java
@Data
public class UserDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度 2-20 字符")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 1, message = "年龄必须大于 0")
private Integer age;
}Controller 中配合 @Valid 使用:
java
@PostMapping("/users")
public Result createUser(@Valid @RequestBody UserDTO userDTO) {
// 业务逻辑
}常用注解速查
| 注解 | 说明 | 适用类型 |
|---|---|---|
@NotBlank | 不能为空且长度 > 0 | String |
@NotNull | 不能为 null | 所有类型 |
@Size | 长度范围 | String/Collection |
@Min/@Max | 数值范围 | Number |
@Email | 邮箱格式 | String |
@Pattern | 正则匹配 | String |
🎯 进阶:自定义校验注解
当内置注解无法满足需求时,可以自定义:
java
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<?>[] payload() default {};
}
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true; // 允许空值,配合 @NotBlank
return PHONE_PATTERN.matcher(value).matches();
}
}🔄 分组校验:同一实体不同场景
创建和更新往往校验规则不同:
java
public interface Create {}
public interface Update {}
@Data
public class UserDTO {
@NotNull(groups = Update.class, message = "更新时 ID 必填")
private Long id;
@NotBlank(groups = {Create.class, Update.class})
private String username;
}
// Controller
@PostMapping
public Result create(@Validated(Create.class) @RequestBody UserDTO dto) {}
@PutMapping
public Result update(@Validated(Update.class) @RequestBody UserDTO dto) {}📝 统一异常处理
校验失败会抛出 MethodArgumentNotValidException,建议统一处理:
java
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidation(MethodArgumentNotValidException e) {
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
return Result.fail("参数校验失败", errors);
}
}💡 最佳实践建议
- DTO 与 Entity 分离:校验注解放在 DTO,Entity 只做持久化
- 宽松入参,严格出参:入参允许部分字段为空,出参返回完整数据
- 业务校验与格式校验分离:格式用注解,业务逻辑在 Service
- 友好错误提示:错误信息要用户能看懂,避免技术术语
📚 参考资源
参数校验看似简单,用好却能减少大量 if-else,让代码更清晰优雅。希望这篇对你有帮助!☕