异常处理
HTTP 请求线程中抛出的未处理异常会传递给 Jmix 异常处理机制。该机制包含一个 handler 列表,每个 handler 都可以处理异常或选择不处理异常。如果异常并未由任何特定的 handler 处理,则会将其传递给 DefaultUiExceptionHandler
,这个处理器会显示一个对话框,包含异常和堆栈信息。下面 有介绍如何自定义对话框的内容。
应用程序异常处理
开发者可以提供自定义的异常处理 handler。最简单的方法是创建 AbstractUiExceptionHandler
的子类,并注册为 Spring bean。
例如,如果有如下异常:
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 bean。 |
2 | - 继承 AbstractUiExceptionHandler 。 |
3 | - 如果需要,可以注入其他 Spring bean。 |
4 | - 将异常的全限定名传递给父类的构造器。 |
5 | - 在 doHandle() 方法处理异常。 |
自定义的异常处理器会被添加至框架定义的处理器列表中。可以通过为 bean 添加 @Order
注解调整自定义处理器在列表中的位置。例如,如果设置顺序为 @Order(JmixOrder.HIGHEST_PRECEDENCE - 10)
,则自定义处理器的优先级会高于框架对同一异常定义的处理器。
如需控制处理器能处理的异常类型,可以重写 AbstractUiExceptionHandler
基类的 canHandle()
方法,或者不使用 AbstractUiExceptionHandler
类,而是直接自定义实现 UiExceptionHandler
接口。参阅 Vaadin 文档 了解如何在更低的级别处理 UI 异常。
处理违反唯一性约束异常
Jmix 提供了一个用于处理违反数据库唯一性约束异常的处理器:UniqueConstraintViolationHandler
。支持两个方面的自定义配置。
首先,可以修改展示给用户的异常消息。异常消息在消息包中通过 databaseUniqueConstraintViolation.<DB_CONSTRAINT_NAME>
格式的键值定义,示例:
databaseUniqueConstraintViolation.IDX_DEPARTMENT_UNQ_NAME=A department with the same name already exists
其次,还可以配置用于识别违反唯一性约束异常的正则表达式。框架为每种数据库类型提供了默认的表达式,例如对于 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 | - 将类注册为 prototype bean。 |
2 | - 继承 ExceptionDialog 。 |
3 | - 重写特定的方法自定义对话框。 |
然后创建 provider 类:
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 是 prototype bean。 |
2 | - 这里可以让 provider 仅对某些特定异常有效。直接返回 true 则对所有异常类型有效。 |
3 | - 返回 MyExceptionDialog 的新实例。 |
框架会自动使用这个 provider 并将其返回的对话框用于处理异常。
导航异常处理
如果在页面切换导航时发生了未处理的异常,Vaadin 会显示一个特殊的错误视图。例如,当用户导航到一个不存在的路由时,会显示以下页面(在生产环境中不会显示可用的路由):
这里用到了 com.vaadin.flow.router.NotFoundException
的默认实现 RouteNotFoundError
,以及 com.vaadin.flow.router.AccessDeniedException
的默认实现 RouteAccessDeniedError
。
如果需要提供自定义的错误页面,可以自定义一个类扩展默认的实现,示例:
package com.company.onboarding.exception;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.dom.Style;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.ErrorParameter;
import com.vaadin.flow.router.NotFoundException;
import com.vaadin.flow.router.RouteNotFoundError;
import jakarta.servlet.http.HttpServletResponse;
public class CustomRouteNotFoundError extends RouteNotFoundError {
@Override
public int setErrorParameter(BeforeEnterEvent event,
ErrorParameter<NotFoundException> parameter) {
Div message = new Div("404");
message.getElement().getStyle().setFontSize("10em");
Anchor link = new Anchor(".", "Main Page");
link.getElement().getStyle().setFontSize("2em");
getElement().getStyle().setTextAlign(Style.TextAlign.CENTER);
getElement().appendChild(
message.getElement(),
link.getElement()
);
return HttpServletResponse.SC_NOT_FOUND;
}
}
框架会自动加载你的类用于展示路由错误页面。
参阅 Vaadin 文档 了解关于路由异常的更多内容。