Перейти к основному содержимому

Разница между ограничениями @NotNull, @NotEmpty и @NotBlank при проверке компонентов

· 5 мин. чтения

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 .