Loading... ## Spring 数据校验(Validation)详解 在开发基于 Spring 的 Web 应用时,数据校验是确保用户输入合法性的重要步骤。通过 Spring 提供的 `Validation` 机制,开发者可以使用标准的注解来验证请求数据的完整性和准确性。Spring 的数据校验通常与 `Bean Validation` (JSR-303) 结合使用,并且可以自定义校验逻辑,以满足复杂的业务需求。 本文将详细介绍如何在 Spring 中使用数据校验功能,并展示如何进行常见的字段验证操作。 ### 1. 添加依赖 在 Spring 项目中使用数据校验功能,首先需要确保引入了 `hibernate-validator` 依赖。Hibernate Validator 是 Bean Validation 的参考实现。 对于 Maven 项目,可以在 `pom.xml` 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> ``` ### 2. 数据校验基础 #### 2.1 常见校验注解 Spring 数据校验注解主要定义在字段上,常见的校验注解如下表: | 校验注解 | 说明 | | ------------- | ------------------------------------- | | `@NotNull` | 验证字段不能为 `null` | | `@NotEmpty` | 验证字符串、集合不为空 | | `@NotBlank` | 验证字符串非空且长度大于0(去掉空格) | | `@Size` | 验证字符串、集合的大小范围 | | `@Min` | 验证数值不能小于指定的最小值 | | `@Max` | 验证数值不能大于指定的最大值 | | `@Email` | 验证邮箱格式 | | `@Pattern` | 验证字符串符合正则表达式 | | `@Past` | 验证日期为过去的某个时间 | | `@Future` | 验证日期为未来的某个时间 | #### 2.2 示例实体类 假设我们有一个用户注册请求对象 `UserDTO`,包含用户名、年龄、邮箱等字段。通过在该类的字段上添加校验注解,可以确保请求的数据满足业务要求。 ```java import javax.validation.constraints.*; public class UserDTO { @NotBlank(message = "用户名不能为空") private String username; @Min(value = 18, message = "年龄不能小于18岁") @Max(value = 60, message = "年龄不能大于60岁") private Integer age; @Email(message = "邮箱格式不正确") @NotBlank(message = "邮箱不能为空") private String email; // Getter和Setter方法 } ``` 在 `UserDTO` 类中: - `@NotBlank` 保证用户名和邮箱字段不能为空白字符。 - `@Min` 和 `@Max` 确保年龄在 18 到 60 之间。 - `@Email` 验证邮箱格式是否合法。 ### 3. 控制器中的数据校验 #### 3.1 在控制器中使用校验 在 Spring Boot 中,使用 `@Valid` 或 `@Validated` 注解可以触发数据校验。以下是一个控制器示例: ```java import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import javax.validation.constraints.NotNull; @RestController @RequestMapping("/users") @Validated public class UserController { @PostMapping("/register") public String registerUser(@Valid @RequestBody UserDTO userDTO) { // 如果数据校验通过,执行注册逻辑 return "用户注册成功"; } } ``` 在这个例子中: - `@Valid` 注解用于触发 `UserDTO` 对象的校验。 - 如果请求体中的数据不符合校验规则,Spring 会自动返回 400 错误码,并附带校验错误的信息。 #### 3.2 自定义全局异常处理 为了更好地处理校验失败的情况,我们可以自定义全局异常处理器来返回更加友好的错误信息。 ```java import org.springframework.http.HttpStatus; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage())); return errors; } } ``` 在此 `GlobalExceptionHandler` 中: - `MethodArgumentNotValidException` 被捕获,用于处理参数校验失败的异常。 - `handleValidationExceptions` 方法会返回字段及其对应的校验错误信息。 ### 4. 分组校验 在某些情况下,不同场景下对同一对象的校验规则可能不同。Spring 提供了**分组校验**机制,允许我们在不同场景下应用不同的校验规则。 #### 4.1 定义校验分组 首先,定义分组接口: ```java public interface CreateGroup {} public interface UpdateGroup {} ``` #### 4.2 应用分组校验 在实体类中使用 `groups` 属性指定不同的校验规则: ```java public class UserDTO { @NotBlank(message = "用户名不能为空", groups = CreateGroup.class) private String username; @Min(value = 18, message = "年龄不能小于18岁", groups = {CreateGroup.class, UpdateGroup.class}) private Integer age; @Email(message = "邮箱格式不正确", groups = UpdateGroup.class) private String email; // Getter和Setter方法 } ``` 在此例中: - `username` 字段仅在创建用户时进行校验。 - `age` 字段在创建和更新时都需校验。 - `email` 字段仅在更新时校验。 #### 4.3 在控制器中指定分组 控制器中使用 `@Validated` 并指定分组来触发不同场景下的校验: ```java @RestController @RequestMapping("/users") @Validated public class UserController { @PostMapping("/create") public String createUser(@Validated(CreateGroup.class) @RequestBody UserDTO userDTO) { return "用户创建成功"; } @PutMapping("/update") public String updateUser(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) { return "用户更新成功"; } } ``` 在这里,根据请求类型分别触发不同的校验逻辑。 ### 5. 自定义校验 有时内置的校验注解无法满足复杂的业务逻辑要求,我们可以通过自定义校验注解实现特定的校验逻辑。 #### 5.1 创建自定义校验注解 自定义注解需要结合 `Constraint` 注解和 `ConstraintValidator` 实现。例如,验证用户年龄是否为偶数: ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Documented @Constraint(validatedBy = EvenNumberValidator.class) @Target({ ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface EvenNumber { String message() default "年龄必须是偶数"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` #### 5.2 实现校验逻辑 接着,编写 `EvenNumberValidator` 校验逻辑类: ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class EvenNumberValidator implements ConstraintValidator<EvenNumber, Integer> { @Override public void initialize(EvenNumber constraintAnnotation) { } @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { if (value == null) { return false; // 可以根据需求允许为 null } return value % 2 == 0; } } ``` #### 5.3 使用自定义校验 现在可以在实体类中使用自定义注解: ```java public class UserDTO { @EvenNumber(message = "年龄必须是偶数") private Integer age; // 其他字段和方法 } ``` ### 6. 结论 Spring 的数据校验机制通过注解驱动,为开发者提供了一种简洁高效的方式来验证用户输入。无论是使用内置校验注解还是自定义校验逻辑,开发者都可以根据业务需求灵活应用。通过结合 `@Valid`、`@Validated` 和分组校验等功能,可以确保数据的准确性和安全性。 最后修改:2024 年 09 月 17 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