@NotNull
@Size(min = 7)
@Pattern(regexp = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+
(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*"
········+ "@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*
········[a-z0-9])?")
@Constraint(validatedBy = {})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Email {
··String message() default "Неверный электронный адрес";
··Class<?>[] groups() default {};
··Class<? extends Payload>[] payload() default {};
}
Все встроенные ограничения (@NotNull, @Size и @Pattern) уже имеют собственные сообщения об ошибках (элемент message()). Таким образом, если у вас окажется нулевой адрес электронной почты, то по результатам валидации ограничение, приведенное в листинге 3.5, выдаст сообщение об ошибке @NotNull, а не то, что вы определили (Неверный электронный адрес). Возможно, вы захотите использовать для всех ограничений Email одно общее сообщение об ошибке, а не несколько разных. Для этого можно добавить аннотацию @ReportAsSingleViolation (как будет показано ниже, в листинге 3.24). Если вы так поступите, то валидация составного ограничения прекратится после невыполнения первого же из входящих в него ограничений и будет выдано сообщение об ошибке, соответствующее именно тому ограничению, которое оказалось невыполненным.
Объединение ограничений полезно, так как подобная практика помогает избежать дублирования кода и способствует переиспользованию сравнительно простых ограничений. В таком случае вы будете скорее создавать простые ограничения, чем составлять из них более сложные валидационные правила.
Создавая новое ограничение, обязательно дайте ему информативное имя. Если название для аннотации будет подобрано правильно, то ограничения будут лучше восприниматься в коде.
Объединение простых ограничений — полезная практика, но, как правило, только ею не обойтись. Часто приходится применять сложные валидационные алгоритмы: проверять значение в базе данных, делегировать определенную валидационную работу вспомогательным классам и т. д. Именно в таких случаях приходится добавлять к вашей ограничивающей аннотации класс реализации.
В листинге 3.6 показан объект POJO, представляющий сетевое соединение с сервером, на котором находятся элементы CD-BookStore. Этот POJO имеет несколько атрибутов типа String, все они представляют URL. Вы хотите, чтобы URL имел допустимый формат, и даже задаете конкретный протокол (например, http, ftp…), хост и/или номер порта. Пользовательское ограничение @URL гарантирует, что разные строковые атрибуты класса ItemServerConnection соответствуют формату URL. Например, атрибут resourceURL может представлять собой любой допустимый URL (в частности, file://www.cdbookstore.com/item/123). С другой стороны, вы хотите ограничить атрибут itemURL так, чтобы он работал только с http-протоколом и с хостом, имя которого начинается с www.cdbookstore.com (например, http://www.cdbookstore.com/book/h2g2).
public class ItemServerConnection {
··@URL
··private String resourceURL;
··@NotNull @URL(protocol = "http", host = "www.cdbookstore.com")
··private String itemURL;
··@URL(protocol = "ftp", port = 21)
··private String ftpServerURL;
··private Date lastConnectionDate;
··// Конструкторы, геттеры, сеттеры
}
Если мы хотим создать такое пользовательское ограничение для работы с URL, то первым делом должны определить аннотацию. В листинге 3.7 показана аннотация, выполняющая все предпосылки для валидации компонентов (метааннотация @Constraint, атрибуты message, groups и payload). Кроме того, она добавляет и специфические атрибуты: protocol, host и port, которые отображаются на имена элементов аннотации (например, @URL(protocol = "http")). Ограничение может использовать любой атрибут любого типа данных. Обратите также внимание, что у этих атрибутов есть значения, задаваемые по умолчанию, — например, пустая строка для протокола или хоста или –1 для номера порта.