Как централизованно обрабатывать исключения, возникающие в методах DataProvider

Когда метод выборки или подсчета DataProvider вызывает исключение, например поскольку пользователь не авторизован, как я могу обрабатывать эти исключения централизованно? Я знаю, что существует HasErrorParameter интерфейс для отображения сообщений об ошибках, когда возникает исключение при маршрутизации. Но эти представления ошибок не запускаются, когда DataProvider выдает исключение.

Пример:

  new AbstractBackEndDataProvider<String, Void>() {
        @Override
        protected Stream<String> fetchFromBackEnd(Query<String, Void> query) {
            ...
        }

        @Override
        protected int sizeInBackEnd(Query<String, Void> query) {
            throw new UnsupportedOperationException("test");
        }
    }

@Route("failed")
public class FailView extends VerticalLayout 
         implements HasErrorParameter<UnsupportedOperationException> {...}

Даже если я сделаю try catch в методах DataProvider, я не понимаю, как я могу перейти к соответствующему представлению ошибок, просто используя перехваченное исключение, а не класс компонента представления (это не приведет к запуску метода setErrorParameter).

Кстати: мне не хватает темы обработки исключений маршрутизатора в документации Vaadin Flow 13. Интересно, почему они его удалили.


person Steffen Harbich    schedule 17.03.2019    source источник
comment
Это известная проблема / ограничение / ошибка в текущей реализации HesErrorParameter, и есть много заявок на выпуск, связанных с этим github.com/vaadin/flow/issues/4715 github.com/vaadin/ flow / issues / 4549 github.com/vaadin/flow/issues/4607 github.com/vaadin/flow/issues/3192   -  person Tatu Lund    schedule 18.03.2019


Ответы (1)


Я считаю, что все исключения, которые не возникают при маршрутизации, будут переданы ErrorHandler сеанса VaadinSession, в котором произошла ошибка.

Лучший способ установить ErrorHandler - переопределить метод sessionInit в пользовательском SessionInitListener

Вы можете добавить пользовательский SessionInitListener внутри servletInitialized метода пользовательского VaadinServlet.

class CustomServlet extends VaadinServlet{
    @Override
    protected void servletInitialized() throws ServletException {
        super.servletInitialized();
        getService().addSessionInitListener(new CustomSessionInitListener());
    }
}

И этот SessionInitListener (в этом примере CustomSessionInitListener) должен установить errorHandler сеансов, которые инициализируются.

class CustomSessionInitListener implements SessionInitListener{
    @Override
    public void sessionInit(SessionInitEvent event) throws ServiceException {
        event.getSession().setErrorHandler(new CustomErrorHandler());
    }
}

Для получения дополнительной информации о том, как создать свой собственный сервлет, просмотрите Страница руководства по Ваадину (вам нужно прокрутить вниз до раздела «Настройка сервлета Ваадина»)

Изменить: чтобы показать страницу с ошибкой, вам нужно заставить Ваадина перенаправить на ошибку. Чтобы добиться этого, мы можем использовать BeforeEnterEvent, BeforeEnterEvents иметь метод rerouteToError, который мы можем использовать, чтобы позволить Ваадину показать наш ErrorView.

Но мы также хотим передать экземпляр Exception, поэтому мы должны его сохранить. Я сделал именно это со следующим классом:

@Route("error-view") // Route shown in the user's browser
public class ErrorViewShower extends Div implements BeforeEnterObserver {
    // Class to store the current Exception of each UI in
    private static class UIExceptionContainer extends HashMap<UI, Exception> {

    }

    // Method to call when we want to show an error
    public static void showError(Exception exception) {
        UIExceptionContainer exceptionContainer = VaadinSession.getCurrent().getAttribute(UIExceptionContainer.class);
        // Creating and setting the exceptionContainer in case it hasn't been set yet.
        if (exceptionContainer == null) {
            exceptionContainer = new UIExceptionContainer();
            VaadinSession.getCurrent().setAttribute(UIExceptionContainer.class, exceptionContainer);
        }

        // Storing the exception for the beforeEnter method
        exceptionContainer.put(UI.getCurrent(), exception);

        // Now we navigate to an Instance of this class, to use the BeforeEnterEvent to reroute to the actual error view
        UI.getCurrent().navigate(ErrorViewShower.class);// If this call doesn't work you might want to wrap into UI.access
    }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        UIExceptionContainer exceptionContainer = VaadinSession.getCurrent().getAttribute(UIExceptionContainer.class);

        // Retrieving the previously stored exception. You might want to handle if this has been called without setting any Exception.
        Exception exception = exceptionContainer.get(UI.getCurrent());

        //Clearing out the now handled Exception
        exceptionContainer.remove(UI.getCurrent());

        // Using the BeforeEnterEvent to show the error
        event.rerouteToError(exception, "Possible custom message for the ErrorHandler here");
    }

}

Использование его в сочетании с обработчиком ошибок выглядит так:

public class CustomErrorHandler implements ErrorHandler {
    @Override
    public void error(ErrorEvent event) {
        // This can easily throw an exception itself, you need to add additional checking before casting.
        // And it's possible that this method is called outside the context of an UI(when a dynamic resource throws an exception for example)
        Exception exception = (Exception) event.getThrowable();
        ErrorViewShower.showError(exception);
    }

}

Edit2: Как выясняется, исключения, возникающие внутри вызовов внутренних методов, не обрабатываются UI ErrorHandler или ErrorHandler VaadinSession, а вместо этого другим обработчиком ошибок, который заставляет клиентскую сторону завершать работу и показывать уведомление об ошибке,

