Java Platform, Enterprise Edition (Java EE) 8
Учебник по Java EE

Назад Вперёд Содержание

Создание пользовательских ограничений

Bean Validation определяет аннотации, интерфейсы и классы чтобы позволить разработчикам создавать собственные ограничения.

Здесь рассматриваются следующие темы:

Использование предустановленных ограничений для создания нового ограничения

Bean Validation включает несколько предустановленных ограничений, которые можно комбинировать для создания новых, повторно используемых ограничений. Это может упростить определение ограничения, позволяя разработчикам определять пользовательское ограничение, состоящее из нескольких предустановленных ограничений, которые затем могут быть применены к атрибутам компонента одной аннотацией.

@Pattern.List({
  /* Формат номера “+1-NNN-NNN-NNNN” */
@Pattern(regexp = “\\+1-\\d{3}-\\d{3}-\\d{4})

@Constraint(validatedBy = {})
@Documented
@Target({ElementType.METHOD,
    ElementType.FIELD,
    ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR,
    ElementType.PARAMETER
    ElementType.Type_Use})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(List.class)
public @interface USPhoneNumber {

String message() default "Not a valid US Phone Number";

Class[] groups() default {};

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


  @Target({ElementType.METHOD,
     ElementType.FIELD,
     ElementType.ANNOTATION_TYPE,
     ElementType.CONSTRUCTOR,
     ElementType.PARAMETER
     ElementType.Type_Use })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
USPhoneNumber[] value();

}

}

Вы также можете реализовать Constraint Validator для валидации ограничения @USPhoneNumber. Для получения дополнительной информации об использовании Constraint Validator см. javax.validation.ConstraintValidator.

@USPhoneNumber
protected String phone;

Устранение неоднозначности в целях ограничений

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

@Constraint(validatedBy=MyConstraintValidator.class)
@Target({ METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface MyConstraint {
  String message() default "{com.example.constraint.MyConstraint.message}";
  Class[] groups() default {};
  ConstraintTarget validationAppliesTo() default ConstraintTarget.PARAMETERS;
...
}

Это ограничение по умолчанию устанавливает цель validationAppliesTo для параметров метода.

@MyConstraint(validationAppliesTo=ConstraintTarget.RETURN_TYPE)
public String doSomething(String param1, String param2) { ... }

В предыдущем примере целью является возвращаемое значение метода.

Реализация временны́х ограничений с помощью ClockProvider

В Bean Validation 2.0 объект Clock доступен для реализаций валидатора для валидации любых временны́х ограничений даты или времени.

ValidatorFactory validatorFactory = Validation.
	buildDefaultValidatorFactory();
ClockProvider clockProvider = validatorFactory.getClockProvider();
java.time.Clock Clock = clockProvider.getClock();

Вы также можете зарегистрировать пользовательский ClockProvider с помощью ValidatorFactory:

// Регистрирация реализации кастомного провайдера часов с помощью фабрики
ValidatorFactory factory = Validation
       .byDefaultProvider().configure()
          .clockProvider( new CustomClockProvider() )
          .buildValidatorFactory();

// Получение и использование кастомного провайдера часов и часов в реализации валидатора
public class CustomConstraintValidator implements ConstraintValidator<CustomConstraint, Object> {

    public boolean isValid(Object value, ConstraintValidatorContext context){
        java.time.Clock clock = context.getClockProvider().getClock();
        ...
        ...

    }
}

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

Рассмотрим сотрудника в фирме, расположенной в США. Когда вы регистрируете номер телефона сотрудника или изменяете номер телефона, его необходимо проверить, чтобы убедиться, что номер телефона соответствует шаблону номера телефона в США.

public class Employee extends Person {

  @USPhoneNumber
  protected String phone;

  public Employee(String name, String phone, int age){
    super(name, age);
    this.phone = phone;
  }

  public String getPhone() {
    return phone;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

Определение ограничения @USPhoneNumber определено в примере, приведённом в разделе Использование предустановленных ограничений для создания нового ограничения. В этом примере другое ограничение @Pattern используется для проверки номера телефона.

Использование предустановленных экстракторов значений в кастомных контейнерах

Каскадная валидация:

Bean Validation поддерживает каскадную валидацию для различных объектов. Вы можете указать @Valid для члена объекта, который подлежит валидации, чтобы гарантировать, что член также валидируется каскадным способом. Вы можете проверить типы аргументов, например, параметризованные типы и их члены, если члены имеют указанную аннотацию @Valid.

public class Department {
    private List<@Valid Employee> employeesList;
}

Указывая @Valid для параметризованного типа, когда проверяется объект Department, все элементы, такие как Employee в employeeList также проверены. В этом примере «phone» каждого сотрудника проверяется на соответствие ограничению @USPhoneNumber.

Для получения дополнительной информации см. https://javaee.github.io/javaee-spec/javadocs/

Экстрактор значений:

При проверке объекта или графа объекта может потребоваться проверка ограничений в параметризованных типах контейнера. Чтобы проверить элементы контейнера, валидатор должен извлечь значения этих элементов в контейнере. Например, чтобы проверить значения элементов List на соблюдение одного или нескольких ограничений, таких как List<@NotOnVacation Employee>, или применить каскадную валидацию к List<@Valid Employee>, вам нужен экстрактор значений для контейнера List.

Bean Validation предоставляет предустановленные экстракторы значений для наиболее часто используемых типов контейнеров, таких как List, Iterable и другие. Однако также возможно создать и зарегистрировать реализации извлечения значений для кастомных типов контейнеров или переопределить предустановленные реализации извлечения значений.

Рассмотрим статистический калькулятор для группы сущностей «Персона», а «Сотрудник» является одной из дочерних типов сущности «Персона».

public class StatsCalculator<T extends Person> {

  /* Каскадная валидация, а также ограничение @NotNull */
  private List<@NotNull @Valid T> members = new ArrayList<T>();


  public void addMember(T member) {
    members.add(member);
  }

  public boolean removeMember(T member) {
    return members.remove(member);
  }

  public int getAverageAge() {

    if (members.size() == 0)
      return 0;

    short sum = 0;
    for (T member : members) {
      if(member != null) {
        sum += member.getAge();
      }
    }
    return sum / members.size();
  }

  public int getOldest() {
    int oldest = -1;

    for (T member : members) {
      if(member != null) {
        if (member.getAge() > oldest) {
          oldest = member.getAge();
        }
      }
    }
    return oldest;
  }

Когда StatsCalculator проверяется, поле «members» также проверяется. Предустановленный экстрактор значений для List используется для извлечения значений List для валидации элементов в List. В случае списка сотрудников валидируется каждый элемент «Сотрудник». Например, «phone» сотрудника проверяется с использованием ограничения @USPhoneNumber.

В следующем примере рассмотрим StatisticsPrinter, который печатает и отображает статистику на экране.

public class StatisticsPrinter {
    private StatsCalculator<@Valid Employee> calculator;

    public StatisticsPrinter(StatsCalculator<Employee> statsCalculator){
      this.calculator = statsCalculator;
    }

    public void displayStatistics(){
      // Использование StatsCalculator, получение статистики, её форматирование и отображение.
    }

    public void printStatistics(){
      // Использование StatScalculator, получение статистики, её форматирование и печать.
    }

  }

Контейнер StatisticsPrinter использует StatisticsCalculator. Когда StatisticsPrinter валидируется, StatisticsCalculator также валидируется с помощью каскадной валидации, такой как аннотация @Valid. Однако для получения значений типа контейнера StatsCalculator требуется экстрактор значений. Реализация ValueExtractor для StatsCalculator выглядит следующим образом:

public class ExtractorForStatsCalculator implements ValueExtractor<StatsCalculator<@ExtractedValue ?>>{

    @Override
    public void extractValues(StatsCalculator<@ExtractedValue ?> statsCalculator,
        ValueReceiver valueReceiver) {
        /* Здесь выполняется простое извлечение значения.
           При необходимости его можно изменить. */
      valueReceiver.value("<extracted value>", statsCalculator);
    }
  }

Существует несколько механизмов для регистрации ValueExtractor в Bean Validation. См. раздел «Регистрация ValueExtractor» в спецификации Bean Validation http://www.jcp.org/en/jsr/detail?id=380. Одним из механизмов является регистрация экстрактора значений в контексте проверки компонентов.

ValidatorFactory validatorFactory = Validation
        .buildDefaultValidatorFactory();

    ValidatorContext context = validatorFactory.
        usingContext()
        .addValueExtractor(new ExtractorForStatsCalculator());


    Validator validator = context.getValidator();

Используя этот валидатор, StatisticsPrinter проверяется в следующей последовательности операций:

  1. StatisticsPrinter валидный.

    1. Валидируются члены StatisticsPrinter, которым требуется каскадная валидация.

    2. Для типов контейнеров определяется экстрактор значений. В случае StatsCalculator, ExtractorForStatsCalculator найден, а затем получены значения для валидации.

    3. Валидируются StatsCalculator и его члены, такие как List.

    4. Предустановленный ValueExtractor для java.util.List используется для получения значений элементов списка и их валидации. В этом случае валидируются Сотрудник и поле «phone», аннотированные ограничением @USPhoneNumber.


Назад Вперёд Содержание
Логотип Oracle  Copyright © 2017, Oracle и/или её дочерних компаний. Все права защищены. Версия перевода 1.0.5 (Java EE Tutorial — русскоязычная версия)