«1. Введение

Интерполяция сообщений — это процесс, используемый для создания сообщений об ошибках для ограничений проверки Java-бинов. Например, мы можем увидеть сообщения, предоставив нулевое значение для поля, аннотированного аннотацией javax.validation.constraints.NotNull.

В этом уроке мы узнаем, как использовать интерполяцию сообщений Spring по умолчанию и как создать собственный механизм интерполяции.

Чтобы увидеть примеры других библиотек, предоставляющих ограничения помимо javax.validation, взгляните на специальные ограничения Hibernate Validator. Мы также можем создать пользовательскую аннотацию Spring Validation.

2. Интерполяция сообщений по умолчанию

Прежде чем переходить к фрагментам кода, давайте рассмотрим пример ответа HTTP 400 с сообщением о нарушении ограничения по умолчанию @NotNull:

{
    ....
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            ....
            "defaultMessage": "must not be null",
            ....
        }
    ],
    "message": "Validation failed for object='notNullRequest'. Error count: 1",
    ....
}

Spring извлекает детали сообщения о нарушении ограничения из сообщения дескрипторы. Каждое ограничение определяет свой дескриптор сообщения по умолчанию, используя атрибут сообщения. Но, конечно, мы можем перезаписать его произвольным значением.

В качестве примера мы создадим простой REST-контроллер с методом POST:

@RestController
public class RestExample {

    @PostMapping("/test-not-null")
    public void testNotNull(@Valid @RequestBody NotNullRequest request) {
        // ...
    }
}

Тело запроса будет сопоставлено с объектом NotNullRequest, который содержит только одну строку String с аннотацией @NotNull: ~~ ~

public class NotNullRequest {

    @NotNull(message = "stringValue has to be present")
    private String stringValue;

    // getters, setters
}

Теперь, когда мы отправляем запрос POST, который не проходит эту проверку, мы увидим наше специальное сообщение об ошибке:

{
    ...
    "errors": [
        {
            ...
            "defaultMessage": "stringValue has to be present",
            ...
        }
    ],
    ...
}

Единственное значение, которое изменяется, — это defaultMessage. Но мы по-прежнему получаем много информации о кодах ошибок, имени объекта, имени поля и т. д. Чтобы ограничить количество отображаемых значений, мы можем реализовать обработку пользовательских сообщений об ошибках для REST API.

3. Интерполяция с выражениями сообщений

В Spring мы можем использовать Unified Expression Language для определения наших дескрипторов сообщений. Это позволяет определять сообщения об ошибках на основе условной логики, а также включает расширенные параметры форматирования.

Чтобы понять это более четко, давайте рассмотрим несколько примеров.

В каждой аннотации ограничения мы можем получить доступ к фактическому значению проверяемого поля:

@Size(
  min = 5,
  max = 14,
  message = "The author email '${validatedValue}' must be between {min} and {max} characters long"
)
private String authorEmail;

Наше сообщение об ошибке будет содержать как фактическое значение свойства, так и минимальные и максимальные параметры аннотации @Size:

"defaultMessage": "The author email '[email protected]' must be between 5 and 14 characters long"

Обратите внимание, что для доступа к внешним переменным мы используем синтаксис ${}, но для доступа к другим свойствам из аннотации проверки мы используем {}.

Также возможно использование тернарного оператора:


@Min(
  value = 1,
  message = "There must be at least {value} test{value > 1 ? 's' : ''} in the test case"
)
private int testCount;

Spring преобразует тернарный оператор в одно значение в сообщении об ошибке:

"defaultMessage": "There must be at least 2 tests in the test case"

Мы также можем вызывать методы для внешних переменных:

@DecimalMin(
  value = "50",
  message = "The code coverage ${formatter.format('%1$.2f', validatedValue)} must be higher than {value}%"
)
private double codeCoverage;

Неверный ввод вызовет сообщение об ошибке с форматированным значением:

"defaultMessage": "The code coverage 44.44 must be higher than 50%"

As we can see from these examples, some characters such as {, }, $, and / are used in message expressions, so we need to escape them with a backslash character before using them literally: \{, \}, \$, and \\.

4. Пользовательская интерполяция сообщений

В некоторых случаях мы хотим реализовать собственный механизм интерполяции сообщений. Для этого мы должны сначала реализовать интерфейс javax.validation.MessageInterpolation:

public class MyMessageInterpolator implements MessageInterpolator {
    private final MessageInterpolator defaultInterpolator;

    public MyMessageInterpolator(MessageInterpolator interpolator) {
        this.defaultInterpolator = interpolator;
    }

    @Override
    public String interpolate(String messageTemplate, Context context) {
        messageTemplate = messageTemplate.toUpperCase();
        return defaultInterpolator.interpolate(messageTemplate, context);
    }

    @Override
    public String interpolate(String messageTemplate, Context context, Locale locale) {
        messageTemplate = messageTemplate.toUpperCase();
        return defaultInterpolator.interpolate(messageTemplate, context, locale);
    }
}

В этой простой реализации мы просто меняем сообщение об ошибке на верхний регистр. При этом наше сообщение об ошибке будет выглядеть так:

"defaultMessage": "THE CODE COVERAGE 44.44 MUST BE HIGHER THAN 50%"

Нам также необходимо зарегистрировать наш интерполятор в фабрике javax.validation.Validation:

Validation.byDefaultProvider().configure().messageInterpolator(
  new MyMessageInterpolator(
    Validation.byDefaultProvider().configure().getDefaultMessageInterpolator())
);

5. Заключение

В этой статье мы узнали, как работает интерполяция сообщений Spring по умолчанию и как создать собственный механизм интерполяции сообщений.

И, как всегда, весь исходный код доступен на GitHub.