1. Overview
1.概述
In this quick tutorial, we’ll focus on the differences between the @Valid and @Validated annotations in Spring.
在这个快速教程中,我们将重点介绍Spring中的@Valid和@Validated 注解之间的区别。
Validating users’ input is a common functionality in most of our applications. In the Java Ecosystem, we specifically use the Java Standard Bean Validation API to support this, which is well integrated with Spring from version 4.0 onward. The @Valid and @Validated annotations stem from this Standard Bean API.
验证用户的输入是我们大多数应用程序中的一个常见功能。在 Java 生态系统中,我们特别使用 Java Standard Bean Validation API 来支持这一功能,从 4.0 版开始,该 API 已与 Spring 完美集成。@Valid和@Validated注解源自该标准 Bean API。
In the next sections, we’ll explore them in greater detail.
在接下来的章节中,我们将更详细地探讨它们。
2. @Valid and @Validated Annotations
2.@Valid和@Validated注解
In Spring, we use JSR-303’s @Valid annotation for method level validation. We also use it to mark a member attribute for validation. However, this annotation doesn’t support group validation.
在Spring中,我们使用JSR-303的@Valid 注解来进行方法级验证。我们也使用它来标记一个成员属性进行验证。然而,这个注解并不支持分组验证。
Groups help to limit the constraints applied during validation. One particular use case is UI wizards. In the first step, we may have a certain sub-group of fields. In the subsequent step, there may be another group belonging to the same bean. So we need to apply constraints on these limited fields in each step, but @Valid doesn’t support this.
组有助于限制验证期间应用的约束。一个特别的用例是UI向导。在第一步中,我们可能有某个子组的字段。在随后的步骤中,可能会有另一组属于同一Bean。所以我们需要在每一步中对这些有限的字段应用约束,但是@Valid不支持这个。
In this case, for group-level, we have to use Spring’s @Validated, which is a variant of JSR-303’s @Valid. This is used at the method-level. For marking member attributes, we continue to use the @Valid annotation.
在这种情况下,对于组级,我们必须使用Spring的@Validated,,这是JSR-303的@Valid的变体。 这是在方法级使用的。对于标记成员属性,我们继续使用@Valid注解。
Now let’s dive right in and look at the usage of these annotations with an example.
现在让我们直接进入,通过一个例子来看看这些注释的用法。
3. Example
3.例子
Let’s consider a simple user registration form developed using Spring Boot. To begin with, we’ll have only the name and the password attributes:
让我们考虑一下使用Spring Boot开发的一个简单的用户注册表单。首先,我们将只有name和password属性。
public class UserAccount {
@NotNull
@Size(min = 4, max = 15)
private String password;
@NotBlank
private String name;
// standard constructors / setters / getters / toString
}
Next, let’s look at the controller. Here we’ll have the saveBasicInfo method with the @Valid annotation to validate the user input:
接下来,让我们看一下控制器。在这里,我们将有一个带有@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";
}
Now let’s test this method:
现在我们来测试一下这个方法。
@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());
}
After confirming that the test runs successfully, we’ll extend the functionality. The next logical step is to convert this to a multi-step registration form, as is the case with most wizards. The first step with the name and password remains unchanged. In the second step, we’ll fetch additional information like age and phone. Then we’ll update our domain object with these additional fields:
在确认测试运行成功后,我们将扩展该功能。下一个合乎逻辑的步骤是将其转换为一个多步骤的注册表,就像大多数向导的情况一样。带有姓名和密码的第一步保持不变。在第二步,我们将获取额外的信息,如年龄和电话。然后,我们将用这些额外的字段更新我们的域对象。
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
}
However, this time we’ll notice that the previous test fails. This is because we aren’t passing in the age and phone fields, which are still not in the picture on the UI. To support this behavior, we’ll need group validation and the @Validated annotation.
然而,这一次我们会注意到,之前的测试失败了。这是因为我们没有传入年龄和电话字段,这些字段仍然没有出现在UI上。为了支持这种行为,我们需要分组验证和@Validated注解。
For this, we need to group the fields creating two distinct groups. First, we’ll need to create two marker interfaces, a separate one for each group or each step. We can refer to our article on group validation for the exact implementation of this. Here, let’s focus on the differences in the annotations.
为此,我们需要对字段进行分组,创建两个不同的组。首先,我们需要创建两个标记接口,每个组或每个步骤都有一个单独的接口。我们可以参考我们关于group validation的文章,以了解这方面的具体实现。在这里,让我们把重点放在注释的差异上。
We’ll have the BasicInfo interface for the first step, and the AdvanceInfo for the second step. Furthermore, we’ll update our UserAccount class to use these marker interfaces:
我们将有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
}
In addition, we’ll update our controller to use the @Validated annotation instead of @Valid:
此外,我们将更新我们的控制器,使用@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";
}
As a result of this update, our test now runs successfully. We’ll also test this new method:
作为这一更新的结果,我们的测试现在成功运行。我们还将测试这个新方法。
@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());
}
This too runs successfully. Therefore, we can see how the usage of @Validated is essential for group validation.
这也能成功运行。因此,我们可以看到@Validated的使用对于组验证是多么的重要。
Next, let’s see how @Valid is essential to trigger the validation of nested attributes.
接下来,让我们看看@Valid是如何触发嵌套属性的验证的。
4. Using @Valid Annotation to Mark Nested Objects
4.使用@Valid注释来标记嵌套对象
The @Valid annotation is used to mark nested attributes, in particular. This triggers the validation of the nested object. For instance, in our current scenario, we can create a UserAddress object:
@Valid注解被用来标记嵌套属性,特别是。这将触发对嵌套对象的验证。例如,在我们目前的情况下,我们可以创建一个UserAddress对象。
public class UserAddress {
@NotBlank
private String countryCode;
// standard constructors / setters / getters / toString
}
To ensure validation of this nested object, we’ll decorate the attribute with the @Valid annotation:
为了确保这个嵌套对象的验证,我们将用@Valid注解来装饰这个属性。
public class UserAccount {
//...
@Valid
@NotNull(groups = AdvanceInfo.class)
private UserAddress useraddress;
// standard constructors / setters / getters / toString
}
5. Pros and Cons
5.优点和缺点
Let’s look at some of the pros and cons of using the @Valid and @Validated annotations in Spring.
让我们看看在Spring中使用@Valid和@Validated注解的一些优点和缺点。
The @Valid annotation ensures the validation of the whole object. Importantly, it performs the validation of the whole object graph. However, this creates issues for scenarios needing only partial validation.
@Valid注解确保了整个对象的验证。重要的是,它执行了整个对象图的验证。然而,这为只需要部分验证的场景带来了问题。
On the other hand, we can use @Validated for group validation, including the above partial validation. However, in this instance, the validated entities have to know the validation rules for all the groups or use-cases they’re used in. Here we’re mixing concerns, which can result in an anti-pattern.
另一方面,我们可以使用@Validated进行组验证,包括上述的部分验证。然而,在这种情况下,被验证的实体必须知道它们被用于所有组或用例的验证规则。在这里,我们正在混合关注点,这可能会导致反模式的出现。
6. Conclusion
6.结语
In this brief article, we explored the key differences between the @Valid and @Validated Annotations.
在这篇简短的文章中,我们探讨了@Valid和@Validated注释之间的主要区别。
To conclude, for any basic validation, we’ll use the JSR @Valid annotation in our method calls. On the other hand, for any group validation, including group sequences, we’ll need to use Spring’s @Validated annotation in our method call. The @Valid annotation is also needed to trigger the validation of nested properties.
最后,对于任何基本验证,我们将在方法调用中使用JSR @Valid 注解。另一方面,对于任何分组验证,包括分组序列,我们需要在方法调用中使用Spring的@Validated注解。@Valid注解也需要用来触发嵌套属性的验证。
As always, the code presented in this article is available over on GitHub.
一如既往,本文介绍的代码可在GitHub上获得over。