1. Overview
1.概述
In this article, we’ll discuss how to define and validate method constraints using Bean Validation 2.0 (JSR-380).
在这篇文章中,我们将讨论如何使用Bean Validation 2.0(JSR-380)来定义和验证方法约束。
In the previous article, we discussed JSR-380 with its built-in annotations, and how to implement property validation.
在上一篇文章中,我们讨论了JSR-380及其内置注释,以及如何实现属性验证。
Here, we’ll focus on the different types of method constraints such as:
在这里,我们将重点讨论不同类型的方法约束,如:。
- single-parameter constraints
- cross-parameter
- return constraints
Also, we’ll have a look at how to validate the constraints manually and automatically using Spring Validator.
此外,我们将看看如何使用Spring Validator手动和自动验证约束。
For the following examples, we need exactly the same dependencies as in Java Bean Validation Basics.
对于下面的例子,我们需要与Java Bean验证基础中完全相同的依赖关系。
2. Declaration of Method Constraints
2.方法约束的声明
To get started, we’ll first discuss how to declare constraints on method parameters and return values of methods.
为了开始,我们将首先讨论如何声明方法参数和方法返回值的约束。
As mentioned before, we can use annotations from javax.validation.constraints, but we can also specify custom constraints (e. g. for custom constraints or cross-parameter constraints).
如前所述,我们可以使用javax.validation.constraints中的注解,但我们也可以指定自定义的约束(例如用于自定义约束或跨参数约束)。
2.1. Single Parameter Constraints
2.1.单一参数的约束
Defining constraints on single parameters is straightforward. We simply have to add annotations to each parameter as required:
定义单个参数的约束是很直接的。我们只需根据需要为每个参数添加注释。
public void createReservation(@NotNull @Future LocalDate begin,
@Min(1) int duration, @NotNull Customer customer) {
// ...
}
Likewise, we can use the same approach for constructors:
同样地,我们也可以对构造函数使用同样的方法。
public class Customer {
public Customer(@Size(min = 5, max = 200) @NotNull String firstName,
@Size(min = 5, max = 200) @NotNull String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// properties, getters, and setters
}
2.2. Using Cross-Parameter Constraints
2.2.使用跨参数约束
In some cases, we might need to validate multiple values at once, e.g., two numeric amounts being one bigger than the other.
在某些情况下,我们可能需要一次验证多个值,例如,两个数字金额一个比一个大。
For these scenarios, we can define custom cross-parameter constraints, which might depend on two or more parameters.
对于这些情况,我们可以定义自定义的跨参数约束,这可能取决于两个或多个参数。
Cross-parameter constraints can be considered as the method validation equivalent to class-level constraints. We could use both to implement validation based on several properties.
跨参数约束可以被认为是相当于类级约束的方法验证。我们可以使用两者来实现基于几个属性的验证。
Let’s think about a simple example: a variation of the createReservation() method from the previous section takes two parameters of type LocalDate: a begin date and an end date.
让我们想一想一个简单的例子:上一节中的createReservation()方法的变体需要两个LocalDate类型的参数:一个开始日期和一个结束日期。
Consequently, we want to make sure that begin is in the future, and end is after begin. Unlike in the previous example, we can’t define this using single parameter constraints.
因此,我们要确保begin是在未来,而end是在begin之后。与前面的例子不同,我们不能用单参数约束来定义这个。
Instead, we need a cross-parameter constraint.
相反,我们需要一个交叉参数的约束。
In contrast to single-parameter constraints, cross-parameter constraints are declared on the method or constructor:
与单参数约束不同,跨参数约束是在方法或构造函数上声明的。
@ConsistentDateParameters
public void createReservation(LocalDate begin,
LocalDate end, Customer customer) {
// ...
}
2.3. Creating Cross-Parameter Constraints
2.3.创建跨参数约束
To implement the @ConsistentDateParameters constraint, we need two steps.
为了实现@ConsistentDateParameters约束,我们需要两个步骤。
First, we need to define the constraint annotation:
首先,我们需要定义约束注解。
@Constraint(validatedBy = ConsistentDateParameterValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {
String message() default
"End date must be after begin date and both must be in the future";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Here, these three properties are mandatory for constraint annotations:
在这里,这三个属性是约束性注释的必选项。
- message – returns the default key for creating error messages, this enables us to use message interpolation
- groups – allows us to specify validation groups for our constraints
- payload – can be used by clients of the Bean Validation API to assign custom payload objects to a constraint
For details how to define a custom constraint, have a look at the official documentation.
关于如何定义自定义约束的细节,请看官方文档。
After that, we can define the validator class:
之后,我们可以定义验证器类。
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParameterValidator
implements ConstraintValidator<ConsistentDateParameters, Object[]> {
@Override
public boolean isValid(
Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null || value[1] == null) {
return true;
}
if (!(value[0] instanceof LocalDate)
|| !(value[1] instanceof LocalDate)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
return ((LocalDate) value[0]).isAfter(LocalDate.now())
&& ((LocalDate) value[0]).isBefore((LocalDate) value[1]);
}
}
As we can see, the isValid() method contains the actual validation logic. First, we make sure that we get two parameters of type LocalDate. After that, we check whether both are in the future and end is after begin.
我们可以看到,isValid() 方法包含了实际的验证逻辑。首先,我们确保得到两个LocalDate类型的参数。然后,我们检查这两个参数是否在未来,并且end是在begin之后。
Also, it’s important to notice that the @SupportedValidationTarget(ValidationTarget.PARAMETERS) annotation on the ConsistentDateParameterValidator class is required. The reason for this is because @ConsistentDateParameter is set on method-level, but the constraints shall be applied to the method parameters (and not to the return value of the method, as we’ll discuss in the next section).
另外,需要注意的是,@SupportedValidationTarget(ValidationTarget.PARAMETERS)注释在ConsistentDateParameterValidator类上是必需的。原因是@ConsistentDateParameter是在方法级上设置的,但约束条件应适用于方法参数(而不是方法的返回值,我们将在下一节讨论)。
Note: the Bean Validation specification recommends to consider null-values as valid. If null isn’t a valid value, the @NotNull-annotation should be used instead.
注意:Bean验证规范建议将null-值视为有效值。如果null不是一个有效的值,应该使用@NotNull-注释来代替。
2.4. Return Value Constraints
2.4.返回值的限制
Sometimes we’ll need to validate an object as it is returned by a method. For this, we can use return value constraints.
有时我们需要验证一个对象,因为它是由一个方法返回的。为此,我们可以使用返回值约束。
The following example uses built-in constraints:
下面的例子使用了内置的约束。
public class ReservationManagement {
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers() {
return null;
}
}
For getAllCustomers(), the following constraints apply:
对于getAllCustomers(),适用以下约束。
- First, the returned list must not be null and must have at least one entry
- Furthermore, the list must not contain null entries
2.5. Return Value Custom Constraints
2.5.返回值的自定义约束
In some cases, we might also need to validate complex objects:
在某些情况下,我们可能还需要对复杂的对象进行验证。
public class ReservationManagement {
@ValidReservation
public Reservation getReservationsById(int id) {
return null;
}
}
In this example, a returned Reservation object must satisfy the constraints defined by @ValidReservation, which we’ll define next.
在这个例子中,返回的Reservation对象必须满足@ValidReservation所定义的约束条件,我们接下来将定义这个约束条件。
Again, we first have to define the constraint annotation:
同样,我们首先要定义约束注解。
@Constraint(validatedBy = ValidReservationValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ValidReservation {
String message() default "End date must be after begin date "
+ "and both must be in the future, room number must be bigger than 0";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
After that, we define the validator class:
之后,我们定义验证器类。
public class ValidReservationValidator
implements ConstraintValidator<ValidReservation, Reservation> {
@Override
public boolean isValid(
Reservation reservation, ConstraintValidatorContext context) {
if (reservation == null) {
return true;
}
if (!(reservation instanceof Reservation)) {
throw new IllegalArgumentException("Illegal method signature, "
+ "expected parameter of type Reservation.");
}
if (reservation.getBegin() == null
|| reservation.getEnd() == null
|| reservation.getCustomer() == null) {
return false;
}
return (reservation.getBegin().isAfter(LocalDate.now())
&& reservation.getBegin().isBefore(reservation.getEnd())
&& reservation.getRoom() > 0);
}
}
2.6. Return Value in Constructors
2.6.构造函数中的返回值
As we defined METHOD and CONSTRUCTOR as target within our ValidReservation interface before, we can also annotate the constructor of Reservation to validate constructed instances:
由于我们之前在ValidReservation接口中定义了METHOD和CONSTRUCTOR作为target,我们也可以对Reservation的构造函数进行注释,以验证构造的实例。
public class Reservation {
@ValidReservation
public Reservation(
LocalDate begin,
LocalDate end,
Customer customer,
int room) {
this.begin = begin;
this.end = end;
this.customer = customer;
this.room = room;
}
// properties, getters, and setters
}
2.7. Cascaded Validation
2.7.级联验证
Finally, the Bean Validation API allows us to not only validate single objects but also object graphs, using the so-called cascaded validation.
最后,Bean Validation API允许我们不仅验证单个对象,还可以验证对象图,使用所谓的级联验证。
Hence, we can use @Valid for a cascaded validation, if we want to validate complex objects. This works for method parameters as well as for return values.
因此,如果我们想验证复杂的对象,我们可以使用@Valid来进行级联验证。这对方法参数和返回值都适用。
Let’s assume that we have a Customer class with some property constraints:
让我们假设我们有一个Customer类,有一些属性约束。
public class Customer {
@Size(min = 5, max = 200)
private String firstName;
@Size(min = 5, max = 200)
private String lastName;
// constructor, getters and setters
}
A Reservation class might have a Customer property, as well as further properties with constraints:
一个Reservation类可能有一个Customer属性,以及更多带有约束的属性。
public class Reservation {
@Valid
private Customer customer;
@Positive
private int room;
// further properties, constructor, getters and setters
}
If we now reference Reservation as a method parameter, we can force the recursive validation of all properties:
如果我们现在引用Reservation作为方法参数,我们可以强制对所有属性进行递归验证。
public void createNewCustomer(@Valid Reservation reservation) {
// ...
}
As we can see, we use @Valid at two places:
我们可以看到,我们在两个地方使用了@Valid。
- On the reservation-parameter: it triggers the validation of the Reservation-object, when createNewCustomer() is called
- As we have a nested object graph here, we also have to add a @Valid on the customer-attribute: thereby, it triggers the validation of this nested property
This also works for methods returning an object of type Reservation:
这也适用于返回Reservation类型对象的方法。
@Valid
public Reservation getReservationById(int id) {
return null;
}
3. Validating Method Constraints
3.验证方法的约束
After the declaration of constraints in the previous section, we can now proceed to actually validate these constraints. For that, we have multiple approaches.
在上一节中声明了约束条件后,我们现在可以着手实际验证这些约束条件了。为此,我们有多种方法。
3.1. Automatic Validation With Spring
3.1.使用Spring进行自动验证
Spring Validation provides an integration with Hibernate Validator.
Spring验证提供了一个与Hibernate验证器的集成。
Note: Spring Validation is based on AOP and uses Spring AOP as the default implementation. Therefore, validation only works for methods, but not for constructors.
注意:Spring验证是基于AOP的,并且使用Spring AOP作为默认实现。因此,验证只对方法起作用,而对构造函数不起作用。
If we now want Spring to validate our constraints automatically, we have to do two things:
如果我们现在希望Spring自动验证我们的约束,我们必须做两件事。
Firstly, we have to annotate the beans, which shall be validated, with @Validated:
首先,我们必须用@Validated来注解应被验证的bean。
@Validated
public class ReservationManagement {
public void createReservation(@NotNull @Future LocalDate begin,
@Min(1) int duration, @NotNull Customer customer){
// ...
}
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers(){
return null;
}
}
Secondly, we have to provide a MethodValidationPostProcessor bean:
其次,我们必须提供一个MethodValidationPostProcessor bean:。
@Configuration
@ComponentScan({ "org.baeldung.javaxval.methodvalidation.model" })
public class MethodValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
The container now will throw a javax.validation.ConstraintViolationException, if a constraint is violated.
如果违反了约束条件,容器现在将抛出一个javax.validation.ConstraintViolationException。
If we are using Spring Boot, the container will register a MethodValidationPostProcessor bean for us as long as hibernate-validator is in the classpath.
如果我们使用Spring Boot,只要hibernate-validator在classpath中,容器将为我们注册一个MethodValidationPostProcessorbean。
3.2. Automatic Validation With CDI (JSR-365)
3.2.使用CDI进行自动验证(JSR-365)
As of version 1.1, Bean Validation works with CDI (Contexts and Dependency Injection for Jakarta EE).
从1.1版本开始,Bean Validation与CDI(Jakarta EE的上下文和依赖注入)一起工作。
If our application runs in a Jakarta EE container, the container will validate method constraints automatically at the time of invocation.
如果我们的应用程序在Jakarta EE容器中运行,容器将在调用时自动验证方法约束。
3.3. Programmatic Validation
3.3.程序性验证
For manual method validation in a standalone Java application, we can use the javax.validation.executable.ExecutableValidator interface.
对于独立的Java应用程序中的手动方法验证,我们可以使用javax.validation.executable.ExecutableValidator接口。
We can retrieve an instance using the following code:
我们可以使用以下代码检索一个实例。
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
ExecutableValidator offers four methods:
ExecutableValidator提供四种方法。
- validateParameters() and validateReturnValue() for method validation
- validateConstructorParameters() and validateConstructorReturnValue() for constructor validation
Validating the parameters of our first method createReservation() would look like this:
验证我们的第一个方法createReservation()的参数将看起来像这样。
ReservationManagement object = new ReservationManagement();
Method method = ReservationManagement.class
.getMethod("createReservation", LocalDate.class, int.class, Customer.class);
Object[] parameterValues = { LocalDate.now(), 0, null };
Set<ConstraintViolation<ReservationManagement>> violations
= executableValidator.validateParameters(object, method, parameterValues);
Note: The official documentation discourages to call this interface directly from the application code, but to use it via a method interception technology, like AOP or proxies.
注意:官方文档不鼓励从应用程序代码中直接调用该接口,而是通过方法拦截技术,如AOP或代理来使用它。
In case you are interested how to use the ExecutableValidator interface, you can have a look at the official documentation.
如果你对如何使用ExecutableValidator接口感兴趣,你可以看一下官方文档。
4. Conclusion
4.结论
In this tutorial, we had a quick look at how to use method constraints with Hibernate Validator, also we discussed some new features of JSR-380.
在本教程中,我们快速了解了如何使用Hibernate验证器的方法约束,也讨论了JSR-380的一些新特性。
First, we discussed how to declare different types of constraints:
首先,我们讨论了如何声明不同类型的约束。
- Single parameter constraints
- Cross-parameter
- Return value constraints
We also had a look at how to validate the constraints manually and automatically using Spring Validator.
我们还看了一下如何使用Spring Validator手动和自动验证约束。
As always, the full source code of the examples is available over on GitHub.
一如既往,这些示例的完整源代码可在GitHub上获得over。