решение состоит в том, чтобы перехватить Исключения внутри методов DataProvider и передать их ErrorViewShower.showError() и по-прежнему возвращаться без каких-либо Исключений, перемещающих трассировку стека вверх. (Или не создавайте никаких исключений самостоятельно, а просто передайте новое значение методу ErrorViewShower.showError()).

При обычном возврате Ваадин даже не знает, что что-то пошло не так.
ErrorViewShower.showError() вызывает ui.navigate, эта команда навигации, кажется, «ставится в очередь» после вызовов DataProvider, что означает, что представление пользователя изменится в том же запросе.

Dataprovider с такой реализацией:

new AbstractBackEndDataProvider<String, Void>() {
    @Override
    protected Stream<String> fetchFromBackEnd(Query<String, Void> query) {
        try{
            //Code that can throw an Exception here
        }catch(Exception e){
            ErrorViewShower.showError(e);
            //We have to make sure that query.getLimit and query.getOffset gets called, otherwise Vaadin throws an Exception with the message "the data provider hasn't ever called getLimit() method on the provided query. It means that the the data provider breaks the contract and the returned stream contains unxpected data."
            query.getLimit();
            query.getOffset();
            return Stream.of(); //Stream of empty Array to return without error
        }
    }

    @Override
    protected int sizeInBackEnd(Query<String, Void> query) {
        //Second way i mentioned, but this will not catch any Exception you didn't create, where as the try...catch has no way to let any Exception reach Vaadin.
        if(badThingsHappened){
            ErrorViewShower.showError(new UnsupportedOperationException("Bad things..."));
            return 0;//Exiting without error
        }
    }
}
person froemijojo    schedule 21.03.2019
comment
Действительно, вызывается обработчик ошибок, но это мне не очень помогает, потому что навигация или UI.getCurrent().getPage().executeJavaScript("window.location = '/my-error-page'") не работают в обработчике ошибок. Моя цель - показать соответствующую страницу с ошибкой. - person Steffen Harbich; 26.03.2019
comment
@SteffenHarbich, я отредактировал свой ответ, чтобы включить способ заставить Ваадина перенаправить на просмотр ошибок. - person froemijojo; 26.03.2019
comment
Спасибо за ответ. Итак, этот код работает в вашем случае? Я спрашиваю, потому что, когда я вызываю UI.getCurrent().navigate в моем обработчике ошибок, ничего не происходит, кроме обычной панели ошибок Vaadin Flow Internal Error ... обратите внимание на несохраненные данные ... в верхнем правом углу представления. Без перенаправления. - person Steffen Harbich; 26.03.2019
comment
Скопировал ваш подход, по-прежнему не получая перенаправления на просмотр ошибок. - person Steffen Harbich; 26.03.2019
comment
Да, работает именно так, как ожидалось. (Просмотрите изменения в классе с помощью Appriorate HasErrorParameter, URL-адрес браузера изменится на / error-view) - person froemijojo; 26.03.2019
comment
но панель ошибок Vaadin Flow Internal Error ... обратите внимание на несохраненные данные, похоже, что Exception не дошел до вашего ErrorHandler, но ErrorHandler по умолчанию Vaadin, вы уверены, что правильно настроили свой ErrorHandler? Если вы буквально скопировали мой код, вам нужно добавить аннотацию @WebServlet в CustomServlet (см. здесь) - person froemijojo; 26.03.2019
comment
Я использую Spring Boot, поэтому я закодировал компонент, реализующий VaadinServiceInitListener, чтобы наконец зарегистрировать SessionInitListener и ErrorHandler. Обработчик ошибок вызывается должным образом, и я могу перешагнуть вызов UI.getCurrent().navigate(ErrorViewShower.class) в режиме отладки. Даже beforeEnter называется. Какая у вас версия Ваадина? - person Steffen Harbich; 26.03.2019
comment
Я использую Vaadin 13.0.2. Я только что создал урезанную копию своего приложения и изменил версию Vaadin на 10.0.11, там она не работает. Но ничего не меняя, кроме версии Vaadin на 13.0.2, заставляет работать. Это работает, потому что клиентская сторона не показывает панель ошибок и закрывается, а вместо этого продолжает работать. В этой ветке форума Vaadin говорится, что клиентская сторона завершает работу при ошибке происходит, но это было 10 месяцев назад. Похоже, они изменили это в более новых версиях (по крайней мере, Vaadin 13). - person froemijojo; 27.03.2019
comment
Я использую Vaadin 13.0.2 и совершенно не понимаю, почему он работает в вашем приложении, но не в моем ... постараюсь создать минимальный пример. - person Steffen Harbich; 27.03.2019
comment
Мой минимальный пример не воспроизводит ваше поведение . Не могли бы вы взглянуть на это? Он основан на стартовом проекте Vaadin Flow без пружины. Тысяча благодарностей! - person Steffen Harbich; 27.03.2019
comment
Гм, я тестировал исключения только вне Dataprovider, там он работает. Похоже, что исключения внутри таких внутренних методов обрабатываются иначе, чем при вызове других слушателей. Я отредактировал (или все еще редактирую) свой ответ, чтобы включить идею, которую я должен обойти. - person froemijojo; 27.03.2019
comment
Хорошо, это отлично работает. Будем надеяться, что Vaadin когда-нибудь исправит обработку исключений, поэтому этот обходной путь больше не потребуется. Спасибо большое за вашу помощь! - person Steffen Harbich; 28.03.2019