如何集中处理DataProvider方法中引发的异常

时间:2019-03-17 18:53:27

标签: vaadin vaadin-grid vaadin-flow vaadin10

当DataProvider的fetch或count方法引发异常时,例如因为未授权用户,我如何集中处理这些异常??我知道有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> {...}

即使我在DataProvider方法中执行try catch,我也看不到如何仅通过使用捕获的异常而不是视图组件类来导航到适当的错误视图(这不会触发setErrorParameter方法。

顺便说一句:我错过了Vaadin Flow 13文档中的路由器异常处理主题。我想知道为什么他们将其删除。

1 个答案:

答案 0 :(得分:2)

我相信在路由过程中不会发生的所有异常都会被提供给发生错误的VaadinSession的ErrorHandler。

设置ErrorHandler的最好方法似乎是在自定义sessionInit中覆盖SessionInitListener方法

您可以在自定义VaadinServlet的SessionInitListener方法内添加自定义servletInitialized

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());
    }
}

有关如何创建自己的Servlet的更多信息,请查看Vaadin's tutorial page(您需要向下滚动到“ Customing Vaadin Servlet”)

编辑: 要显示错误页面,您需要让Vaadin重新路由到错误。为此,我们可以使用BeforeEnterEventBeforeEnterEvents有一个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: 事实证明,内部方法调用中发生的异常不是由UI的ErrorHandler或VaadinSession的ErrorHandler处理,而是由另一个导致客户端终止并显示错误通知的错误处理程序处理,

一个解决方案是在DataProvider的方法中捕获异常并将它们传递给ErrorViewShower.showError()并仍然返回,而没有任何异常向上飞行stacktrace。 (或者不要自己抛出任何Exception,而只是将一个new传递给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
        }
    }
}