一、注解出處與核心定位
1. 注解來源
? 所屬框架:@Validated
是 Spring Framework 提供的注解(org.springframework.validation.annotation
包下)。
? 核心定位:
作為 Spring 對 JSR-380(Bean Validation 2.0+) 規范的增強實現,用于觸發方法參數、返回值或類成員變量的校驗邏輯。
與標準Valid`(JSR 原生注解)相比,支持 分組驗證 和 層級校驗,更貼合 Spring 生態。
二、基礎使用步驟
1. 添加依賴(Spring Boot 示例)
在 pom.xml
中引入 Bean Validation 實現:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 啟用校驗支持
在 Spring Boot 中無需額外配置,自動生效。
3. 編寫校驗規則(DTO/VO)
在數據對象中標注 Bean Validation 注解java
public class UserCreateDTO {
@NotNull(message = "ID 不能為空")
private Long id;@NotEmpty(message = "用戶名不能為空")
private String username;@NotBlank(message = "密碼不能為空或純空格")
private String password;@Size(min = 6, max = 20, message = "密碼長度需在6-20位之間")
private String password;@Email(message = "郵箱格式不正確")
private String email;@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手機號格式錯誤")
private String phone;@Min(value = 18, message = "年齡需≥18歲")
private Integer age;@DecimalMin(value = "0.0", inclusive = false, message = "價格必須大于0")
private BigDecimal price;@Positive(message = "庫存必須為正數")
private Integer stock;@NegativeOrZero(message = "折扣率需≤0")
private BigDecimal discountRate;@Past(message = "出生日期必須早于當前時間")
private LocalDate birthday;@Future(message = "預約時間必須晚于當前時間")
private LocalDateTime appointmentTime;@Length(min = 2, max = 10, message = "名稱長度需在2-10字符之間")
private String name;@Range(min = 1, max = 100, message = "數量需在1-100之間")
private Integer quantity;@URL(protocol = "https", message = "僅支持HTTPS鏈接")
private String website;@CreditCardNumber(message = "信用卡號無效")
private String cardNumber;@SafeHtml(message = "包含非法HTML標簽")
private String description;
}
4. 觸發校驗(Controller 層)
在 Controller 方法參數前使用 @Validated
或 @Valid
:
@RestController
public class UserController {@PostMapping("/users")public ResponseEntity<?> createUser(@RequestBody @Validated UserCreateDTO dto) { // 觸發校驗// 業務邏輯return ResponseEntity.ok().build();}
}
5. 處理校驗異常
通過 @ExceptionHandler
捕獲校驗失敗異常,返回統一錯誤響應:
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {List<String> errors = ex.getBindingResult().getFieldErrors().stream().map(error -> error.getField() + ": " + error.getDefaultMessage()).toList();return ResponseEntity.badRequest().body(new ErrorResponse("參數校驗失敗", errors));}
}
三、進階功能與使用拓展
1. 分組校驗(多場景復用規則)
場景說明
- 創建用戶時:所有字段必填
- 更新用戶時:允許不修改密碼(密碼字段非必填)
步驟實現
1. 定義分組接口
public interface CreateGroup {} // 創建分組標記
public interface UpdateGroup {} // 更新分組標記
2. 在 DTO 中指定分組
public class UserDTO {@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})private String username;@NotBlank(groups = CreateGroup.class) // 僅創建時校驗private String password;
}
3. Controller 方法指定生效分組
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody @Validated(CreateGroup.class) UserDTO dto) { // 僅校驗 CreateGroup 分組規則
}@PutMapping("/users/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id,@RequestBody @Validated(UpdateGroup.class) UserDTO dto) { // 僅校驗 UpdateGroup 分組規則
}
2. 方法級別校驗(Service 層驗證)
在 Service 接口/實現類中使用 @Validated
觸發方法參數校驗:
@Service
@Validated //級別開啟校驗
public class UserService {public void updateEmail(@NotNull Long userId, @Email String newEmail) { // 直接標注校驗規則// 業務邏輯}
}
3. 自定義校驗注解
需求示例
驗證手機號格式(自定義規則)。
實現步驟
1. 定義注解 @Phone
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Phone {String message() default "手機號格式不正確";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
2. 編寫校驗器 PhoneValidator
public class PhoneValidator implements ConstraintValidator<Phone, String> {private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if (value == null) return true; // 結合 @NotNull 使用return PHONE_PATTERN.matcher(value).matches();}
}
3. 在 DTO 中使用
public class UserDTO {@Phoneprivate String phone;
}
4. 級聯校驗(嵌套對象驗證)
當對象包含其他對象成員時,使用 @Valid
觸發嵌套校驗:
public class OrderCreateDTO {@Valid // 觸發 AddressDTO 內部校驗@NotNull(message = "收貨地址不能為空")private AddressDTO address;// 其他字段...
}public class AddressDTO {@NotBlankprivate String city;@NotBlankprivate String street;
}
四、原理與關鍵機制
1. 校驗觸發流程
- 代理攔截:Spring 通過 AOP 代理攔截帶有
@Validated
注解的類方法。 - 參數解析:在方法執行前,校驗參數是否符合約束。
- 異常拋出:若校驗失敗,拋出
ConstraintViolationException
(方法級別)或MethodArgumentNotValidException
(Controller 層)。 - 異常處理:通過
HandlerExceptionResolver
或@ExceptionHandler
處理錯誤。
2. 與 @Valid
的核心差異
特性 | @Valid (JSR-380) | @Validated (Spring) |
---|---|---|
分組支持 | 不支持 | 支持 |
校驗范圍 | 僅方法參數、字段 | 方法參數、返回值、類成員變量 |
層級校驗觸發 | 需顯式添加 @Valid | 可自動級聯 |
適用場景 | 簡單 Bean 校驗 | Spring 生態復雜校驗需求 |
五、常見問題與排查
1. 校驗注解不生效
? 可能原因:
? 未添加 spring-boot-starter-validation
依賴
? 未在類上標注 @Validated
(方法級別校驗)
? 校驗方法未被 Spring 代理(如內部方法調用)
2. 分組校驗未按預期執行
? 檢查點:
? Controller/Service 方法是否指定了正確的 groups
? 分組接口是否正確定義(空接口即可,無需實現)
3. 自定義校驗器未生效
? 排查步驟:
? 確保 ConstraintValidator
實現類被 Spring 管理(如添加 @Component
)
? 檢查注解的 @Constraint(validatedBy)
指向正確類
六、最佳實踐總結
-
分層校驗:
? Controller 層:處理基礎數據格式校驗
? Service 層:執行業務規則校驗(如庫存檢查) -
合理分組:避免為不同場景創建重復 DTO,通過分組復用校驗規則。
-
統一異常處理:全局捕獲校驗異常,返回結構化的錯誤信息。
-
性能優化:
? 避免在復雜校驗中執行數據庫操作(優先通過緩存或異步校驗)
? 對高頻接口禁用實時校驗(如通過groups
動態控制)
通過靈活運用 @Validated
,開發者可以構建健壯維護的校驗體系,顯著提升代碼質量和系統可靠性。