1. Обзор
Bean Validation — это стандартная спецификация проверки, которая позволяет нам легко проверять объекты предметной области с помощью набора ограничений, объявленных в форме аннотаций .
Хотя в целом использование реализаций проверки компонентов, таких как Hibernate Validator , довольно просто, стоит изучить некоторые тонкие, но важные различия в том, как реализованы некоторые из этих ограничений.
В этом руководстве мы рассмотрим различия между ограничениями @NotNull
, @NotEmpty
и @NotBlank
.
2. Зависимости Maven
Чтобы быстро настроить рабочую среду и протестировать поведение ограничений @NotNull
, @NotEmpty
и @NotBlank
, сначала нам нужно добавить необходимые зависимости Maven.
В этом случае мы будем использовать Hibernate Validator , эталонную реализацию проверки бина, для проверки наших объектов домена.
Вот соответствующий раздел нашего файла pom.xml
:
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
Мы будем использовать JUnit и AssertJ в наших модульных тестах, поэтому обязательно проверьте последние версии hibernate- validator , реализации EL GlassFish , junit и assertj-core на Maven Central.
3. Ограничение @NotNull
Двигаясь вперед, давайте реализуем наивный класс домена UserNotNull и
ограничим его поле имени аннотацией
@NotNull
:
public class UserNotNull {
@NotNull(message = "Name may not be null")
private String name;
// standard constructors / getters / toString
}
Теперь нам нужно изучить, как на самом деле работает @NotNull
.
Для этого давайте создадим простой модульный тест для класса и проверим несколько его экземпляров:
@BeforeClass
public static void setupValidatorInstance() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void whenNotNullName_thenNoConstraintViolations() {
UserNotNull user = new UserNotNull("John");
Set<ConstraintViolation<UserNotNull>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
@Test
public void whenNullName_thenOneConstraintViolation() {
UserNotNull user = new UserNotNull(null);
Set<ConstraintViolation<UserNotNull>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenEmptyName_thenNoConstraintViolations() {
UserNotNull user = new UserNotNull("");
Set<ConstraintViolation<UserNotNull>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
Как и ожидалось, ограничение @NotNull
не допускает нулевых значений для ограничиваемых полей. Однако поля могут быть пустыми.
Чтобы лучше понять это, давайте рассмотрим метод isValid()
класса NotNullValidator
, который использует ограничение @NotNull
. Реализация метода действительно тривиальна:
public boolean isValid(Object object) {
return object != null;
}
Как показано выше, поле (например , CharSequence
, Collection
, Map
или Array)
, ограниченное @NotNull
, не должно быть нулевым. Однако пустое значение вполне допустимо .
4. Ограничение @NotEmpty
Теперь давайте реализуем пример класса UserNotEmpty
и используем ограничение @NotEmpty
:
public class UserNotEmpty {
@NotEmpty(message = "Name may not be empty")
private String name;
// standard constructors / getters / toString
}
Имея класс на месте, давайте проверим его, присвоив разные значения полю имени :
@Test
public void whenNotEmptyName_thenNoConstraintViolations() {
UserNotEmpty user = new UserNotEmpty("John");
Set<ConstraintViolation<UserNotEmpty>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
@Test
public void whenEmptyName_thenOneConstraintViolation() {
UserNotEmpty user = new UserNotEmpty("");
Set<ConstraintViolation<UserNotEmpty>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenNullName_thenOneConstraintViolation() {
UserNotEmpty user = new UserNotEmpty(null);
Set<ConstraintViolation<UserNotEmpty>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
Аннотация @NotEmpty
использует реализацию isValid()
класса @NotNull
, а также проверяет, что размер/длина предоставленного объекта (конечно, это зависит от типа проверяемого объекта) больше нуля. ``
Вкратце это означает, что поле (например , CharSequence
, Collection
, Map
или Array)
, ограниченное @NotEmpty
, не должно быть нулевым, а его размер/длина должны быть больше нуля .
Кроме того, мы можем ввести еще более строгие ограничения, если будем использовать аннотацию @NotEmpty
в сочетании с @Size.
При этом мы также добавим, чтобы минимальные и максимальные значения размера объекта находились в пределах указанного минимального/максимального диапазона:
@NotEmpty(message = "Name may not be empty")
@Size(min = 2, max = 32, message = "Name must be between 2 and 32 characters long")
private String name;
5. Ограничение @NotBlank
Точно так же мы можем ограничить поле класса с помощью аннотации @NotBlank
:
public class UserNotBlank {
@NotBlank(message = "Name may not be blank")
private String name;
// standard constructors / getters / toString
}
В том же духе мы можем реализовать модульный тест, чтобы понять, как работает ограничение @NotBlank
:
@Test
public void whenNotBlankName_thenNoConstraintViolations() {
UserNotBlank user = new UserNotBlank("John");
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
@Test
public void whenBlankName_thenOneConstraintViolation() {
UserNotBlank user = new UserNotBlank(" ");
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenEmptyName_thenOneConstraintViolation() {
UserNotBlank user = new UserNotBlank("");
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenNullName_thenOneConstraintViolation() {
UserNotBlank user = new UserNotBlank(null);
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
В аннотации @NotBlank используется класс
NotBlankValidator
, который проверяет, не пуста ли усеченная длина последовательности символов:
public boolean isValid(
CharSequence charSequence,
ConstraintValidatorContext constraintValidatorContext)
if (charSequence == null ) {
return true;
}
return charSequence.toString().trim().length() > 0;
}
Забавно, что метод возвращает true для нулевых значений. Таким образом, мы можем подумать, что @NotBlank
допускает нулевые значения, но на самом деле это не так.
Метод isValid()
класса @NotNull
вызывается после метода isValid()
класса @NotBlank
, что запрещает нулевые значения.
``
Проще говоря, поле String
, ограниченное @NotBlank
, не должно быть нулевым, а усеченная длина должна быть больше нуля .
6. Наглядное сравнение
До сих пор мы подробно рассматривали, как ограничения @NotNull
, @NotEmpty
и @NotBlank
работают по отдельности в полях класса.
Давайте проведем быстрое параллельное сравнение, чтобы мы могли взглянуть на функциональность ограничений с высоты птичьего полета и легко определить их различия:
@NotNull:
ограниченныйCharSequence
,Collection
,Map
илиArray
действителен, если он не равен нулю, но может быть пустым.@NotEmpty:
ограниченныйCharSequence
,Collection
,Map
илиArray
действителен, если он не равен нулю, а его размер/длина больше нуля.@NotBlank:
ограниченнаястрока
действительна, если она не равна нулю, а усеченная длина больше нуля.
7. Заключение
В этой статье мы рассмотрели ограничения @NotNull
, @NotEmpty
и @NotBlank
, реализованные в Bean Validation, и выделили их сходства и различия.
Как обычно, все примеры кода, показанные в этой статье, доступны на GitHub .