1. 概述
本快速教程將重點介紹 Spring 中的 @Valid 和 @Validated 註解之間的差異。
驗證用户輸入是大多數應用程序中常見的功能。在 Java 生態系統中,我們專門使用 Java 標準 Bean 驗證 API 來支持此功能,該 API 從 Spring 4.0 版本開始與 Spring 深度集成。 @Valid 和 @Validated 註解源於該標準 Bean API。
在下一部分,我們將更詳細地探討它們。
2. @Valid 和 @Validated 註解
在 Spring 中,我們使用 JSR-303 的 @Valid 註解用於方法級別的驗證。我們還將其用於標記成員屬性的驗證。 但是,此註解不支持組級別的驗證。
組有助於限制驗證過程中應用的約束。一個典型的用例是 UI 流程嚮導。在第一步中,我們可能只有一組子屬性。在後續步驟中,可能還有另一個屬於同一 bean 的組。因此,我們需要在每個步驟中對這些有限的字段應用約束,但 @Valid 不支持此功能。
在這種情況下,對於組級別的驗證,我們必須使用 Spring 的 @Validated, 它是 JSR-303 的 @Valid 的變體。此註解主要用於方法級別。 對於標記成員屬性,我們仍然使用 @Valid 註解。
現在,讓我們直接來看一下這些註解的用法,並提供一個示例。
@Valid
public class User {
@Validated
private String name;
@Validated
private int age;
}
3. 示例
讓我們考慮一個使用 Spring Boot 開發的簡單用户註冊表單。首先,我們將僅包含 姓名 和 密碼 屬性:
public class UserAccount {
@NotNull
@Size(min = 4, max = 15)
private String password;
@NotBlank
private String name;
// standard constructors / setters / getters / toString
}
接下來,我們來看一下控制器。在這裏,我們將有 saveBasicInfo方法,帶有 @Valid註解,用於驗證用户輸入:
@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)
public String saveBasicInfo(
@Valid @ModelAttribute("useraccount") UserAccount useraccount,
BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "error";
}
return "success";
}現在讓我們測試一下這個方法:
@Test
public void givenSaveBasicInfo_whenCorrectInput_thenSuccess() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfo")
.accept(MediaType.TEXT_HTML)
.param("name", "test123")
.param("password", "pass"))
.andExpect(view().name("success"))
.andExpect(status().isOk())
.andDo(print());
}在確認測試運行成功後,我們將擴展功能。接下來最自然的步驟是將其轉換為多步驟註冊表單,就像大多數嚮導所採用的。第一步,包括姓名和密碼,保持不變。在第二步,我們將獲取額外信息,例如年齡和電話。然後我們將使用這些額外字段更新我們的領域對象:
public class UserAccount {
@NotNull
@Size(min = 4, max = 15)
private String password;
@NotBlank
private String name;
@Min(value = 18, message = "Age should not be less than 18")
private int age;
@NotBlank
private String phone;
// standard constructors / setters / getters / toString
}
然而,這次我們會發現之前的測試失敗了。這是因為我們沒有將 age 和 phone 字段傳遞進來,這些字段仍然不在 UI 上顯示 . 為了支持這種行為,我們需要進行分組驗證以及 @Validated 註解。
為此,我們需要創建兩個分組,創建兩個不同的分組。首先,我們需要創建兩個標記接口,一個用於每個組或每個步驟。可以參考我們關於分組驗證的文章,以獲取確切的實現。在這裏,讓我們專注於註解之間的差異。
我們將擁有 BasicInfo 接口作為第一步,以及 AdvanceInfo 作為第二步。此外,我們將更新我們的 UserAccount 類以使用這些標記接口:
public class UserAccount {
@NotNull(groups = BasicInfo.class)
@Size(min = 4, max = 15, groups = BasicInfo.class)
private String password;
@NotBlank(groups = BasicInfo.class)
private String name;
@Min(value = 18, message = "Age should not be less than 18", groups = AdvanceInfo.class)
private int age;
@NotBlank(groups = AdvanceInfo.class)
private String phone;
// standard constructors / setters / getters / toString
}
此外,我們還將更新控制器以使用 @Validated 註解,而不是 @Valid 註解:
@RequestMapping(value = "/saveBasicInfoStep1", method = RequestMethod.POST)
public String saveBasicInfoStep1(
@Validated(BasicInfo.class)
@ModelAttribute("useraccount") UserAccount useraccount,
BindingResult result, ModelMap model) {
if (result.hasErrors()) {
return "error";
}
return "success";
}由於此次更新,我們的測試現在已成功運行。我們還將測試此新方法:
@Test
public void givenSaveBasicInfoStep1_whenCorrectInput_thenSuccess() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1")
.accept(MediaType.TEXT_HTML)
.param("name", "test123")
.param("password", "pass"))
.andExpect(view().name("success"))
.andExpect(status().isOk())
.andDo(print());
}這同樣成功運行。因此,我們可以看到使用 @Validated 驗證組對於驗證的必要性。
接下來,讓我們看看 @Valid 對於觸發嵌套屬性驗證的必要性。
4. 使用 @Valid 註解標記嵌套對象
@Valid 註解用於標記嵌套屬性,特別是。這會觸發嵌套對象的驗證。例如,在當前場景中,我們可以創建一個 UserAddress 對象:
public class UserAddress {
@NotBlank
private String countryCode;
// standard constructors / setters / getters / toString
}為了確保此嵌套對象的有效性,我們將使用 @Valid</em/> 裝飾器來註解該屬性:
public class UserAccount {
//...
@Valid
@NotNull(groups = AdvanceInfo.class)
private UserAddress useraddress;
// standard constructors / setters / getters / toString
}5. 優缺點
讓我們來看一下在 Spring 中使用 <em @Valid</em> 和 <em @Validated</em> 註解的優缺點。
@Valid 註解確保對整個對象的驗證。 重要的是,它執行整個對象圖的驗證。 但是,這在需要僅部分驗證的場景中會產生問題。
另一方面,我們可以使用 @Validated 進行組驗證,包括上述部分驗證。 但是,在這種情況下,被驗證的實體必須知道它們所使用的所有組或用例的驗證規則,或者混合了關注點,這可能會導致反模式。
6. 結論
在本文中,我們探討了 @Valid 和 @Validated 註解之間的關鍵差異。
總結一下,對於任何基本的驗證,我們將使用 JSR 中的 @Valid 註解在方法調用中。另一方面,對於任何組驗證,包括 組序列,我們將需要在方法調用中使用 Spring 的 @Validated 註解。 @Valid 註解也被需要來觸發嵌套屬性的驗證。