前言
之前一篇文章介绍了基本的统一异常处理思路: Spring MVC/Boot 统一异常处理最佳实践.
上篇文章也有许多人提出了一些问题:
- 如何区分 Ajax 请求和普通页面请求, 以分别返回 JSON 错误信息和错误页面.
- 如何结合 HTTP 状态码进行统一异常处理.
今天这篇文章就主要来讲讲这些, 以及其他的一些拓展点.
区分请求方式
其实 Spring Boot 本身是内置了一个异常处理机制的, 会判断请求头的参数来区分要返回 JSON 数据还是错误页面. 源码为: org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
, 他会处理 /error
请求. 核心处理代码如下:
1 | ( |
这两个方法的共同点是: 他们都调用了 this.getErrorAttributes(…) 方法来获取响应信息.
然后来看看他默认情况下对于 AJAX 请求和 HTML 请求, 分别的返回结果是怎样的:
对于返回错误页面, 其中还调用了一个非常重要的方法: this.resolveErrorView(...)
方法, 源码我就不带大家看了, 他的作用就是根据 HTTP 状态码来去找错误页面, 如 500 错误会去找 /error/500.html
, 403 错误回去找 /error/403.html
, 如果找不到则再找 /error/4xx.html
或 /error/5xx.html
页面. 还找不到的话, 则会去找 /error.html
页面, 如果都没有配置, 则会使用 Spring Boot 默认的页面. 即:
看到这里, 应该就清楚了, 我们主要需要做四件事:
- 发送异常后, 重定向到
BasicErrorController
来处理 (既然Spring Boot 都已经写好了区分请求的功能, 我们就不必要再写这些判断代码了) - 自定义 HTTP 错误状态码
- 他返回的信息格式可能不是我们想要的, 所以必须要改造
getErrorAttributes(...)
方法, 以自定义我们向页面返回的数据. (自定义错误信息) - 创建我们自己的
/error/4xx.html
或/error/5xx.html
等页面, (自定义错误页面)
BasicErrorController
第一点很简单, BasicErrorController
他处理 /error
请求, 我们只需要将页面重定向到 /error
即可, 在 ControllerAdvice 中是这样的:
1 |
|
自定义 HTTP 错误状态码
我们来看下 this.getStatus(request);
的源码, 看他原来时如何获取错误状态码的:
1 | protected HttpStatus getStatus(HttpServletRequest request) { |
简单来说就是从 request 域中获取 javax.servlet.error.status_code
的值, 如果为 null 或不合理的值, 都返回 500. 既然如何在第一步, 重定向到 /error
之前将其配置到 request 域中即可, 如:
1 |
|
自定义错误信息
也就是 getErrorAttributes 方法, 默认的代码是这样的:
1 | public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { |
他获取了时间戳, 错误状态码, 错误信息, 错误路径等信息, 和我们之前看到默认的返回内容是一致的:
1 | { |
同样的思路, 我们将错误信息也放到 request 域中, 然后在 getErrorAttributes 中从 request 域中获取:
1 |
|
再继承 DefaultErrorAttributes
类, 重写 getErrorAttributes
方法:
1 | //@Component |
自定义错误页面
我们遵循 SpringBoot 的规则, 在 /error/
下建立 400.html
, 500.html
等页面细粒度的错误, 并配置一个 /error.html
用来处理细粒度未处理到的其他错误.
/error/400.html
1 |
|
/error/500.html
1 |
|
/error.html
1 |
|
测试效果
到此位置, 大功告成, 然后来创造一个异常来测试一下效果:
前端 error 处理
现在使用了 HTTP 状态码, 所以 Ajax 请求出现错误后, 需要在每个 Ajax 请求方法中都写 error: function() {}
方法, 甚至麻烦. 好在 jQuery 为我们提供了全局处理 Ajax 的 error 结果的方法 ajaxError() :
1 | $(document).ajaxError(function(event, response){ |
结语
回顾一下讲到的这些内容:
- 理解
SpringBoot
默认提供的BasicErrorController
- 自定义 HTTP 错误状态码, (通过 request 域的
javax.servlet.error.status_code
参数) - 自定义错误信息, (将我们自定义的错误信息放到 request 域中, 并重写
DefaultErrorAttributes
的getErrorAttributes
方法, 从 request 域中获取这些信息). - 自定义错误页面, (根据 SpringBoot 查找错误页面的逻辑来自定义错误页面:
/error/500.html
,/error/400.html
,/error.html
)
可以自己根据文章一步一步走一遍, 或者看我写好的演示项目先看看效果, 总是动手实践, 而不是收藏文章并封存。
演示项目地址: https://github.com/zhaojun1998/exception-handler-demo