Spring中@Valid和@Validated註解的差異

    1.概述

    在本快速教程中,我們將重點介紹Spring中@Valid@Validated註解之間的區別。

    驗證用戶輸入是我們大多數應用程序中的常見功能。在Java生態系統中,我們專門使用Java標準Bean驗證API來支持此功能。而且,從4.0版本開始,它也與Spring很好地集成在一起。 @Valid@Validated註解源自此Standard Bean API

    在下一節中,我們將詳細介紹它們。

    2. @Valid@Validated註解

    在Spring中,我們使用JSR-303的**@Valid批註進行方法級別驗證此外,我們還使用它來標記成員屬性以進行驗證**。但是,此註釋不支持組驗證。

    組有助於限制驗證期間應用的約束。 UI嚮導是一種特殊的用例。在這裡,第一步,我們可能有一個特定的字段子組。在後續步驟中,可能存在另一個屬於同一bean的組。因此,我們需要在每個步驟中對這些有限的字段應用約束,但是@Valid不支持此約束。

    在這種情況下,**對於組級別,我們必須使用Spring的@Validated,**這是此JSR-303的@Valid的變體。在方法級別使用。為了標記成員屬性,我們繼續使用@Valid批註。

    現在,讓我們深入研究一下,並通過示例查看這些註釋的用法。

    3.例子

    讓我們考慮一個使用Spring Boot開發的簡單用戶註冊表單。首先,我們只有namepassword屬性:

    public class UserAccount {
    
    
    
     @NotNull
    
     @Size(min = 4, max = 15)
    
     private String password;
    
    
    
     @NotBlank
    
     private String name;
    
    
    
     // standard constructors / setters / getters / toString
    
    
    
     }
    

    接下來,讓我們看一下控制器。在這裡,我們將使用帶有@Valid批註的saveBasicInfo方法來驗證用戶輸入:

    @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());
    
     }

    在確認測試成功運行之後,現在讓我們擴展功能。下一步的邏輯步驟是將其轉換為多步驟註冊表格,就像大多數嚮導一樣。第一步, namepassword保持不變。在第二步中,我們將獲取其他信息,例如agephone 。因此,我們將使用以下其他字段更新域對象:

    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
    
    
    
     }
    

    但是,這一次,我們將注意到先前的測試失敗。這是因為我們沒有傳遞agephone字段,這些字段仍然不在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批註裝飾屬性:

    public class UserAccount {
    
    
    
     //...
    
    
    
     @Valid
    
     @NotNull(groups = AdvanceInfo.class)
    
     private UserAddress useraddress;
    
    
    
     // standard constructors / setters / getters / toString
    
     }

    5.利與弊

    讓我們看看在Spring中使用@Valid@Validated批註的一些優缺點。

    @Valid批註確保整個對象的驗證。重要的是,它執行整個對像圖的驗證。但是,這為僅需要部分驗證的方案帶來了問題。

    另一方面,我們可以使用@Validated進行組驗證,包括上面的部分驗證。但是,在這種情況下,經過驗證的實體必須知道其使用的所有組或用例的驗證規則。在這裡,我們混合了各種顧慮,因此這可能會導致產生反模式。

    六,結論

    在本快速教程中,我們探討了@Valid@Validated批註之間的主要區別。

    總之,對於任何基本驗證,我們將在方法調用中使用JSR @Valid批註。另一方面,對於包括組序列在內的任何組驗證,我們都需要在方法調用中使用Spring的@Validated批註。還需要@Valid批註來觸發嵌套屬性的驗證。

    與往常一樣,本文提供的代碼可從GitHub上獲得