«1. Введение

В этой статье мы рассмотрим реализацию библиотеки Apache BVal спецификации Java Bean Validation (JSR 349).

2. Зависимости Maven

Чтобы использовать Apache BVal, нам сначала нужно добавить следующие зависимости в наш файл pom.xml:

<dependency>
    <groupId>org.apache.bval</groupId>
    <artifactId>bval-jsr</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>

Пользовательские ограничения BVal можно найти в необязательных bval-extras зависимость:

<dependency>
    <groupId>org.apache.bval</groupId>
    <artifactId>bval-extras</artifactId>
    <version>1.1.2</version>
</dependency>

Последние версии bval-jsr, bval-extras и validation-api можно загрузить с Maven Central.

3. Применение ограничений

Apache BVal предоставляет реализации для всех ограничений, определенных в пакете javax.validation. Чтобы применить ограничение к свойству компонента, мы можем добавить аннотацию ограничения к объявлению свойства.

Давайте создадим класс User с четырьмя атрибутами, затем применим аннотации @NotNull, @Size и @Min: нам нужно получить экземпляр ValidatorFactory и один или несколько экземпляров Validator.

public class User {
    
    @NotNull
    private String email;
    
    private String password;

    @Size(min=1, max=20)
    private String name;

    @Min(18)
    private int age;

    // standard constructor, getters, setters
}

4.1. Получение ValidatorFactory

В документации Apache BVal рекомендуется получить один экземпляр этого класса, поскольку создание фабрики — сложный процесс:

4.2. Получение валидатора

Далее нам нужно получить экземпляр валидатора из фабрики валидаторов, определенной выше:

ValidatorFactory validatorFactory 
  = Validation.byProvider(ApacheValidationProvider.class)
  .configure().buildValidatorFactory();

Это поточно-безопасная реализация, поэтому мы можем безопасно повторно использовать уже созданные экземпляры.

Класс Validator предлагает три метода для определения достоверности bean-компонента: validate(), validateProperty() и validateValue().

Validator validator = validatorFactory.getValidator();

Каждый из этих методов возвращает набор объектов ConstraintViolation, содержащих информацию об ограничении, которое не было соблюдено.

4.3. API validate()

Метод validate() проверяет правильность всего bean-компонента, что означает, что он проверяет все ограничения, применяемые к свойствам объекта, который передается в качестве параметра.

Давайте создадим тест JUnit, в котором мы определяем объект User и используем метод validate() для проверки его свойств:

4.4. API validateProperty()

Метод validateProperty() можно использовать для проверки одного свойства компонента.

@Test
public void givenUser_whenValidate_thenValidationViolations() {
    User user
      = new User("[email protected]", "pass", "nameTooLong_______________", 15);

    Set<ConstraintViolation<User>> violations = validator.validate(user);
    assertTrue("no violations", violations.size() > 0);
}

Давайте создадим тест JUnit, в котором мы определим объект User со свойством age меньше требуемого минимального значения 18 и проверим, что проверка этого свойства приводит к одному нарушению:

4.5. API validateValue()


Метод validateValue() можно использовать для проверки того, является ли какое-либо значение допустимым значением свойства компонента, прежде чем устанавливать его в компоненте.

@Test
public void givenInvalidAge_whenValidateProperty_thenConstraintViolation() {
    User user = new User("[email protected]", "pass", "Ana", 12);

    Set<ConstraintViolation<User>> propertyViolations
      = validator.validateProperty(user, "age");
 
    assertEquals("size is not 1", 1, propertyViolations.size());
}

Давайте создадим тест JUnit с объектом User, а затем убедимся, что значение 20 является допустимым значением для свойства age:

4.6. Закрытие ValidatorFactory

После использования ValidatorFactory мы должны не забыть закрыть его в конце:

@Test
public void givenValidAge_whenValidateValue_thenNoConstraintViolation() {
    User user = new User("[email protected]", "pass", "Ana", 18);
    
    Set<ConstraintViolation<User>> valueViolations
      = validator.validateValue(User.class, "age", 20);
 
    assertEquals("size is not 0", 0, valueViolations.size());
}

5. Ограничения, не относящиеся к JSR

Библиотека Apache BVal также предоставляет ряд ограничений, которые не являются частью спецификации JSR и предоставляют дополнительные и более мощные возможности проверки.

if (validatorFactory != null) {
    validatorFactory.close();
}

Пакет bval-jsr содержит два дополнительных ограничения: @Email для проверки действительного адреса электронной почты и @NotEmpty для проверки того, что значение не является пустым.

Остальные пользовательские ограничения BVal предоставляются в дополнительном пакете bval-extras.

