51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Spring 中 @Valid 和 @Validated 注解的区别

1、概览 {#1概览}

本文将带你了解 Spring 中 @Valid@Validated 注解的用法和它们之间的区别。

在大多数应用中,验证用户输入是常见的功能。在 Java 生态系统中,一般使用 Java Standard Bean Validation API 来支持这一点,从 Spring 4.0 版本开始,它就与 Spring 完美集成。@Valid@Validated 注解就源自于这个 Standard Bean API。

2、@Valid 和 @Validated 注解 {#2valid-和-validated-注解}

在 Spring 中,通常使用 JSR-303 的 @Valid 注解进行方法级验证,以及用于标记成员属性以进行验证。不过,该注解不支持分组验证。

分组(Group)可以帮助限制在验证过程中应用的约束条件。一个特定的使用案例是 UI 引导(UI wizards)。在第一步中,可能会有一组特定的字段。在后续步骤中,可能还有同一个 Bean 的另一组字段。因此,需要在每个步骤中对这些有限的字段应用约束条件,但是 @Valid 无法支持这样的功能。

对于分组级(Group-Level)验证,必须使用 Spring 的 @Validated,它是 JSR-303 的 @Valid 的变体,用于方法级。对于成员属性的标记,继续使用 @Valid 注解就行。

3、示例 {#3示例}

使用 Spring Boot 开发一个简单的用户注册表单。

首先,只有 namepassword 属性:

public class UserAccount {

    @NotNull
    @Size(min = 4, max = 15)
    private String password;

    @NotBlank
    private String name;

    // 构造函数、Get、Set 省略
}

接下来是 Controller。在 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());
}

确认测试运行成功后,扩展功能。下一个合理的步骤是将其转换为多步骤的注册表单,就像大多数引导应用一样。第一步中的 namepassword 保持不变。在第二步中,获取额外的信息,例如 agephone。然后,使用这些额外的字段更新 Domain 对象:

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;
    
    // 省略其他代码 
    
}

不过,这次会发现之前的测试失败了,这是因为没有传入 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;
    
    // 省略其他代码 
    
}

更新 Controller,使用 @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 适用于这种需要分组验证的场景。

4、使用 @Valid 注解标记嵌套对象 {#4使用-valid-注解标记嵌套对象}

@Valid 注解用于标记嵌套属性。这会触发嵌套对象的验证。例如,在当前场景中,可以创建一个 UserAddress 对象:

public class UserAddress {

    @NotBlank
    private String countryCode;

    // 其他代码省略
}

使用 @Valid 注解来装饰该属性,确保对嵌套对象进行验证:

public class UserAccount {
    
    //...
    
    @Valid
    @NotNull(groups = AdvanceInfo.class)
    private UserAddress useraddress;
    
    // 其他代码省略
}

5、利弊 {#5利弊}

@Valid 注解用于对整个对象进行验证,但不能对字段进行分组验证。

@Validated 可以进行组验证。但是,在这种情况下,被验证的实体必须知道它们所使用的所有组或用例的验证规则。这混合了关注点,可能会导致反模式(在实践中经常出现但又低效或是有待优化的设计模式)。

6、总结 {#6总结}

本文介绍了 Spring 应用中 @Valid 注解和 @Validated 注解之间的主要区别。

总的来说:对于基本验证和嵌套属性验证,使用 JSR @Valid 注解。对于分组验证使用 Spring 的 @Validated 注解。


Ref:https://www.baeldung.com/spring-valid-vs-validated

赞(3)
未经允许不得转载:工具盒子 » Spring 中 @Valid 和 @Validated 注解的区别