详解SpringBoot错误处理

开发 前端
如果要显示给定状态代码的自定义HTML错误页面,可以将文件添加到/error目录中。错误页面可以是静态HTML(即添加到任何静态资源目录下),也可以使用模板构建。文件的名称应该是确切的状态代码或序列掩码。

环境: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接口控制优先级

以上是本篇文章的全部内容,希望对你有帮助。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2011-05-25 10:26:42

ora-02069错误

2021-03-02 09:12:25

Java异常机制

2021-04-14 07:08:14

Nodejs错误处理

2023-10-28 16:30:19

Golang开发

2010-06-01 16:14:04

2009-08-05 16:04:50

2021-04-29 09:02:44

语言Go 处理

2022-11-16 08:41:43

2014-11-17 10:05:12

Go语言

2023-12-26 22:05:53

并发代码goroutines

2017-04-06 14:40:29

JavaScript错误处理堆栈追踪

2015-08-19 14:11:56

SQL Server错误处理

2017-03-08 08:57:04

JavaScript错误堆栈

2016-08-19 10:41:42

Swift 2错误

2009-07-31 11:28:42

错误处理机制ASP.NET

2016-09-07 20:28:17

MySQL存储数据库

2009-06-19 16:20:14

ASP.NET错误处理

2023-11-08 15:04:55

事务GORM

2023-10-08 20:31:18

React

2023-03-10 08:48:29

点赞
收藏

51CTO技术栈公众号