Реализация ограничения считается управляемым компонентом. Это означает, что с ней вы можете использовать все сервисы, доступные для обработки управляемых компонентов — в частности, внедрение любого вспомогательного класса, EJB или даже EntityManager (подробнее об этом — в следующих главах). Вы также можете перехватывать или декорировать методы initialize и isValid и даже задействовать управление жизненным циклом (@PostConstruct и @PreDestroy).
Иногда полезно применять одинаковое ограничение к одной и той же цели, используя при этом разные свойства или группы (подробнее об этом ниже). Распространенный пример такого рода — ограничение @Pattern, проверяющее соответствие целевой сущности определенному регулярному выражению. В листинге 3.9 показано, как применить два регулярных выражения к одному и тому же атрибуту. Множественные ограничения используют оператор AND. Это означает, что для валидности атрибута orderId необходимо, чтобы он удовлетворял двум регулярным выражениям.
public class Order {
··@Pattern.List({
······@Pattern(regexp = "[C,D,M][A-Z][0–9]*"),
······@Pattern(regexp = ".[A-Z].*?")
··})
··private String orderId;
··private Date creationDate;
··private Double totalAmount;
··private Date paymentDate;
··private Date deliveryDate;
··private List<OrderLine> orderLines;
··// Конструкторы, геттеры, сеттеры
}
Чтобы иметь возможность несколько раз применить одно и то же ограничение к данной цели, ограничивающая аннотация должна определить массив на основе самой себя. При валидации компонентов такие массивы ограничений обрабатываются по-особому: каждый элемент массива интерпретируется как обычное ограничение. В листинге 3.10 показана ограничивающая аннотация @Pattern, определяющая внутренний интерфейс (произвольно названный List) с элементом Pattern[]. Внутренний интерфейс должен иметь правило хранения RUNTIME и использовать в качестве исходного ограничения один и тот же набор целей (в данном случае METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER).
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = PatternValidator.class)
public @interface Pattern {
··String regexp();
··String message() default "{javax.validation.constraints.Pattern.message}";
··Class<?>[] groups() default {};
··Class<? extends Payload>[] payload() default {};
··// Определяет несколько аннотаций @Pattern, применяемых к одному элементу
··@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
··@Retention(RUNTIME)
··@interface List {
····Pattern[] value();
··}
}
При разработке собственной ограничивающей аннотации следует добавить соответствующую ей аннотацию с множеством значений. Спецификация Bean Validation не требует этого строго, но настоятельно рекомендует определять внутренний интерфейс под названием List.
Выше мы рассмотрели различные способы разработки ограничения, которое применялось бы к атрибуту (или геттеру). Но вы также можете создать ограничение для целого класса. Идея заключается в том, чтобы выразить ограничение на основе нескольких свойств, которыми обладает заданный класс.
В листинге 3.11 показан класс для оформления заказа. Этот заказ товара следует определенному жизненному циклу бизнес-логики: создается в системе, оплачивается клиентом, а потом доставляется клиенту. Класс отслеживает все эти события, оперируя соответствующими датами: creationDate, paymentDate и deliveryDate. Аннотация @ChronologicalDates действует на уровне класса и проверяет, находятся ли эти даты в правильном хронологическом порядке.
@ChronologicalDates
public class Order {
··private String orderId;
··private Double totalAmount;
··private Date creationDate;
··private Date paymentDate;
··private Date deliveryDate;
··private List<OrderLine> orderLines;
··// Конструкторы, геттеры, сеттеры
}