Spring Validation作為Spring生態系統的重要組成部分,提供了一套強大而靈活的數據校驗機制。
1. Bean Validation基礎注解
Spring Validation集成了JSR-380 (Bean Validation 2.0)規范,提供了一系列開箱即用的校驗注解。
常用注解示例
@Data
public class UserDTO {@NotNull(message = "用戶ID不能為空")private Long id;@NotBlank(message = "用戶名不能為空")@Size(min = 4, max = 20, message = "用戶名長度必須在4到20個字符之間")private String username;@Email(message = "郵箱格式不正確")private String email;@Min(value = 18, message = "年齡必須大于或等于18")@Max(value = 120, message = "年齡必須小于或等于120")private Integer age;@Past(message = "出生日期必須是過去的日期")private LocalDate birthDate;@Pattern(regexp = "^1[3-9]\d{9}$", message = "手機號碼格式不正確")private String phoneNumber;
}
在控制器中應用
@RestController
@RequestMapping("/api/users")
public class UserController {@PostMappingpublic ResponseEntity<UserDTO> createUser(@RequestBody @Valid UserDTO userDTO, BindingResult bindingResult) {if (bindingResult.hasErrors()) {// 處理驗證錯誤throw new ValidationException(bindingResult);}// 處理業務邏輯return ResponseEntity.ok(userDTO);}
}
最佳實踐:使用有意義的錯誤消息,保持一致的命名風格,避免在實體類上直接使用驗證注解,而是在DTO對象上應用驗證規則。
2. 自定義約束驗證器
Spring Validation允許開發者創建自定義約束,滿足特定業務規則的驗證需求。
定義自定義約束注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
public @interface UniqueUsername {String message() default "用戶名已存在";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
實現驗證器
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {@Autowiredprivate UserRepository userRepository;@Overridepublic boolean isValid(String username, ConstraintValidatorContext context) {if (username == null) {return true; // 讓@NotNull處理空值}return !userRepository.existsByUsername(username);}
}
應用自定義約束
public class UserRegistrationDTO {@NotBlank@Size(min = 4, max = 20)@UniqueUsernameprivate String username;// 其他字段...
}
使用場景:驗證業務特定規則,如唯一性約束、密碼復雜度、信用卡格式等。
3. 分組驗證
分組驗證允許根據不同場景應用不同的驗證規則,例如創建和更新操作可能需要不同的驗證邏輯。
定義驗證分組
// 定義驗證分組接口
public interface ValidationGroups {interface Create {}interface Update {}
}
應用分組到約束
@Data
public class ProductDTO {@Null(groups = ValidationGroups.Create.class, message = "創建產品時ID必須為空")@NotNull(groups = ValidationGroups.Update.class, message = "更新產品時ID不能為空")private Long id;@NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class})private String name;@PositiveOrZero(groups = ValidationGroups.Create.class)@Positive(groups = ValidationGroups.Update.class)private BigDecimal price;
}
在控制器中指定分組
@RestController
@RequestMapping("/api/products")
public class ProductController {@PostMappingpublic ResponseEntity<ProductDTO> createProduct(@RequestBody @Validated(ValidationGroups.Create.class) ProductDTO productDTO) {// 創建產品邏輯return ResponseEntity.ok(productDTO);}@PutMapping("/{id}")public ResponseEntity<ProductDTO> updateProduct(@PathVariable Long id,@RequestBody @Validated(ValidationGroups.Update.class) ProductDTO productDTO) {// 更新產品邏輯return ResponseEntity.ok(productDTO);}
}
提示:注意使用@Validated
注解而不是@Valid
,因為只有前者支持分組驗證。
4. 嵌套驗證
嵌套驗證允許驗證復雜對象結構中的嵌套對象。
定義嵌套對象
@Data
public class OrderDTO {@NotNullprivate Long id;@NotNull@Valid // 標記需要級聯驗證的字段private CustomerDTO customer;@NotEmpty@Valid // 驗證集合中的每個元素private List<OrderItemDTO> items;
}@Data
public class CustomerDTO {@NotNullprivate Long id;@NotBlankprivate String name;@Emailprivate String email;@Valid // 進一步嵌套驗證private AddressDTO address;
}
關鍵點:在需要級聯驗證的字段上添加@Valid
注解,確保驗證深入到嵌套對象中。
5. 方法級別驗證
Spring Validation不僅可以用于控制器參數,還可以應用于服務層的方法。
啟用方法級別驗證
@Configuration
@EnableMethodValidation
public class ValidationConfig {// 配置內容
}
定義帶驗證的服務方法
@Service
public class UserService {@Validatedpublic User createUser(@Valid UserDTO userDTO) {// 業務邏輯return new User();}@NotNullpublic User findById(@Min(1) Long id) {// 查詢邏輯return new User();}@Validated(ValidationGroups.Update.class)public void updateUser(@Valid UserDTO userDTO) {// 更新邏輯}
}
應用場景:確保服務層方法接收到的參數和返回的結果符合預期,增強代碼的健壯性。
6. 錯誤消息處理和國際化
Spring Validation提供了強大的錯誤消息處理和國際化支持。
自定義錯誤消息
在ValidationMessages.properties
文件中定義:
# ValidationMessages.properties
javax.validation.constraints.NotEmpty.message=字段不能為空
javax.validation.constraints.Email.message=不是有效的電子郵箱地址
user.name.size=用戶名長度必須在{min}到{max}個字符之間
國際化錯誤消息
創建特定語言的屬性文件:
# ValidationMessages_en.properties
javax.validation.constraints.NotEmpty.message=Field cannot be empty
javax.validation.constraints.Email.message=Not a valid email address
user.name.size=Username must be between {min} and {max} characters# ValidationMessages_zh_CN.properties
javax.validation.constraints.NotEmpty.message=字段不能為空
javax.validation.constraints.Email.message=不是有效的電子郵箱地址
user.name.size=用戶名長度必須在{min}到{max}個字符之間
使用自定義消息
@Size(min = 4, max = 20, message = "{user.name.size}")
private String username;
7. 程序化驗證
除了注解驅動的驗證,Spring Validation還支持以編程方式進行驗證。
使用Validator手動驗證對象
@Service
public class ValidationService {private final Validator validator;public ValidationService(Validator validator) {this.validator = validator;}public <T> void validate(T object) {Set<ConstraintViolation<T>> violations = validator.validate(object);if (!violations.isEmpty()) {throw new ConstraintViolationException(violations);}}public <T> void validateWithGroup(T object, Class<?>... groups) {Set<ConstraintViolation<T>> violations = validator.validate(object, groups);if (!violations.isEmpty()) {throw new ConstraintViolationException(violations);}}public <T> List<String> getValidationErrors(T object) {return validator.validate(object).stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());}
}
使用場景:在復雜業務邏輯中需要條件性驗證,或者驗證非控制器傳入的對象時。
8. 組合約束
組合約束允許將多個基本約束組合成一個更復雜的約束,減少代碼重復。
創建組合約束
@NotNull
@Size(min = 8, max = 30)
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$", message = "密碼必須包含至少一個數字、小寫字母、大寫字母和特殊字符")
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
public @interface StrongPassword {String message() default "密碼不符合安全要求";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
應用組合約束
public class PasswordChangeDTO {@NotBlankprivate String oldPassword;@StrongPasswordprivate String newPassword;@NotBlankprivate String confirmPassword;
}
優點:提高代碼可讀性和可維護性,確保驗證規則在整個應用中保持一致。
9. 跨字段驗證
跨字段驗證允許根據多個字段之間的關系進行驗證。
創建類級別約束
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordMatchesValidator.class)
public @interface PasswordMatches {String message() default "確認密碼與新密碼不匹配";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};String field();String fieldMatch();
}
實現驗證器
public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {private String field;private String fieldMatch;@Overridepublic void initialize(PasswordMatches constraintAnnotation) {this.field = constraintAnnotation.field();this.fieldMatch = constraintAnnotation.fieldMatch();}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext context) {try {Object fieldValue = BeanUtils.getPropertyDescriptor(value.getClass(), field).getReadMethod().invoke(value);Object fieldMatchValue = BeanUtils.getPropertyDescriptor(value.getClass(), fieldMatch).getReadMethod().invoke(value);return (fieldValue != null) && fieldValue.equals(fieldMatchValue);} catch (Exception e) {return false;}}
}
應用類級別約束
@Data
@PasswordMatches(field = "newPassword", fieldMatch = "confirmPassword")
public class PasswordChangeDTO {@NotBlankprivate String oldPassword;@StrongPasswordprivate String newPassword;@NotBlankprivate String confirmPassword;
}
使用場景:驗證密碼確認、日期范圍比較、最小/最大值比較等。
總結
Spring Validation提供了一套全面而強大的數據校驗工具,從基本的注解驗證到復雜的自定義約束,從單一字段驗證到跨字段關系驗證,都有相應的解決方案。
合理利用這些驗證工具,不僅能提高應用的健壯性和安全性,還能改善代碼質量和可維護性。