环境:SpringBoot2.7.16
1. 简介
默认情况下,Spring Boot提供了一个/error映射,以合理的方式处理所有错误,并且它在servlet容器中注册为“全局”错误页面。对于机器客户端,它会生成一个JSON响应,其中包含错误、HTTP状态和异常消息的详细信息。对于浏览器客户端,有一个“白标签”错误视图,它以HTML格式呈现相同的数据(要自定义它,只需要定义一个以error 为beanName的View bean对象)。
如果需要自定义默认的错误处理行为,可以通过设置server.error相应属性。
要完全替换默认行为,可以实现ErrorController并注册为Bean,或者添加ErrorAttributes类型的bean。
BasicErrorController可以用作自定义ErrorController的基类。如果想为新的内容类型添加处理程序,这一点尤其有用(默认情况是专门处理text/html,并为其他所有内容提供后备)。要做到这一点,请扩展BasicErrorController,添加一个带有具有products属性的@RequestMapping的公共方法,并创建一个新类型的bean。
从Spring Framework 6.0开始,支持RFC 7807 Problem Details。Spring MVC可以使用application/pproblem+json媒体类型生成自定义错误消息,如:
{
"type": "http://www.pack.com/users/666",
"title": "Unknown project",
"status": 404,
"detail": "xxxxx",
"instance": "/users/666"
}
可以通过将spring.mvc.problemdetails.enabled设置为true来启用此支持。
还可以定义一个用@ControllerAdvice注释的类,以自定义JSON格式输出,如以下示例所示:
@RestControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
HttpStatus status = HttpStatus.resolve(code);
return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
}
}
2. 自定义错误页
如果要显示给定状态代码的自定义HTML错误页面,可以将文件添加到/error目录中。错误页面可以是静态HTML(即添加到任何静态资源目录下),也可以使用模板构建。文件的名称应该是确切的状态代码或序列掩码。
例如,要将404映射到静态HTML文件,目录结构如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
要使用FreeMarker模板映射所有5xx错误,目录结构如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates>
对于更复杂的映射,还可以添加实现ErrorViewResolver接口的bean,如以下示例所示:
@Component
public class PackErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
if (status == HttpStatus.INTERNAL_SERVER_ERROR) {
return new ModelAndView("error") ;
}
return null ;
}
}
3. 向容器注册错误页
对于不使用Spring MVC的应用程序,可以使用ErrorPageRegistrar接口直接注册ErrorPages。这种抽象直接与底层嵌入式Servlet容器一起工作,即使没有Spring MVC DispatcherServlet也能工作。
@Configuration
public class PackErrorPagesConfiguration {
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return this::registerErrorPages;
}
private void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}
4. 默认错误页注册原理
这里以Tomcat为例,SpringBoot内嵌tomcat容器会自动注册TomcatServletWebServerFactory该类进行Tomcat容器的配置,这其中就包括将错误页注册到tomcat中。并且该类实现了ErrorPageRegistry接口,该类专门用来注册错误页。
public class TomcatServletWebServerFactory {
public WebServer getWebServer(...) {
Tomcat tomcat = new Tomcat();
// ...
prepareContext(...);
}
protected void prepareContext(...) {
// ...
configureContext(...)
}
protected void configureContext(...) {
// ...
// 获取容器中定义的所有ErrorPage错误页
for (ErrorPage errorPage : getErrorPages()) {
org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
tomcatErrorPage.setLocation(errorPage.getPath());
tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
context.addErrorPage(tomcatErrorPage);
}
}
}
这些ErrorPage通过如下方式被添加到上面的TomcatServletWebServerFactory中
SpringBoot会注册一个ErrorPageRegistrarBeanPostProcessor处理器
public class ErrorPageRegistrarBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 上面说了TomcatServletWebServerFactory实现了ErrorPageRegistry接口
if (bean instanceof ErrorPageRegistry) {
postProcessBeforeInitialization((ErrorPageRegistry) bean);
}
return bean;
}
private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
for (ErrorPageRegistrar registrar : getRegistrars()) {
registrar.registerErrorPages(registry);
}
}
private Collection<ErrorPageRegistrar> getRegistrars() {
if (this.registrars == null) {
// 获取容器中的所有ErrorPageRegistrar
this.registrars = new ArrayList<>(
this.beanFactory.getBeansOfType(ErrorPageRegistrar.class, false, false).values());
this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE);
this.registrars = Collections.unmodifiableList(this.registrars);
}
return this.registrars;
}
}
注意:自定义ErrorPageRegistrar时,我们可以通过实现Ordered接口控制优先级
以上是本篇文章的全部内容,希望对你有帮助。