Spring Boot2 系列教程(十三)Spring Boot 中的全局异常处理

在 Spring Boot 项目中 ,异常统一处理,可使用 Spring 中 @ControllerAdvice 来统一处理,也能够本身来定义异常处理方案。Spring Boot 中,对异常的处理有一些默认的策略,咱们分别来看。html

默认状况下,Spring Boot 中的异常页面 是这样的:java

咱们从这个异常提示中,也能看出来,之因此用户看到这个页面,是由于开发者没有明确提供一个 /error 路径,若是开发者提供了 /error 路径 ,这个页面就不会展现出来,不过在 Spring Boot 中,提供 /error 路径其实是下下策,Spring Boot 自己在处理异常时,也是当全部条件都不知足时,才会去找 /error 路径。那么咱们就先来看看,在 Spring Boot 中,如何自定义 error 页面,总体上来讲,能够分为两种,一种是静态页面,另外一种是动态页面。react

静态异常页面

自定义静态异常页面,又分为两种,第一种 是使用 HTTP 响应码来命名页面,例如 404.html、405.html、500.html ....,另外一种就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面。web

默认是在 classpath:/static/error/ 路径下定义相关页面:ajax

此时,启动项目,若是项目抛出 500 请求错误,就会自动展现 500.html 这个页面,发生 404 就会展现 404.html 页面。若是异常展现页面既存在 5xx.html,也存在 500.html ,此时,发生500异常时,优先展现 500.html 页面。spring

动态异常页面

动态的异常页面定义方式和静态的基本 一致,能够采用的页面模板有 jsp、freemarker、thymeleaf。动态异常页面,也支持 404.html 或者 4xx.html ,可是通常来讲,因为动态异常页面能够直接展现异常详细信息,因此就没有必要挨个枚举错误了 ,直接定义 4xx.html(这里使用thymeleaf模板)或者 5xx.html 便可。后端

注意,动态页面模板,不须要开发者本身去定义控制器,直接定义异常页面便可 ,Spring Boot 中自带的异常处理器会自动查找到异常页面。服务器

页面定义以下:架构

页面内容以下:app

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>5xx</h1>
<table border="1">
    <tr>
        <td>path</td>
        <td th:text="${path}"></td>
    </tr>
    <tr>
        <td>error</td>
        <td th:text="${error}"></td>
    </tr>
    <tr>
        <td>message</td>
        <td th:text="${message}"></td>
    </tr>
    <tr>
        <td>timestamp</td>
        <td th:text="${timestamp}"></td>
    </tr>
    <tr>
        <td>status</td>
        <td th:text="${status}"></td>
    </tr>
</table>
</body>
</html>
复制代码

默认状况下,完整的异常信息就是这5条,展现 效果以下 :

若是动态页面和静态页面同时定义了异常处理页面,例如 classpath:/static/error/404.htmlclasspath:/templates/error/404.html 同时存在时,默认使用动态页面。即完整的错误页面查找方式应该是这样:

发生了 500 错误-->查找动态 500.html 页面-->查找静态 500.html --> 查找动态 5xx.html-->查找静态 5xx.html。

自定义异常数据

默认状况下,在 Spring Boot 中,全部的异常数据其实就是上文所展现出来的 5 条数据,这 5 条数据定义在 org.springframework.boot.web.reactive.error.DefaultErrorAttributes 类中,具体定义在 getErrorAttributes 方法中 :

@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        Throwable error = getError(request);
        HttpStatus errorStatus = determineHttpStatus(error);
        errorAttributes.put("status", errorStatus.value());
        errorAttributes.put("error", errorStatus.getReasonPhrase());
        errorAttributes.put("message", determineMessage(error));
        handleException(errorAttributes, determineException(error), includeStackTrace);
        return errorAttributes;
}
复制代码

DefaultErrorAttributes 类自己则是在 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 异常自动配置类中定义的,若是开发者没有本身提供一个 ErrorAttributes 的实例的话,那么 Spring Boot 将自动提供一个 ErrorAttributes 的实例,也就是 DefaultErrorAttributes 。

基于此 ,开发者自定义 ErrorAttributes 有两种方式 :

  1. 直接实现 ErrorAttributes 接口
  2. 继承 DefaultErrorAttributes(推荐),由于 DefaultErrorAttributes 中对异常数据的处理已经完成,开发者能够直接使用。

具体定义以下:

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        if ((Integer)map.get("status") == 500) {
            map.put("message", "服务器内部错误!");
        }
        return map;
    }
}
复制代码

定义好的 ErrorAttributes 必定要注册成一个 Bean ,这样,Spring Boot 就不会使用默认的 DefaultErrorAttributes 了,运行效果以下图:

自定义异常视图

异常视图默认就是前面所说的静态或者动态页面,这个也是能够自定义的,首先 ,默认的异常视图加载逻辑在 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 类的 errorHtml 方法中,这个方法用来返回异常页面+数据,还有另一个 error 方法,这个方法用来返回异常数据(若是是 ajax 请求,则该方法会被触发)。

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                        request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
复制代码

在该方法中 ,首先会经过 getErrorAttributes 方法去获取异常数据(实际上会调用到 ErrorAttributes 的实例 的 getErrorAttributes 方法),而后调用 resolveErrorView 去建立一个 ModelAndView ,若是这里建立失败,那么用户将会看到默认的错误提示页面。

正常状况下, resolveErrorView 方法会来到 DefaultErrorViewResolver 类的 resolveErrorView 方法中:

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
                modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
        }
        return modelAndView;
}
复制代码

在这里,首先以异常响应码做为视图名分别去查找动态页面和静态页面,若是没有查找到,则再以 4xx 或者 5xx 做为视图名再去分别查找动态或者静态页面。

要自定义异常视图解析,也很容易 ,因为 DefaultErrorViewResolver 是在 ErrorMvcAutoConfiguration 类中提供的实例,即开发者没有提供相关实例时,会使用默认的 DefaultErrorViewResolver ,开发者提供了本身的 ErrorViewResolver 实例后,默认的配置就会失效,所以,自定义异常视图,只须要提供 一个 ErrorViewResolver 的实例便可:

@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
    public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
        super(applicationContext, resourceProperties);
    }
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        return new ModelAndView("/aaa/123", model);
    }
}
复制代码

实际上,开发者也能够在这里定义异常数据(直接在 resolveErrorView 方法从新定义一个 model ,将参数中的model 数据拷贝过去并修改,注意参数中的 model 类型为 UnmodifiableMap,即不能够直接修改),而不须要自定义 MyErrorAttributes。定义完成后,提供一个名为 123 的视图,以下图:

如此以后,错误试图就算定义成功了。

总结

实际上也能够自定义异常控制器 BasicErrorController ,不过松哥以为这样太大动干戈了,不必,前面几种方式已经能够知足咱们的大部分开发需求了。若是是先后端分离架构,异常处理还有其余一些处理方案,这个松哥之后和你们聊。

关注公众号【江南一点雨】,专一于 Spring Boot+微服务以及先后端分离等全栈技术,按期视频教程分享,关注后回复 Java ,领取松哥为你精心准备的 Java 干货!

相关文章
相关标签/搜索