leftso 2861 0 2018-04-05 10:42:34

文章位置:左搜> 编程技术> 正文

1.概述

在本文中,我们将讨论如何使用Bean Validation 2.0(JSR-380)定义和验证方法约束。

在前面的文章中(Java Bean 基础验证),我们讨论了JSR-380及其内置的注释,以及如何实现属性验证。

在这里,我们将重点讨论不同类型的方法约束,例如:

  • 单参数约束
  • 交叉复杂参数(自定义Java Bean验证注解)
  • 返回约束

另外,我们将看看如何使用Spring Validator手动和自动验证约束。

对于以下示例,我们需要与Java Bean验证基础中完全相同的依赖关系 。
 

2.方法约束声明

首先,我们将首先讨论如何声明方法参数的约束和方法返回的值

如前所述,我们可以使用来自javax.validation.constraints的注解  ,但我们也可以指定自定义约束(例如,对于自定义约束或交叉参数约束)。

2.1。单参数约束

定义单个参数的限制很简单。我们只需要根据需要为每个参数添加注解

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
}

2.2。使用交叉复杂参数约束

在某些情况下,我们可能需要一次验证多个值,例如,两个数值比另一个大一些。

对于这些场景,我们可以定义自定义跨参数约束,这可能取决于两个或更多参数。

交叉参数约束可以被认为是等同于类级约束的方法验证。我们可以使用两者来基于多个属性来实现验证。

让我们考虑一个简单的例子:上一节中的createReservation()方法的变体  接受两个LocalDate类型的参数  开始日期和结束日期。

因此,我们要确保开始是在未来,结束是在开始之后。与前面的例子不同,我们不能用单个参数约束来定义它。

相反,我们需要一个交叉参数约束。

与单参数约束相反,交叉参数约束在方法或构造函数中声明

@ConsistentDateParameters
public void createReservation(LocalDate begin, 
  LocalDate end, Customer customer) {
 
    // ...
}

2.3。创建交叉参数约束

要实现@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 {};
}

这里,这三个属性对于约束注释是强制性的:

  1. message返回创建错误消息的默认密钥,这使我们能够使用消息插值
  2. groups   - 允许我们为约束指定验证组
  3. payload  - 可由Bean Validation API的客户端使用,以将自定义有效载荷对象分配给约束

有关如何定义自定义约束的详细信息,请查看  官方文档

之后,我们可以定义验证器类:

@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。

2.4。返回值约束

有时我们需要验证一个对象,因为它是由方法返回的。为此,我们可以使用返回值约束。

以下示例使用内置约束:

public class ReservationManagement {
 
    @NotNull
    @Size(min = 1)
    public List<@NotNull Customer> getAllCustomers() {
        return null;
    }
}

对于getAllCustomers(),以下约束条件适用:

  • 首先,返回的列表不能为空,且必须至少有一个条目
  • 此外,该列表不能包含条目

2.5。返回值自定义约束

在某些情况下,我们可能还需要验证复杂的对象:

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

2.6。在构造函数中返回值

由于我们之前在我们的ValidReservation接口中定义了  METHODCONSTRUCTOR作为目标我们也可以注释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。级联验证

最后,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

  • 保留参数上:当createNewCustomer()被调用时,它触发Reservation- object 的验证
  • 由于我们在这里有一个嵌套的对象图,我们还必须在customer- attribute上添加一个@Valid:因此,它会触发验证这个嵌套属性

这也适用于返回预留类型对象的方法  :

@Valid
public Reservation getReservationById(int id) {
    return null;
}

3.验证方法约束

在上一节中声明约束之后,我们现在可以继续实际验证这些约束。为此,我们有多种方法。

3.1。使用Spring自动验证

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  。

3.2。CDI自动验证(JSR-365)

从版本1.1开始,Bean Validation与CDI(用于Java EE的上下文和依赖注入)协同工作。

如果我们的应用程序在Java EE容器中运行,那么容器将在调用时自动验证方法约束。

3.3。程序化验证

对于  独立Java应用程序中的手动方法验证,我们可以使用  javax.validation.executable.ExecutableValidator接口。

我们可以使用以下代码检索实例:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();

ExecutableValidator提供了四种方法:

  • 用于方法验证的validateParameters()validateReturnValue()
  • 用于构造函数验证的validateConstructorParameters()validateConstructorReturnValue()

验证我们的第一个方法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接口,可以查看官方文档

4。总结

在本教程中,我们快速浏览了如何使用Hibernate Validator的方法约束,同时我们还讨论了JSR-380的一些新功能。

首先,我们讨论了如何声明不同类型的约束:

  • 单参数约束
  • 跨参数
  • 返回值约束

我们还看了如何使用Spring Validator手动和自动验证约束。