SpringBoot 自定义 404 页面

第一次做,绕了很久的弯路。详细总结一下。

一、背景

用 SpringBoot 写的后端服务,遇到 404 的时候,默认会返回一个 WhiteLable 页面(如图所示)。

可读性很差,因此:

  1. 我们需要有自定义的错误提示页面;
  2. 针对不同类型的错误(404、403、500),返回不同的错误页面。

二、步骤

Step1 加入 thymeleaf 依赖

在 build.gradle 中加入:

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

Step2 添加默认错误页面

在 resources/templates/ 目录下,创建一个 error.html 文件。

例如内容为:

<!DOCTYPE html>
<html>
<body>
<h1> Default Error - Something went wrong! </h1>
<p><a href="/">Go Home</a></p>
</body>
</html>

此时,只要遇到错误,都会显示这个错误页面。

Step3 实现自定义的 ErrorController

先在 templates 目录中添加特定的错误页面:

  • error-404.html
  • error-403.html
  • error-500.html

创建一个 Controller 类,去实现 ErrorController。其中有一个 GetMapping,路径是 /error。

@Controller
public class MyErrorController implements ErrorController {
  // 如果接口中没有这个方法,就不写
  @Override
  @SuppressWarnings("deprecation")
  public String getErrorPath() {
    return "error";
  }

  @GetMapping("/error")
  public String handleError(HttpServletRequest request) {
    Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
    if (status != null) {
      int statusCode = Integer.parseInt(status.toString());
      if(statusCode == HttpStatus.NOT_FOUND.value()) {
        return "error-404";
      }
      else if(statusCode == HttpStatus.FORBIDDEN.value()) {
        return "error-403";
      }
      else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
        return "error-500";
      }
    }
    return "error";
  }


  // 附带一个测试接口,用于验证功能。验证完可以删掉
  @GetMapping(value = "/test/{id}")
  public void testError(HttpServletResponse response, @PathVariable("id") int id) {
    if (id == 403) {
      throw new ResponseStatusException(HttpStatus.FORBIDDEN, "forbidden");
    } else if (id == 404) {
      throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unable to find resource");
    } else {
      throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "internal error");
    }
  }
}

三、原理浅析

细节还是要慢慢 debug 去看。

主要原理是:

服务器收到一个 request 后,尝试处理。处理过程中发生异常,就一层层地抛出。直到外层某个地方会 catch 住这个异常,把 request 对象的内容修改掉,请求路径改为 /error,然后再次进行处理。