在本文中,我们将讨论如何使用Bean Validation 2.0(JSR-380)定义和验证方法约束。
在前面的文章中(Java Bean 基础验证),我们讨论了JSR-380及其内置的注释,以及如何实现属性验证。
在这里,我们将重点讨论不同类型的方法约束,例如:
另外,我们将看看如何使用Spring Validator手动和自动验证约束。
对于以下示例,我们需要与Java Bean验证基础中完全相同的依赖关系 。
首先,我们将首先讨论如何声明方法参数的约束和方法返回的值。
如前所述,我们可以使用来自javax.validation.constraints的注解 ,但我们也可以指定自定义约束(例如,对于自定义约束或交叉参数约束)。
定义单个参数的限制很简单。我们只需要根据需要为每个参数添加注解:
public void createReservation(@NotNull @Future LocalDate begin,
@Min(1) int duration, @NotNull Customer customer) {
// ...
}
同样,我们可以对构造函数使用相同的方法:
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
}
在某些情况下,我们可能需要一次验证多个值,例如,两个数值比另一个大一些。
对于这些场景,我们可以定义自定义跨参数约束,这可能取决于两个或更多参数。
交叉参数约束可以被认为是等同于类级约束的方法验证。我们可以使用两者来基于多个属性来实现验证。
让我们考虑一个简单的例子:上一节中的createReservation()方法的变体 接受两个LocalDate类型的参数: 开始日期和结束日期。
因此,我们要确保开始是在未来,结束是在开始之后。与前面的例子不同,我们不能用单个参数约束来定义它。
相反,我们需要一个交叉参数约束。
与单参数约束相反,交叉参数约束在方法或构造函数中声明:
@ConsistentDateParameters
public void createReservation(LocalDate begin,
LocalDate end, Customer customer) {
// ...
}
要实现@ConsistentDateParameters约束,我们需要两个步骤。
首先,我们需要定义约束注解:
@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 {};
}
这里,这三个属性对于约束注释是强制性的:
有关如何定义自定义约束的详细信息,请查看 官方文档。
之后,我们可以定义验证器类:
@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]);
}
}
正如我们所看到的,isValid() 方法包含实际的验证逻辑。首先,我们确保我们得到两个类型为LocalDate的参数。 之后,我们检查两者是否在未来,结束是在开始之后。
此外,值得注意的是,很重要 @SupportedValidationTarget(ValidationTarget 。参数)的注解 ConsistentDateParameterValidator类是必需的。原因是因为 @ConsistentDateParameter 是在方法级别上设置的,但是约束条件应该应用于方法参数(而不是方法的返回值,我们将在下一节讨论)。
注意:Bean验证规范建议将空值视为有效。如果null不是有效值,则应该使用@NotNull -annotation。
有时我们需要验证一个对象,因为它是由方法返回的。为此,我们可以使用返回值约束。
以下示例使用内置约束:
public class ReservationManagement {
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers() {
return null;
}
}
对于getAllCustomers(),以下约束条件适用:
在某些情况下,我们可能还需要验证复杂的对象:
public class ReservationManagement {
@ValidReservation
public Reservation getReservationsById(int id) {
return null;
}
}
在这个例子中,返回的Reservation 对象必须满足由@ValidReservation定义的约束,我们将在下面定义它。
再次,我们首先必须定义约束注释:
@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 {};
}
之后,我们定义类验证器:
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);
}
}
由于我们之前在我们的ValidReservation接口中定义了 METHOD和CONSTRUCTOR作为目标,我们也可以注释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
}
最后,Bean Validation API使我们不仅可以验证单个对象,还可以使用所谓的级联验证验证对象图。
因此,如果我们想验证复杂的对象,我们可以使用@Valid进行级联验证。这适用于方法参数以及返回值。
假设我们有一个带有一些属性约束的Customer类:
public class Customer {
@Size(min = 5, max = 200)
private String firstName;
@Size(min = 5, max = 200)
private String lastName;
// constructor, getters and setters
}
一个预定类可能有一个客户的财产,以及与约束进一步特性:
public class Reservation {
@Valid
private Customer customer;
@Positive
private int room;
// further properties, constructor, getters and setters
}
如果我们现在引用Reservation作为方法参数,我们可以强制所有属性的递归验证:
public void createNewCustomer(@Valid Reservation reservation) {
// ...
}
我们可以看到,我们在两个地方使用@Valid:
这也适用于返回预留类型对象的方法 :
@Valid
public Reservation getReservationById(int id) {
return null;
}
在上一节中声明约束之后,我们现在可以继续实际验证这些约束。为此,我们有多种方法。
Spring Validation提供了与Hibernate Validator的集成。
注意:Spring验证基于AOP,并使用Spring AOP作为默认实现。因此,验证只适用于方法,但不适用于构造函数。
如果我们现在希望Spring自动验证我们的约束,我们必须做两件事:
首先,我们必须使用@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;
}
}
其次,我们必须提供一个MethodValidationPostProcessor bean:
@Configuration
@ComponentScan({ "org.baeldung.javaxval.methodvalidation.model" })
public class MethodValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
如果违反约束,容器现在将抛出 javax.validation.ConstraintViolationException。
如果我们使用Spring Boot,只要hibernate-validator在类路径中,容器就会为我们注册一个 MethodValidationPostProcessor bean 。
从版本1.1开始,Bean Validation与CDI(用于Java EE的上下文和依赖注入)协同工作。
如果我们的应用程序在Java EE容器中运行,那么容器将在调用时自动验证方法约束。
对于 独立Java应用程序中的手动方法验证,我们可以使用 javax.validation.executable.ExecutableValidator接口。
我们可以使用以下代码检索实例:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
ExecutableValidator提供了四种方法:
验证我们的第一个方法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);
注意:官方文档不鼓励直接从应用程序代码调用此接口,而是通过方法拦截技术(如AOP或代理)来使用它。
如果您有兴趣如何使用 ExecutableValidator接口,可以查看官方文档。
在本教程中,我们快速浏览了如何使用Hibernate Validator的方法约束,同时我们还讨论了JSR-380的一些新功能。
首先,我们讨论了如何声明不同类型的约束:
我们还看了如何使用Spring Validator手动和自动验证约束。
https://www.leftso.com/article/390.html