如何集中处理DataProvider方法抛出的异常
How to handle exceptions thrown in DataProvider methods centrally
当 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
,我也看不到如何仅通过使用捕获的异常而不是视图组件导航到适当的错误视图 class (这不会触发 setErrorParameter
方法)。
顺便说一句:我错过了 Vaadin Flow 13 文档中的路由器异常处理主题。我想知道他们为什么删除它。
我相信所有在路由时没有发生的异常都会被提供给发生错误的 VaadinSession 的 ErrorHandler。
设置 ErrorHandler 的最佳方法似乎是在自定义 SessionInitListener
中覆盖 sessionInit
方法
您可以在自定义 VaadinServlet 的 servletInitialized
方法中添加自定义 SessionInitListener
。
class CustomServlet extends VaadinServlet{
@Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(new CustomSessionInitListener());
}
}
并且 SessionInitListener
(在此示例中 CustomSessionInitListener
)必须设置初始化会话的错误处理程序。
class CustomSessionInitListener implements SessionInitListener{
@Override
public void sessionInit(SessionInitEvent event) throws ServiceException {
event.getSession().setErrorHandler(new CustomErrorHandler());
}
}
有关如何创建自己的 Servlet 的更多信息,请查看 Vaadin's tutorial page(您需要向下滚动至 "Customizing Vaadin Servlet")
编辑:
要显示错误页面,您需要让 Vaadin 重新路由到错误。为此,我们可以使用 BeforeEnterEvent
,BeforeEnterEvents
有一个 rerouteToError
方法,我们可以使用该方法让 Vaadin 显示我们的 ErrorView。
但是我们还想传递 Exception 实例,所以我们也必须存储它。我正是用以下 class:
做到了这一点
@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);
}
}
编辑2:
事实证明,内部方法调用中发生的异常不会由 UI 的 ErrorHandler 或 VaadinSession 的 ErrorHandler 处理,而是由另一个导致客户端终止并显示错误通知的错误处理程序处理,
一个解决方案是在 DataProvider 的方法中捕获异常并将它们传递给 ErrorViewShower.showError()
并且仍然 return 没有任何异常向上飞行堆栈跟踪。 (或者不要自己抛出任何异常,而是简单地将一个新的传递给 ErrorViewShower.showError()
方法)。
通过 return 通常 Vaadin 甚至不知道出了什么问题。
ErrorViewShower.showError()
调用 ui.navigate
,该导航命令似乎 "queued" 落后于对 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
}
}
}
当 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
,我也看不到如何仅通过使用捕获的异常而不是视图组件导航到适当的错误视图 class (这不会触发 setErrorParameter
方法)。
顺便说一句:我错过了 Vaadin Flow 13 文档中的路由器异常处理主题。我想知道他们为什么删除它。
我相信所有在路由时没有发生的异常都会被提供给发生错误的 VaadinSession 的 ErrorHandler。
设置 ErrorHandler 的最佳方法似乎是在自定义 SessionInitListener
sessionInit
方法
您可以在自定义 VaadinServlet 的 servletInitialized
方法中添加自定义 SessionInitListener
。
class CustomServlet extends VaadinServlet{
@Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(new CustomSessionInitListener());
}
}
并且 SessionInitListener
(在此示例中 CustomSessionInitListener
)必须设置初始化会话的错误处理程序。
class CustomSessionInitListener implements SessionInitListener{
@Override
public void sessionInit(SessionInitEvent event) throws ServiceException {
event.getSession().setErrorHandler(new CustomErrorHandler());
}
}
有关如何创建自己的 Servlet 的更多信息,请查看 Vaadin's tutorial page(您需要向下滚动至 "Customizing Vaadin Servlet")
编辑:
要显示错误页面,您需要让 Vaadin 重新路由到错误。为此,我们可以使用 BeforeEnterEvent
,BeforeEnterEvents
有一个 rerouteToError
方法,我们可以使用该方法让 Vaadin 显示我们的 ErrorView。
但是我们还想传递 Exception 实例,所以我们也必须存储它。我正是用以下 class:
做到了这一点@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);
}
}
编辑2: 事实证明,内部方法调用中发生的异常不会由 UI 的 ErrorHandler 或 VaadinSession 的 ErrorHandler 处理,而是由另一个导致客户端终止并显示错误通知的错误处理程序处理,
一个解决方案是在 DataProvider 的方法中捕获异常并将它们传递给 ErrorViewShower.showError()
并且仍然 return 没有任何异常向上飞行堆栈跟踪。 (或者不要自己抛出任何异常,而是简单地将一个新的传递给 ErrorViewShower.showError()
方法)。
通过 return 通常 Vaadin 甚至不知道出了什么问题。
ErrorViewShower.showError()
调用 ui.navigate
,该导航命令似乎 "queued" 落后于对 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
}
}
}