🚀 在SpringBoot项目开发中,数据校验是一个不可或缺的环节。合理使用@Validated注解可以让参数校验变得简单高效,避免在业务逻辑中编写大量的判断代码。本文将详细介绍如何在SpringBoot中优雅地使用@Validated进行数据校验,让你的代码更加简洁、健壮。
🔍 为什么需要数据校验?
一、数据校验的意义
数据校验的重要性
没有数据校验时,开发者需要在业务逻辑中手动编写大量的判断代码,不仅繁琐且容易遗漏,导致系统出现安全隐患或异常。
数据校验解决的问题:
- 🚫 避免了 非法数据进入业务逻辑层
- ✅ 提高了 代码的可读性和可维护性
- 🔄 简化了 参数验证的流程
- 🔒 增强了 系统的安全性和稳定性
二、SpringBoot中的数据校验方案
SpringBoot提供了多种数据校验方案,主要包括:
- @Validated注解:Spring提供的校验注解,可以应用在类或方法上
- Bean Validation注解:JSR-380规范定义的校验注解,如@NotNull 、@Size等
- 自定义校验器:通过实现ConstraintValidator接口创建自定义校验规则
🛠️ @Validated注解的使用方法
一、基本配置
首先,需要在项目中添加相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
二、在Controller中使用@Validated
@RestController
@RequestMapping("/api/users")
@Validated // 类级别使用@Validated
public class UserController {
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// 业务逻辑
return ResponseEntity.ok(user);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable @Min(1) Long id) {
// 业务逻辑
return ResponseEntity.ok(userService.findById(id));
}
@GetMapping("/search")
public ResponseEntity<List<User>> searchUsers(
@RequestParam @Size(min = 2, max = 50) String name,
@RequestParam @Min(0) @Max(100) Integer age) {
// 业务逻辑
return ResponseEntity.ok(userService.search(name, age));
}
}
三、在实体类中使用校验注解
@Data
public class User {
@NotNull(message = "用户ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 50, message = "用户名长度必须在2-50个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$",
message = "密码必须包含字母和数字,且长度不少于8位")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@Past(message = "生日必须是过去的日期")
private Date birthday;
@Future(message = "预约时间必须是未来的时间")
private Date appointmentTime;
}
🔧 常用校验注解详解
一、Bean Validation标准注解
| 注解 | 说明 | 示例 |
|---|---|---|
| @NotNull | 不能为null | @NotNull(message = "ID不能为空") |
| @NotEmpty | 不能为空(字符串长度不为0,集合大小不为0) | @NotEmpty(message = "列表不能为空") |
| @NotBlank | 不能为空(字符串去除空格后长度不为0) | @NotBlank(message = "用户名不能为空") |
| @Size | 长度或大小在指定范围内 | @Size(min = 2, max = 50) |
| @Min | 最小值 | @Min(value = 0) |
| @Max | 最大值 | @Max(value = 100) |
| @DecimalMin | 最小值(用于BigDecimal) | @DecimalMin(value = "0.0") |
| @DecimalMax | 最大值(用于BigDecimal) | @DecimalMax(value = "100.0") |
| @Digits | 数字的整数位和小数位的位数限制 | @Digits(integer = 3, fraction = 2) |
| @Past | 必须是过去的日期 | @Past |
| @Future | 必须是将来的日期 | @Future |
| @Pattern | 正则表达式匹配 | @Pattern(regexp = "^[A-Za-z0-9]+$") |
| 必须是有效的电子邮件地址 | ||
| @AssertTrue | 必须为true | @AssertTrue |
| @AssertFalse | 必须为false | @AssertFalse |
二、Hibernate Validator扩展注解
| 注解 | 说明 | 示例 |
|---|---|---|
| @Length | 字符串长度限制 | @Length(min = 2, max = 50) |
| @Range | 数值范围限制 | @Range(min = 0, max = 100) |
| @URL | URL格式验证 | @URL |
| @CreditCardNumber | 信用卡号验证 | @CreditCardNumber |
| @EAN | EAN码验证 | @EAN |
| @ISBN | ISBN码验证 | @ISBN |
| @SafeHtml | HTML安全验证 | @SafeHtml |
🛠️ 自定义校验器
一、创建自定义校验注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
二、实现校验逻辑
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, 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; // 如果允许为空,返回true
}
return PHONE_PATTERN.matcher(value).matches();
}
}
三、使用自定义校验注解
@Data
public class User {
// 其他字段...
@PhoneNumber(message = "请输入正确的手机号")
private String phone;
}
🔧 全局异常处理
一、创建全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Map<String, String>> handleConstraintViolationException(
ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(violation -> {
String fieldName = violation.getPropertyPath().toString();
String errorMessage = violation.getMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
}
二、统一响应格式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "操作成功", data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
}
🚀 高级应用技巧
一、分组校验
// 定义校验分组
public interface Create {}
public interface Update {}
// 在实体类中使用分组
@Data
public class User {
@Null(groups = Create.class, message = "创建时ID必须为空")
@NotNull(groups = Update.class, message = "更新时ID不能为空")
private Long id;
@NotBlank(groups = {Create.class, Update.class}, message = "用户名不能为空")
private String username;
// 其他字段...
}
// 在Controller中使用分组
@PostMapping
public ResponseEntity<User> createUser(@Validated(Create.class) @RequestBody User user) {
// 业务逻辑
return ResponseEntity.ok(user);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@Validated(Update.class) @RequestBody User user) {
// 业务逻辑
return ResponseEntity.ok(user);
}
二、嵌套对象校验
@Data
public class Order {
@NotNull(message = "订单ID不能为空")
private Long id;
@Valid
@NotNull(message = "用户信息不能为空")
private User user;
@Valid
@NotEmpty(message = "订单项不能为空")
private List<OrderItem> items;
}
@Data
public class OrderItem {
@NotNull(message = "商品ID不能为空")
private Long productId;
@Min(value = 1, message = "数量必须大于0")
private Integer quantity;
}