Как обрабатывать исключения, выброшенные в методах DataProvider централизованно - PullRequest
1 голос
/ 17 марта 2019

Когда метод выборки или подсчета 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. Интересно, почему они это убрали?

1 Ответ

2 голосов
/ 21 марта 2019

Я считаю, что все исключения, которые не возникают во время маршрутизации, будут переданы 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());
    }
}

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

Edit: Чтобы показать страницу с ошибкой, вам нужно, чтобы Vaadin перенаправил сообщение об ошибке. Чтобы добиться этого, мы можем использовать BeforeEnterEvent, BeforeEnterEvents и метод rerouteToError, который мы можем использовать, чтобы Vaadin показал наш 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: Оказывается, что исключения, возникающие во внутренних вызовах методов, не обрабатываются ErrorHandler пользовательского интерфейса или ErrorHandler VaadinSession, а вместо этого другим обработчиком ошибок, который приводит к завершению клиентской стороны и отображает уведомление об ошибке,

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

Возвращаясь нормально, Ваадин даже не знает, что что-то пошло не так.
ErrorViewShower.showError() вызывает ui.navigate, эта команда навигации, похоже, «ставится в очередь» за вызовами к 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
        }
    }
}
...