Обработчики исключений

Необработанные исключения, выброшенные в потоке HTTP-запроса, передаются механизму обработки исключений Jmix. Он содержит список обработчиков, каждый из которых может либо обработать исключение, либо отказаться. Если исключение не обработано ни одним из обработчиков, оно передается в DefaultUiExceptionHandler, который отображает диалог с сообщением об исключении и стек вызовов. Вы можете настроить этот диалог, как показано ниже.

Обработчики исключений приложения

Вы можете предоставить собственные обработчики для любых исключений. Самый простой способ сделать это - создать подкласс AbstractUiExceptionHandler и зарегистрировать его как бин Spring.

Например, если у вас есть следующее исключение:

package com.company.onboarding.exception;

public class MyException extends RuntimeException {

    public MyException(String message) {
        super(message);
    }
}

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

package com.company.onboarding.exception;

import io.jmix.flowui.Notifications;
import io.jmix.flowui.exception.AbstractUiExceptionHandler;
import org.springframework.stereotype.Component;

@Component (1)
public class MyExceptionHandler extends AbstractUiExceptionHandler { (2)

    private final Notifications notifications; (3)

    public MyExceptionHandler(Notifications notifications) {
        super(MyException.class.getName()); (4)
        this.notifications = notifications;
    }

    @Override
    protected void doHandle(String className, String message, Throwable throwable) {
        notifications.show("My exception", throwable.getMessage()); (5)
    }
}
1 - Сделайте обработчик бином Spring.
2 - Расширьте AbstractUiExceptionHandler.
3 - Инжектируйте любые другие бины Spring, если необходимо.
4 - Передайте полное имя класса исключения в конструктор суперкласса.
5 - Обработайте исключение в методе doHandle().

Ваш обработчик исключений будет автоматически добавлен в список обработчиков фреймворка. Позицию вашего обработчика в списке можно настроить, добавив аннотацию @Order к бину. Например, если установить порядок как @Order(JmixOrder.HIGHEST_PRECEDENCE - 10), ваш обработчик получит приоритет перед обработчиком фреймворка для того же исключения.

Если необходимо больше контроля над тем, какие исключения должны обрабатываться вашим обработчиком, либо переопределите метод canHandle() базового класса AbstractUiExceptionHandler, либо вовсе не используйте AbstractUiExceptionHandler и реализуйте интерфейс UiExceptionHandler напрямую. См. также документацию Vaadin по обработке исключений UI на более низком уровне.

Обработчик нарушений уникальности

Jmix предоставляет встроенный обработчик нарушений ограничений уникальности базы данных: UniqueConstraintViolationHandler. Его можно легко настроить в двух аспектах.

Во-первых, вы можете изменить сообщение, которое отображается пользователю в ответ на ошибку. Сообщение устанавливается в пакете сообщений с ключом в формате databaseUniqueConstraintViolation.<DB_CONSTRAINT_NAME>, например:

messages_ru.properties
databaseUniqueConstraintViolation.IDX_DEPARTMENT_UNQ_NAME=Отдел с таким именем уже существует

Во-вторых, вы можете предоставить свой собственный шаблон для распознавания нарушений уникальности. Фреймворк содержит шаблоны по умолчанию для каждого типа базы данных, например, для PostgreSQL это ERROR: duplicate key value violates unique constraint "(.+)". Вы можете найти шаблоны по умолчанию в реализациях интерфейса DbmsFeatures. Если шаблон по умолчанию не работает в ваших условиях (например из-за локализации базы данных), предоставьте свой шаблон в свойстве jmix.data.unique-constraint-violation-pattern.

Настройка обработчика по умолчанию

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

Во-первых, создайте свой класс диалога на базе ExceptionDialog и переопределите соответствующие методы:

package com.company.onboarding.exception;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import io.jmix.flowui.exception.ExceptionDialog;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE) (1)
public class MyExceptionDialog extends ExceptionDialog { (2)

    public MyExceptionDialog(Throwable throwable) {
        super(throwable);
    }

    @Override
    protected HorizontalLayout createButtonsPanel() { (3)
        HorizontalLayout buttonsPanel = super.createButtonsPanel();
        Button button = uiComponents.create(Button.class);
        button.setText("Report to admin");
        button.addClickListener(e -> {
            // ...
        });
        buttonsPanel.add(button);
        return buttonsPanel;
    }
}
1 - Зарегистрируйте класс как бин-прототип.
2 - Расширьте ExceptionDialog.
3 - Переопределите соответствующие методы для настройки диалога.

Затем создайте класс-провайдер:

package com.company.onboarding.exception;

import io.jmix.flowui.exception.ExceptionDialog;
import io.jmix.flowui.exception.ExceptionDialogProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;

@Component
public class MyExceptionDialogProvider implements ExceptionDialogProvider {

    private final ObjectProvider<MyExceptionDialog> myExceptionDialogProvider; (1)

    public MyExceptionDialogProvider(ObjectProvider<MyExceptionDialog> myExceptionDialogProvider) {
        this.myExceptionDialogProvider = myExceptionDialogProvider;
    }

    @Override
    public boolean supports(Throwable throwable) {
        return true; (2)
    }

    @Override
    public ExceptionDialog getExceptionDialogOpener(Throwable throwable) {
        return myExceptionDialogProvider.getObject(throwable); (3)
    }
}
1 - Используйте ObjectProvider, потому что MyExceptionDialog является бином-прототипом.
2 - Провайдер может вступать в действие только для конкретных исключений. Верните true, если вы хотите, чтобы он работал для всех типов исключений.
3 - Верните новый экземпляр MyExceptionDialog.

Фреймворк самостоятельно подхватит вашего провайдера и будет использовать возвращенный диалог для необработанных исключений.