Этот пакет содержит ограничения для проверки различных числовых форматов, таких как аннотация @IBAN, которая гарантирует, что номер является действительным номером международного банковского счета, аннотация @Isbn, которая проверяет действительный стандартный номер книги, и аннотация @EAN13 для проверки Международный артикул.

Библиотека также предоставляет аннотации для проверки правильности различных типов номеров кредитных карт: @AmericanExpress, @Diners, @Discover, @Mastercard и @Visa.

Вы можете определить, содержит ли значение действительный домен или Интернет-адрес, используя аннотации @Domain и @InetAddress.


И, наконец, пакет содержит аннотации @Directory и @NotDirectory для проверки того, является ли объект File каталогом или нет.

«Давайте определим дополнительные свойства в нашем классе User и применим к ним некоторые аннотации, отличные от JSR:

Ограничения можно проверить аналогично ограничениям JSR:

Хотя эти дополнительные аннотации удобны для потенциальных потребностей проверки, один недостаток использования аннотаций, которые не являются частью спецификации JSR, заключается в том, что вы не можете легко переключиться на другую реализацию JSR, если это потребуется позже.

public class User {
    
    @NotNull
    @Email
    private String email;
    
    @NotEmpty
    private String password;

    @Size(min=1, max=20)
    private String name;

    @Min(18)
    private int age;
    
    @Visa
    private String cardNumber = "";
    
    @IBAN
    private String iban = "";
    
    @InetAddress
    private String website = "";

    @Directory
    private File mainDirectory = new File(".");

    // standard constructor, getters, setters
}

6. Пользовательские ограничения

@Test
public void whenValidateNonJSR_thenCorrect() {
    User user = new User("[email protected]", "pass", "Ana", 20);
    user.setCardNumber("1234");
    user.setIban("1234");
    user.setWebsite("10.0.2.50");
    user.setMainDirectory(new File("."));
    
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user,"iban");
 
    assertEquals("size is not 1", 1, violations.size());
 
    violations = validator.validateProperty(user,"website");
 
    assertEquals("size is not 0", 0, violations.size());

    violations = validator.validateProperty(user, "mainDirectory");
 
    assertEquals("size is not 0", 0, violations.size());
}

Чтобы определить наши собственные ограничения, нам сначала нужно создать аннотацию, следуя стандартному синтаксису.

Давайте создадим аннотацию Password, которая будет определять условия, которым должен удовлетворять пароль пользователя:

Фактическая проверка значения пароля выполняется в классе, который реализует интерфейс ConstraintValidator — в нашем случае, класс PasswordValidator. Этот класс переопределяет метод isValid() и проверяет, меньше ли длина пароля, чем атрибут length, и содержит ли он меньше, чем указанное количество небуквенно-цифровых символов в атрибуте nonAlpha:

Применим наше пользовательское ограничение для свойства пароля класса User:


@Constraint(validatedBy = { PasswordValidator.class })
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {
    String message() default "Invalid password";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int length() default 6;

    int nonAlpha() default 1;
}

Мы можем создать тест JUnit, чтобы убедиться, что недопустимое значение пароля приводит к нарушению ограничения:

public class PasswordValidator 
  implements ConstraintValidator<Password, String> {

    private int length;
    private int nonAlpha;

    @Override
    public void initialize(Password password) {
        this.length = password.length();
        this.nonAlpha = password.nonAlpha();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        if (value.length() < length) {
            return false;
        }
        int nonAlphaNr = 0;
        for (int i = 0; i < value.length(); i++) {
            if (!Character.isLetterOrDigit(value.charAt(i))) {
                nonAlphaNr++;
            }
        }
        if (nonAlphaNr < nonAlpha) {
            return false;
        }
        return true;
    }
}

Теперь давайте создадим тест JUnit, в котором мы проверяем действительное значение пароля:

@Password(length = 8)
private String password;

7. Заключение

@Test
public void givenValidPassword_whenValidatePassword_thenNoConstraintViolation() {
    User user = new User("[email protected]", "password", "Ana", 20);
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user, "password");
 
    assertEquals(
      "message incorrect",
      "Invalid password", 
      violations.iterator().next().getMessage());
}

В этой статье мы продемонстрировали использование реализации проверки бина Apache BVal.

@Test
public void givenValidPassword_whenValidatePassword_thenNoConstraintViolation() {
    User user = new User("[email protected]", "password#", "Ana", 20);
		
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user, "password");
    assertEquals("size is not 0", 0, violations.size());
}

Полный исходный код этой статьи можно найти на GitHub.

«

The complete source code for this article can be found over on GitHub.