Ограничение определяется комбинацией самой аннотации и нуля или более классов реализации. Классы реализации указываются элементом validatedBy в @Constraint (как показано в листинге 3.2). Листинг 3.3 демонстрирует класс реализации для аннотации @NotNull. Как видите, он реализует интерфейс ConstraintValidator и использует дженерики для передачи имени аннотации (NotNull) и типа, к которому применяется аннотация (в данном случае это Object).
public class NotNullValidator implements ConstraintValidator<NotNull, Object> {
··public void initialize(NotNull parameters) {
··}
··public boolean isValid(Object object, ConstraintValidatorContext context) {
····return object!= null;
··}
}
Интерфейс ConstraintValidator определяет два метода, которые обязательны для реализации конкретными классами.
• initialize — вызывается поставщиком валидации компонентов еще до применения какого-либо ограничения. Именно здесь мы обычно инициализируем любые параметры ограничения, если они имеются.
• isValid — здесь реализуется валидационный алгоритм. Метод интерпретируется поставщиком валидации компонентов всякий раз, когда проверяется конкретное значение. Метод возвращает false, если значение является недопустимым, в противном случае — true. Объект ConstraintValidatorContext несет информацию и операции, доступные в том контексте, к которому применяется ограничение.
Реализация ограничения выполняет валидацию заданной аннотации для указанного типа. В листинге 3.3 ограничение @NotNull типизируется к Object (это означает, что данное ограничение может использоваться с любым типом данных). Но у вас может быть и такая ограничивающая аннотация, которая применяет различные алгоритмы валидации в зависимости от того, об обработке какого типа данных идет речь. Например, вы можете проверять максимальное количество символов для String, максимальное количество знаков для BigDecimal или максимальное количество элементов для Collection. Обратите внимание: в следующей аннотации у нас несколько реализаций одной и той же аннотации (@Size), но она используется с разными типами данных (String, BigDecimal и Collection<?>):
public class SizeValidatorForString·····implements<Size, String>·····{…}
public class SizeValidatorForBigDecimal implements<Size, BigDecimal> {…}
public class SizeValidatorForCollection implements<Size, Collection<?>> {…}
Когда у вас есть аннотация и ее реализация, вы можете применять ограничение с элементом заданного типа (атрибут, конструктор, параметр, возвращаемое значение, компонент, интерфейс или аннотация). Это решение, которое разработчик принимает на этапе проектирования и реализует с помощью метааннотации @Target(ElementType.*) (см. листинг 3.2). Существуют следующие типы:
• FIELD — для ограниченных атрибутов;
• METHOD — для ограниченных геттеров и возвращаемых значений ограниченных методов;
• CONSTRUCTOR — для возвращаемых значений ограниченных конструкторов;
• PARAMETER — для параметров ограниченных методов и конструкторов;
• TYPE — для ограниченных компонентов, интерфейсов и суперклассов;
• ANNOTATION_TYPE — для ограничений, состоящих из других ограничений.
Как видите, ограничивающие аннотации могут применяться с большинством типов элементов, определяемых в Java. Только статические поля и статические методы не могут проверяться валидацией компонентов. В листинге 3.4 показан класс Order, где ограничивающие аннотации применяются к самому классу, атрибутам, конструктору и бизнес-методу.
@ChronologicalDates
public class Order {
··@NotNull @Pattern(regexp = "[C,D,M][A-Z][0–9]*")
··private String orderId;
··private Date creationDate;
··@Min(1)
··private Double totalAmount;
··private Date paymentDate;
··private Date deliveryDate;
··private List<OrderLine> orderLines;
··public Order() {
··}
··public Order(@Past Date creationDate) {
····this.creationDate = creationDate;
··}
··public @NotNull Double calculateTotalAmount(@GreaterThanZero Double changeRate) {
····//…
··}
··// Геттеры и сеттеры
}
В листинге 3.4 @ChronologicalDates — это ограничение, действующее на уровне класса и основанное на нескольких свойствах класса Order (в данном случае оно гарантирует, что значения creationDate, paymentDate и deliveryDate являются хронологическими). Атрибут orderId имеет два ограничения: он не может быть равен нулю (@NotNull) и должен соответствовать шаблону регулярного выражения (@Pattern). Конструктор Order гарантирует, что параметр creationDate должен обозначать момент в прошлом. Метод calculateTotalAmount (рассчитывающий общую сумму заказа на покупку) проверяет, является ли changeRate значением больше нуля — @GreaterThanZero, а также гарантирует, что возвращаемая сумма будет ненулевой.