一、什么是异常处理
1、文档定义
首先我们先来看springboot官方对于异常处理的定义。springboot异常处理
在文档的描述中,我们首先可以看到的一个介绍如下:
By default, Spring Boot provides an /error mapping that handles all errors in a sensible way,
and it is registered as a “global” error page in the servlet container. For machine clients,
it produces a JSON response with details of the error, the HTTP status, and the exception message.
For browser clients, there is a “whitelabel” error view that renders the same data in
HTML format (to customize it, add a View that resolves to error).
To replace the default behavior completely, you can implement ErrorController
and register a bean definition of that type or add a bean of type ErrorAttributes to use
the existing mechanism but replace the contents.
默认情况下,Spring Boot提供了一个/error映射,以合理的方式处理所有错误,并且它在servlet容器中注册为“全局”错误页面。
对于机器客户端,它生成一个JSON响应,其中包含错误、HTTP状态和异常消息的详细信息。对于浏览器客户端,有一个“白标签”错误视图,在中呈现相同的数据HTML格式(要自定义它,请添加一个解析为错误的视图)。
要完全替换默认行为,可以实现ErrorController并注册该类型的bean定义,或添加ErrorAttributes类型的bean以供使用
现有的机制,但取代了内容。
我们看到这里描述的是,当我们发生错误的时候,他默认提供了一个/error的映射(其实就是一个controller方法),他会给你转到这个映射上面,然后返回不同的视图。其中对于机器客户端请求(比如postman这种)就会返回一个json的响应,自然是包含了你的异常信息的。如果对于浏览器客户端的请求,就会返回一个空白的异常页面,在浏览器端渲染出来。
而且你也可以替换默认行为,自己实现ErrorController。这里我们先不说自定义,我们先来看看,默认行为是不是真的是这样的。
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/testError")
public String testError() {
int a = 1 / 0;
return "error";
}
}
我们声明了一个TestController ,里面有一个get请求,故意制造了一个错误,很经典的错误1/0。
我们分别用postman和浏览器来请求测试一下。
-
postman模拟机器客户端
-
浏览器模拟浏览器客户端
所以我们看到默认情况是没问题的。
2、定制异常返回页面
我们再来看文档的下面一部分。
我们看到这里说的是,你要是觉得那种白页太丑了,确实也太丑了,啥也没有。我们可以自己定制页面。定制页面的方式也很简单,就是在静态资源目录下面放一个目录error,然后目录下面放404的html用来返回404请求,可以放一个5xx的页面用来返回异常的页面,OK,我们就来试试。
我的结构如下:
我自己的页面其实就是显示一个一级标题,404 5XX这样。我们来试试。
我们看到没毛病,完全OK。
二、源码分析
1、组件功能
我们先来看一下源码,而源码的整体流程实现基于这些组件的能力串联起来,最后形成了一个处理流程。
在springboot中我们没有处理过异常,他就给我们提供给了这些能力,那一定是自动装配机制提供的。那我们就去autoconfigure这个包下面去找。
而这个功能其实是web开发才有的,于是就在自动装配的web包下面看看。
最终我们找到一个很像error包:org/springframework/boot/autoconfigure/web/servlet/error
我们看到这个包下面有一个ErrorMvcAutoConfiguration的类,这个一看就是自动注入的核心类,springboot底层各种AutoConfiguration结尾的类都是做自动装配能力的。
于是我们点进去看看,我们看到他注入了很多组件,下面我们一一来分析一下。
鉴于理解顺序,我会从源码位置的从下到上来分析,但是都是在这个类里面的。
而且这个自动配置类有一些生效条件如下。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({
Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
// 属性绑定,你可以在配置文件配置这些内容来替代默认值
@EnableConfigurationProperties({
ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })
组件0:DefaultErrorAttributes视图里面有哪些内容
我们之前在页面看到过,视图返回的不管是浏览器看到的异常白页还是postman看到的异常json,都会有一些属性封装。
我们看到有时间,有异常信息,状态码500等等。这个组件就是决定了有哪些内容的,我们来看下。
@Bean
// 默认的异常处理,如果用户没有配置,就使用这个,你可以自己配置一个ErrorAttributes注入来替换他这个
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
但是我们还是要进去看看,他到底干嘛的。点进去我就后悔了,太TM长了,我们就从这个方法可以看到,他其实就是组装了一个map,里面确定了你能放的属性,也就是最后返回视图的内容。注意这里他组装了一个map,里面放着我们那些异常信息。
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (Boolean.TRUE.equals(this.includeException)) {
options = options.including(Include.EXCEPTION);
}
// 放异常
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
// 放异常堆栈
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.put("message", "");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}
组件1:StaticView 静态视图
这是一个名为静态视图的类,他实现了springmvc中的视图接口view。
其中的render为该视图长啥样的渲染实现,我们就主要来看看这个render方法。注意这个render方法需要一个map为他的静态视图添加异常信息。
private static class StaticView implements View {
// http返回类型为html这种页面类型
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response){
// 设置response返回类型为页面类型,因为视图渲染的就是页面
response.setContentType(TEXT_HTML_UTF8.toString());
// html内容的字符串拼接
StringBuilder builder = new StringBuilder();
// 取出当前时间,这个取出来的就是我们组件0放进去的,来这里拼接页面
Object timestamp = model.get("timestamp");
// 取出异常信息
Object message = model.get("message");
// 取出异常堆栈
Object trace = mod